From d2a506eaaa4d2f3925433959ddf9b82556deda3a Mon Sep 17 00:00:00 2001 From: "Embruch, Gerd" <gerd.embruch@uni-hamburg.de> Date: Sat, 27 Jul 2024 21:25:05 +0200 Subject: [PATCH] finished testing route ai/getStatus --- .../__tests__ => __tests__}/ai/status.test.js | 50 +++-- .../confirmpasswordreset.test.js.snap | 48 +++++ __tests__/auth/confirmpasswordreset.test.js | 182 ++++++++++++++++++ __tests__/auth/refreshjwt.test.js | 2 - __tests__/auth/requestpasswordreset.test.js | 1 - __tests__/users/signup.test.js | 4 +- controllers/Auth.js | 2 +- .../users/confirmpasswordreset.test.js | 128 ------------ utils/handleTokens.js | 3 +- 9 files changed, 269 insertions(+), 151 deletions(-) rename {logs/__tests__ => __tests__}/ai/status.test.js (59%) create mode 100644 __tests__/auth/__snapshots__/confirmpasswordreset.test.js.snap create mode 100644 __tests__/auth/confirmpasswordreset.test.js delete mode 100644 logs/__tests__/users/confirmpasswordreset.test.js 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 fc22957..c840eaf 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 0000000..2262b7d --- /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 0000000..bc46af2 --- /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 7891a26..d801ce0 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 d1f9fdb..b4d59b6 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 ef68a14..f551893 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 324836e..4c098ab 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 15df013..0000000 --- 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 028bdea..fc50473 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.' }); -- GitLab