diff --git a/logs/__tests__/ai/status.test.js b/__tests__/ai/status.test.js similarity index 59% rename from logs/__tests__/ai/status.test.js rename to __tests__/ai/status.test.js index fc229578764372a0cd0851efbc4184fe2a4d66a1..c840eaf89b029ffc55be76b1f2e6683ee9f26c50 100644 --- a/logs/__tests__/ai/status.test.js +++ b/__tests__/ai/status.test.js @@ -2,10 +2,7 @@ import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; import supertest from "supertest"; import app from "../../app.js"; -// ignore expiration of the (self-signed) certificate -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; -// set timeout -const BEFORE_ALL_TIMEOUT = 30000; // 30 sec + // set route const ROUTE = '/ai/status'; // prepare response of each test @@ -14,10 +11,39 @@ let response; // ############################ // OBJECTS // ############################ +const mockedVals = vi.hoisted(() => { + return { + foundUser: { + _doc: { + _id: '66a29da2942b3eb', + username: 'snoopy', + name: 'My User', + email: 'user@mail.local', + verified: true, + role: 0, + createdAt: '2024-07 - 25T18: 46: 58.982Z', + updatedAt: '2024-07 - 25T18: 46: 58.982Z', + __v: 0, + fullname: '', + id: '66a29da2942b3ebcaf047f07' + } + } + }; +}); // ############################ // MOCKS // ############################ +// import Database Service +import * as dbService from '../../utils/handleDB.js'; +// mock dbService +vi.mock('../../utils/handleDB.js', async (importOriginal) => { + return { + ...await importOriginal(), + dbConnection: vi.fn(() => 'mocked'), + findOneRecord: vi.fn(() => mockedVals.foundUser), + }; +}); // import AI Service import * as aiService from '../../utils/handleAI.js'; // mock aiService @@ -37,15 +63,12 @@ describe('ai status', () => { beforeAll(async () => { response = await supertest(app) .get(ROUTE); - }, BEFORE_ALL_TIMEOUT); - it('should call required mocks', () => { - expect(aiService.aiIsRunning()).toEqual(true); }); + it('should return a proper status code', () => { expect(response.status).toBe(200); - }); - it('should respond with a proper record and token', () => { - expect(response.body).toEqual({ running: true }); + }); it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); @@ -56,15 +79,12 @@ describe('ai status', () => { aiService.aiIsRunning.mockImplementation(() => false); response = await supertest(app) .get(ROUTE); - }, BEFORE_ALL_TIMEOUT); - it('should call required mocks', () => { - expect(aiService.aiIsRunning()).toEqual(false); }); it('should return a proper status code', () => { expect(response.status).toBe(404); }); - it('should respond with a proper record and token', () => { - expect(response.body).toEqual({ running: false }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); }); \ No newline at end of file diff --git a/__tests__/auth/__snapshots__/confirmpasswordreset.test.js.snap b/__tests__/auth/__snapshots__/confirmpasswordreset.test.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..2262b7d9ddb08157187c3f1c903871242906ac2a --- /dev/null +++ b/__tests__/auth/__snapshots__/confirmpasswordreset.test.js.snap @@ -0,0 +1,48 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`user confirm password reset > given refresh token is malformed > should respond with a proper body 1`] = ` +{ + "message": "Token is no longer valid.", +} +`; + +exports[`user confirm password reset > given required fields are missing > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "confirmPassword": "Required", + }, +} +`; + +exports[`user confirm password reset > given the inputs are correct > should respond with a proper body 1`] = ` +{ + "message": "Password successfully reset. You can now login.", +} +`; + +exports[`user confirm password reset > given the password and confirmPassword do not match > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "confirmPassword": "Passwords don't match", + }, +} +`; + +exports[`user confirm password reset > given token is invalid > should respond with a proper body 1`] = ` +{ + "message": "Token is no longer valid.", +} +`; + +exports[`user confirm password reset > the request body is empty > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "confirmPassword": "Required", + "password": "Required", + "token": "Required", + }, +} +`; diff --git a/__tests__/auth/confirmpasswordreset.test.js b/__tests__/auth/confirmpasswordreset.test.js new file mode 100644 index 0000000000000000000000000000000000000000..bc46af2310c0295fa8343f998a7d1173356f913b --- /dev/null +++ b/__tests__/auth/confirmpasswordreset.test.js @@ -0,0 +1,182 @@ +// import vitest, supertest & app +import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; +import supertest from "supertest"; +import app from "../../app.js"; +import jwt from 'jsonwebtoken'; + +// set route +const ROUTE = '/auth/password-reset'; +// prepare response of each test +let response; + +// ############################ +// OBJECTS +// ############################ +const mockedVals = vi.hoisted(() => { + return { + foundUser: { + _id: '66a29da2942b3eb', + username: 'snoopy', + name: 'My User', + email: 'user@mail.local', + verified: true, + role: 0, + createdAt: '2024-07 - 25T18: 46: 58.982Z', + updatedAt: '2024-07 - 25T18: 46: 58.982Z', + __v: 0, + password: 'StrongPass1!', + resetPasswordToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MOCKED', + id: '66a29da2942b3ebcaf047f07' + }, + validInput: { + token: 'invalidToken', + password: 'SuperPW123!', + confirmPassword: 'SuperPW123!' + } + }; +}); + +// ############################ +// MOCKS +// ############################ +// import Database Service +import * as dbService from '../../utils/handleDB.js'; +// mock dbService +vi.mock('../../utils/handleDB.js', async (importOriginal) => { + return { + ...await importOriginal(), + dbConnection: vi.fn(() => 'mocked'), + findOneRecord: vi.fn(() => mockedVals.foundUser), + updateOneRecord: vi.fn(() => mockedVals.foundUser) + }; +}); + +// import Token Service +import * as tokenService from '../../utils/handleTokens.js'; +// mock dbService +vi.mock('../../utils/handleTokens.js', async (importOriginal) => { + return { + ...await importOriginal(), + verifyVerificationToken: vi.fn((req, res, next) => next()), + }; +}); + +// ############################ +// TESTS +// ############################ +describe('user confirm password reset', () => { + + describe('given the inputs are correct', async () => { + const token = jwt.sign({ id: mockedVals.foundUser.id, email: mockedVals.foundUser.email }, process.env.PASSWORD_TOKEN_KEY + mockedVals.foundUser.password, { expiresIn: process.env.PASSWORD_TOKEN_TTL }); + + // set response by running route + beforeAll(async () => { + + const input = { ...mockedVals.validInput, token }; + + response = await supertest(app) + .patch(ROUTE) + .send(input); + }); + + it('should return a proper status code', () => { + expect(response.status).toBe(200); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + + // ############################ + describe('given refresh token is malformed', () => { + const input = { ...mockedVals.validInput, token: 'malformed-Token' }; + + beforeAll(async () => { + response = await supertest(app) + .patch(ROUTE) + .send(input); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(403); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given token is invalid', () => { + beforeAll(async () => { + + const input = { ...mockedVals.validInput, token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImlhdCI6MTcyMjA5ODM3OX0.7Pq8F2zSDwuEzlCQX3vMZAw9D43N6dSViCyVPZ_s_Zs' }; + + response = await supertest(app) + .patch(ROUTE) + .send(input); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(403); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given required fields are missing', () => { + beforeAll(async () => { + const { confirmPassword, ...input } = mockedVals.validInput; + + response = await supertest(app) + .patch(ROUTE) + .send(input); + }); + + it('should return a proper status code status', () => { + expect(response.status).toBe(400); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given the password and confirmPassword do not match', () => { + beforeAll(async () => { + const input = { ...mockedVals.validInput, confirmPassword: 'StrongPass2!' }; + + response = await supertest(app) + .patch(ROUTE) + .send(input); + }); + + it('should return a proper status code status', () => { + expect(response.status).toBe(400); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('the request body is empty', () => { + beforeAll(async () => { + response = await supertest(app) + .patch(ROUTE) + .send(); + }); + + it('should return a proper status code status', () => { + expect(response.status).toBe(400); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + +}); \ No newline at end of file diff --git a/__tests__/auth/refreshjwt.test.js b/__tests__/auth/refreshjwt.test.js index 7891a265dc9535e1582eda0dd546e8f505cf307c..d801ce03a622e8729d74610c776b0ec93b209f57 100644 --- a/__tests__/auth/refreshjwt.test.js +++ b/__tests__/auth/refreshjwt.test.js @@ -74,8 +74,6 @@ describe('refresh JWT', () => { // ############################ describe('given refresh token is malformed', () => { beforeAll(async () => { - // const refreshToken = jwt.sign({ id: mockedVals.foundUser.id }, process.env.JWT_REFRESH_KEY); - // console.log('refreshToken', refreshToken); response = await supertest(app) .get(ROUTE) .set('Cookie', 'refreshToken=invalid'); diff --git a/__tests__/auth/requestpasswordreset.test.js b/__tests__/auth/requestpasswordreset.test.js index d1f9fdb8ba3b35e9f4b80652f607bc8a8a305863..b4d59b6e7baa91a594d0cf5a2e80277ffee2169e 100644 --- a/__tests__/auth/requestpasswordreset.test.js +++ b/__tests__/auth/requestpasswordreset.test.js @@ -23,7 +23,6 @@ const mockedVals = vi.hoisted(() => { updatedAt: '2024-07 - 25T18: 46: 58.982Z', __v: 0, password: 'StrongPass1!', - // password, id: '66a29da2942b3ebcaf047f07' }, validInput: { diff --git a/__tests__/users/signup.test.js b/__tests__/users/signup.test.js index ef68a14949d20881cb95c57ec5a5043f0fab70cf..f55189311fe1504d61e361873d9470c5a8619314 100644 --- a/__tests__/users/signup.test.js +++ b/__tests__/users/signup.test.js @@ -80,7 +80,7 @@ describe('user registration', () => { }); }); - // // ############################ + // ############################ describe('the request body is empty', () => { beforeAll(async () => { @@ -97,7 +97,7 @@ describe('user registration', () => { }); }); - // // ############################ + // ############################ describe('given the password and confirmPassword do not match', () => { beforeAll(async () => { diff --git a/controllers/Auth.js b/controllers/Auth.js index 324836e33a19a6e36212bd160e5402bef341d4fc..4c098abd0c945ca593bd087ef53f5df7f800966d 100644 --- a/controllers/Auth.js +++ b/controllers/Auth.js @@ -187,7 +187,7 @@ export const passwordReset = async (req, res, next) => { // set new password & remove token req.document.password = req.body.password; req.document.resetPasswordToken = undefined; - console.log(req.document); + // save const updatedRecord = await updateOneRecord(req.document); return res.json({ message: 'Password successfully reset. You can now login.' }); } catch (error) { diff --git a/logs/__tests__/users/confirmpasswordreset.test.js b/logs/__tests__/users/confirmpasswordreset.test.js deleted file mode 100644 index 15df013b01cf2a09414b4e287f40bb3257247245..0000000000000000000000000000000000000000 --- a/logs/__tests__/users/confirmpasswordreset.test.js +++ /dev/null @@ -1,128 +0,0 @@ -// import vitest, supertest & app -import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest'; -import supertest from "supertest"; -import app from "../../app.js"; -// ignore expiration of the (self-signed) certificate -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; -// set timeout -const BEFORE_ALL_TIMEOUT = 30000; // 30 sec -// set route -const ROUTE = '/users/confirmpasswordreset'; -// prepare response of each test -let response; - -// ############################ -// OBJECTS -// ############################ -const invalidTokenResponse = { - code: 404, - message: "Something went wrong while processing your request.", - data: {} -}; - -// ############################ -// MOCKS -// ############################ -// import PocketBase Service -import * as pbService from '../../utils/pocketbase/handlePocketBase.js'; -// mock pbService -vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => { - return { - ...await importOriginal(), - pbConfirmPasswordReset: vi.fn(() => 'mocked') - }; -}); - -// ############################ -// TESTS -// ############################ -describe('user confirm password reset', () => { - describe('given the inputs are correct', async () => { - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ - token: 'invalidToken', - password: 'SuperPW123!', - confirmPassword: 'SuperPW123!' - }); - }, BEFORE_ALL_TIMEOUT); - it('should call required mocks', () => { - expect(pbService.pbConfirmPasswordReset()).toEqual('mocked'); - }); - it('should return a proper status code', () => { - expect(response.status).toBe(200); - }); - it('should respond with a proper message', () => { - expect(response.body.message).toEqual('Password successfully reset. You can now login.'); - }); - }); - - // ############################ - - describe('given the passwords are missing', async () => { - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ - token: 'invalidToken' - }); - }, BEFORE_ALL_TIMEOUT); - it('should return a proper status code', () => { - expect(response.status).toBe(400); - }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors.password).toEqual('Required'); - }); - }); - - // ############################ - - describe('given the passwords do not match', async () => { - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ - token: 'invalidToken', - password: 'SuperPW123!', - confirmPassword: 'SuperPW123' - }); - }, BEFORE_ALL_TIMEOUT); - it('should return a proper status code', () => { - expect(response.status).toBe(400); - }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors.confirmPassword).toEqual("Passwords don't match"); - }); - }); - - // ############################ - - describe('given the token is invalid', async () => { - beforeAll(async () => { - let error = new Error(); - error.name = 'PBError'; - error.response = invalidTokenResponse; - error.status = 400; - pbService.pbConfirmPasswordReset.mockImplementation(() => { throw error; }); - - response = await supertest(app) - .post(ROUTE) - .send({ - token: 'invalidToken', - password: 'SuperPW123!', - confirmPassword: 'SuperPW123!' - }); - }, BEFORE_ALL_TIMEOUT); - it('should force pbConfirmPasswordReset to throw an error', () => { - expect(pbService.pbConfirmPasswordReset).toThrowError(); - }); - it('should return a proper status code', () => { - expect(response.status).toBe(400); - }); - it('should respond with a proper message', () => { - expect(response.body.message).toEqual('Validation errors. Please check the error messages.'); - }); - }); - -}); \ No newline at end of file diff --git a/utils/handleTokens.js b/utils/handleTokens.js index 028bdeadc8e11823c502e2334f8067f795cd743a..fc50473b74cf40958e40b9b7ff01d1e59069c858 100644 --- a/utils/handleTokens.js +++ b/utils/handleTokens.js @@ -161,8 +161,7 @@ export const verifyAccessToken = async (req, res, next) => { */ export const verifyPasswordToken = async (req, res, next) => { // fetch user by token - req.document = await findOneRecord(User, { resetPasswordToken: req.body.token }, '+password'); - // verify token + req.document = await findOneRecord(User, { resetPasswordToken: req.body.token }, '+password'); // verify token jwt.verify(req.body.token, process.env.PASSWORD_TOKEN_KEY + req.document.password, async (error, payload) => { // if invalid if (error) return res.status(403).json({ message: 'Token is no longer valid.' });