From 2cb9a48d2773faa92e24e725e58d69be7156b410 Mon Sep 17 00:00:00 2001 From: "Embruch, Gerd" <gerd.embruch@uni-hamburg.de> Date: Fri, 26 Jul 2024 22:20:25 +0200 Subject: [PATCH] added vitest config, snapshots & confirmVerification --- .env.template | 4 + __tests__/manualREST/users.rest | 2 +- .../confirmverification.test.js.snap | 38 ++++ __tests__/users/confirmverification.test.js | 184 ++++++++++++++++++ __tests__/users/signup.test.js | 32 +-- .../users/confirmverification.test.js | 111 ----------- utils/handleTokens.js | 3 +- vitest.config.js | 10 + 8 files changed, 248 insertions(+), 136 deletions(-) create mode 100644 __tests__/users/__snapshots__/confirmverification.test.js.snap create mode 100644 __tests__/users/confirmverification.test.js delete mode 100644 logs/__tests__/users/confirmverification.test.js create mode 100644 vitest.config.js diff --git a/.env.template b/.env.template index 8267001..805fc41 100644 --- a/.env.template +++ b/.env.template @@ -1,6 +1,10 @@ ########## # BACKEND SERVER ########## +# THIS FORCES NODE TO IGNORE ERRORS ON SELF-SIGNED CERTIFICATES +# especially useful while testing with vitest +NODE_TLS_REJECT_UNAUTHORIZED = 0 + # define if api is live or in development {devel|prod} API_MODE=devel diff --git a/__tests__/manualREST/users.rest b/__tests__/manualREST/users.rest index fb4e412..d5d8c36 100644 --- a/__tests__/manualREST/users.rest +++ b/__tests__/manualREST/users.rest @@ -50,7 +50,7 @@ Content-Type: application/json { "email": "{{email}}", - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTIzMGJhNzMxOTdmODhlNTRiMGU2YSIsImVtYWlsIjoiZW1icnVjaEB6YmgudW5pLWhhbWJ1cmcuZGUiLCJpYXQiOjE3MjE5MDUzNTgsImV4cCI6MTcyMTkwODk1OH0.KhpauIOF3INIepbmB8ZRH_VDfaxEyEs_OokLSrNR2Nw" + "token": "yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2YTNkYTViYTEwNjUzMmNhZTEyYTYwOSIsImVtYWlsIjoiZW1icnVjaEB6YmgudW5pLWhhbWJ1cmcuZGUiLCJpYXQiOjE3MjIwMTgyNDksImV4cCI6MTcyMjAyMTg0OX0.X0-m9RWqCsE7gSi1ywN9zTcbtpWsWRrHGv50tVy5JmI" } diff --git a/__tests__/users/__snapshots__/confirmverification.test.js.snap b/__tests__/users/__snapshots__/confirmverification.test.js.snap new file mode 100644 index 0000000..74d300f --- /dev/null +++ b/__tests__/users/__snapshots__/confirmverification.test.js.snap @@ -0,0 +1,38 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`user verify registration token > given required fields are missing > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "email": "Required", + }, +} +`; + +exports[`user verify registration token > given the email is unknown > should respond with a proper body 1`] = ` +{ + "message": "Unknown eMail address", +} +`; + +exports[`user verify registration token > given the inputs are valid > should respond with a proper body 1`] = ` +{ + "message": "Account successfully verified. You can now login.", +} +`; + +exports[`user verify registration token > given the request body is empty > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "email": "Required", + "token": "Required", + }, +} +`; + +exports[`user verify registration token > given the token is invalid > should respond with a proper body 1`] = ` +{ + "message": "Token is no longer valid.", +} +`; diff --git a/__tests__/users/confirmverification.test.js b/__tests__/users/confirmverification.test.js new file mode 100644 index 0000000..2ff2d73 --- /dev/null +++ b/__tests__/users/confirmverification.test.js @@ -0,0 +1,184 @@ + +// 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"; + +// set route +const ROUTE = '/auth/verification'; +// prepare response of each test +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' + } + }, + validInput: { + email: 'john.doe@local.local', + token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbGIBBERISHTl9.lxQ5ZqO8qWJt15bbnSa4wrPQ02_7fvY4CgN1ZRM' + }, + jwtPayload: { + "id": "66a29da2942b3ebcaf047f07", + "email": "john.doe@local.local", + "iat": 1722018249, + "exp": 1722021849 + }, + jwtError: { + "name": "TokenExpiredError", + "message": "jwt expired", + "expiredAt": "2024-07-26T18:18:19.000Z" + } + }; +}); + +// ############################ +// 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), + createRecord: vi.fn(() => mockedVals.foundUser), + updateOneRecord: vi.fn(() => mockedVals.foundUser) + }; +}); +// mock mailer +vi.mock('../../utils/handleMailer.js', async (importOriginal) => { + return { + ...await importOriginal(), + sendEmail: vi.fn(() => 'mocked') + }; +}); +// 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 verify registration token', () => { + + + describe('given the inputs are valid', async () => { + // set response by running route + beforeAll(async () => { + response = await supertest(app) + .patch(ROUTE) + .send(mockedVals.validInput); + }); + 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 the email is unknown', async () => { + // set response by running route + beforeAll(async ({ expect, task }) => { + dbService.findOneRecord.mockImplementationOnce(() => null); + + response = await supertest(app) + .patch(ROUTE) + .send(mockedVals.validInput); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(404); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given the token is invalid', async () => { + // set response by running route + beforeAll(async () => { + tokenService.verifyVerificationToken.mockImplementation((req, res, next) => { + return res.status(403).json({ message: 'Token is no longer valid.' }); + }); + + const input = { ...mockedVals.validInput, token: 'invalid-token' }; + + 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 the request body is empty', async () => { + // set response by running route + beforeAll(async () => { + response = await supertest(app) + .patch(ROUTE) + .send(); + }); + it('should return a proper status code', () => { + expect(response.status).toBe(400); + }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); + }); + }); + + // ############################ + + describe('given required fields are missing', () => { + beforeAll(async () => { + const { email, ...input } = mockedVals.validInput; + + response = await supertest(app) + .post(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(); + }); + }); + +}); \ No newline at end of file diff --git a/__tests__/users/signup.test.js b/__tests__/users/signup.test.js index 3671b95..f608858 100644 --- a/__tests__/users/signup.test.js +++ b/__tests__/users/signup.test.js @@ -3,10 +3,6 @@ import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, af 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'; // prepare response of each test @@ -73,19 +69,9 @@ describe('user registration', () => { beforeAll(async () => { response = await supertest(app) .post(ROUTE) - .send({ - name: 'John Doe', - username: 'johndoe', - email: 'john.doe@local.local', - password: 'StrongPass1!', - confirmPassword: 'StrongPass1!' - }); - }, BEFORE_ALL_TIMEOUT); - // it('should call required mocks', () => { - // // expect(pbCreateRecord).toHaveBeenCalled(1); - // expect(pbService.pbCreateRecord()).toEqual('mocked'); - // expect(pbService.pbRequestVerification()).toEqual('mocked'); - // }); + .send(mockedVals.validInput); + }); + it('should return a proper status code', () => { expect(response.status).toBe(201); }); @@ -101,7 +87,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code status', () => { expect(response.status).toBe(400); @@ -120,7 +106,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(input); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code status', () => { expect(response.status).toBe(400); @@ -139,7 +125,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(input); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code status', () => { expect(response.status).toBe(400); @@ -158,7 +144,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(input); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code status', () => { expect(response.status).toBe(400); @@ -177,7 +163,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(input); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code status', () => { expect(response.status).toBe(400); @@ -220,7 +206,7 @@ describe('user registration', () => { response = await supertest(app) .post(ROUTE) .send(mockedVals.validInput); - }, BEFORE_ALL_TIMEOUT); + }); it('should return a proper status code', () => { expect(response.status).toBe(400); }); diff --git a/logs/__tests__/users/confirmverification.test.js b/logs/__tests__/users/confirmverification.test.js deleted file mode 100644 index b3ddc6f..0000000 --- a/logs/__tests__/users/confirmverification.test.js +++ /dev/null @@ -1,111 +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/confirmverification'; -// prepare response of each test -let response; - -// ############################ -// OBJECTS -// ############################ - -const invalidTokenResponse = { - code: 400, - message: 'Something went wrong while processing your request.', - data: { - token: { - code: 'validation_invalid_token_claims', - message: 'Missing email token claim.' - } - } -}; - -// ############################ -// 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(), - pbValidateVerificationToken: vi.fn(() => true) - }; -}); - -// ############################ -// TESTS -// ############################ -describe('user verify registration token', () => { - describe('given the inputs are valid', async () => { - // set response by running route - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjE4NDQ2NDAsImlkIjoiNmdsbDQybXpxaGRnNjl3IiwidHlwZSI6ImFkbWluIn0.QsaIDvhiJ7Vn4_q3TiO0PYcA5P6fMhSXPJQnZAhboJ0' }); - }, BEFORE_ALL_TIMEOUT); - it('should call required mocks', () => { - expect(pbService.pbValidateVerificationToken()).toEqual(true); - }); - it('should return a proper status code', () => { - expect(response.status).toBe(200); - }); - it('should respond with a proper message', () => { - expect(response.body.message).toEqual('Account successfully verified. You can now login.'); - }); - }); - - // ############################ - - describe('given the inputs are invalid', async () => { - // set response by running route - beforeAll(async () => { - - let error = new Error(); - error.name = 'PBError'; - error.response = invalidTokenResponse; - error.status = 400; - - pbService.pbValidateVerificationToken.mockImplementation(() => { throw error; }); - - response = await supertest(app) - .post(ROUTE) - .send({ token: '123' }); - }, BEFORE_ALL_TIMEOUT); - - it('should force pbValidateVerificationToken to throw an error', () => { - expect(pbService.pbValidateVerificationToken).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.'); - }); - }); - - // ############################ - - describe('given the request body is empty', async () => { - // set response by running route - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send(); - }, 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.token).toEqual('Required'); - }); - }); - -}); \ No newline at end of file diff --git a/utils/handleTokens.js b/utils/handleTokens.js index ea88985..3018e55 100644 --- a/utils/handleTokens.js +++ b/utils/handleTokens.js @@ -28,9 +28,10 @@ export const createVerificationToken = (payload) => { */ export const verifyVerificationToken = async (req, res, next) => { // verify token - jwt.verify(req.body.token, process.env.VERIFICATION_TOKEN_KEY + req.document.verified, async (error, payload) => { + const valid = jwt.verify(req.body.token, process.env.VERIFICATION_TOKEN_KEY + req.document.verified, async (error, payload) => { // if invalid if (error) return res.status(403).json({ message: 'Token is no longer valid.' }); + // if valid next(); }); }; diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..e45d1b4 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,10 @@ +import { configDefaults, defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['__tests__/**/*.test.js'], + // clearMocks: true, + // mockReset: true, + mockRestore: true, + }, +}); \ No newline at end of file -- GitLab