diff --git a/__tests__/users/__snapshots__/signup.test.js.snap b/__tests__/users/__snapshots__/signup.test.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..06f2c57ba11b167c895c1af1cbc8eb2b05ddea50 --- /dev/null +++ b/__tests__/users/__snapshots__/signup.test.js.snap @@ -0,0 +1,65 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`user registration > 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 registration > given signing up with duplicate email or username > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "email": "Record with this email already exists.", + }, +} +`; + +exports[`user registration > given the email format is invalid > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "email": "Invalid email", + }, +} +`; + +exports[`user registration > given the inputs are valid > should respond with a proper body 1`] = ` +{ + "message": "Check your emails for the verification link.", +} +`; + +exports[`user registration > 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 registration > given the password does not meet strength requirements > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "password": "This value must be min 6 characters long and contain uppercase, lowercase, number, specialchar.", + }, +} +`; + +exports[`user registration > the request body is empty > should respond with a proper body 1`] = ` +{ + "message": "Validation errors. Please check the error messages.", + "validationErrors": { + "confirmPassword": "Required", + "email": "Required", + "name": "Required", + "password": "Required", + "username": "Required", + }, +} +`; diff --git a/__tests__/users/adminlogin.test.js b/__tests__/users/adminlogin.test.js deleted file mode 100644 index f46b90fe0eb0af23b16c6edce21a55b26c2105c8..0000000000000000000000000000000000000000 --- a/__tests__/users/adminlogin.test.js +++ /dev/null @@ -1,120 +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/adminlogin'; -// prepare response of each test -let response; - -// ############################ -// OBJECTS -// ############################ - -const userLoginVerifiedResponse = { - "record": { - avatar: "", - collectionId: "_pb_users_auth_", - collectionName: "users", - created: "2024-05-06 07:45:18.836Z", - email: "johndoe@local.local", - emailVisibility: false, - id: "jr9mt8yvuri3sbd", - name: "John Doe", - updated: "2024-07-02 13:23:52.155Z", - username: "johndoe", - verified: true - }, - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3MjA2NDk3NTQsImlkIjoianI5bXQ4eXZ1cmkzc2JkIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.yFP1vlM_N2Fvpa_56INlaefSXnpwrm9ASCJuxPwf1Vk" -}; - -const userLoginFailedResponse = { - code: 400, - message: 'Failed to authenticate.', - 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(), - pbAdminLogin: vi.fn(() => userLoginVerifiedResponse) - }; -}); - -// ############################ -// TESTS -// ############################ -describe('user login', () => { - describe('given email and password are valid', () => { - beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ - email: 'valid.user@local.local', - password: 'ValidPassword123' - }); - }, BEFORE_ALL_TIMEOUT); - 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(userLoginVerifiedResponse); - }); - }); - - // ############################ - - describe('given email or password are invalid', () => { - beforeAll(async () => { - let error = new Error('Failed to authenticate.'); - error.name = 'ClientResponseError'; - error.response = userLoginFailedResponse; - error.status = 400; - pbService.pbAdminLogin.mockImplementation(() => { throw error; }); - - response = await supertest(app) - .post(ROUTE) - .send({ - email: 'invalid.user@local.local', - password: 'invalidPassword123' - }); - }, BEFORE_ALL_TIMEOUT); - it('should force pbAdminLogin to throw an error', () => { - expect(pbService.pbAdminLogin).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('Failed to authenticate.'); - }); - }); - - // ############################ - - 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.email).toEqual('Required'); - expect(response.body.validationErrors.password).toEqual('Required'); - }); - }); -}); \ No newline at end of file diff --git a/__tests__/users/signup.test.js b/__tests__/users/signup.test.js index 8ade1a8f4212bc1c1544946442a0a7170c83d88f..3671b952267bf930f728caca4703454de6e28a78 100644 --- a/__tests__/users/signup.test.js +++ b/__tests__/users/signup.test.js @@ -2,60 +2,65 @@ 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/signup'; +const ROUTE = '/users'; // prepare response of each test let response; // ############################ // OBJECTS // ############################ -const userPayload = { - avatar: "", - collectionId: "_pb_users_auth_", - collectionName: "users", - created: "2024-05-06 07:45:18.836Z", - email: "johndoe@local.local", - emailVisibility: false, - id: "jr9mt8yvuri3sbd", - name: "John Doe", - updated: "2024-07-02 13:23:52.155Z", - username: "johndoe", - verified: false -}; - -const userDoubletResponse = { - code: 400, - message: 'Failed to create record.', - data: { - email: { - code: 'validation_invalid_email', - message: 'The email is invalid or already in use.' - }, - username: { - code: 'validation_invalid_username', - message: 'The username is invalid or already in use.' +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: { + name: 'John Doe', + username: 'johndoe', + email: 'john.doe@local.local', + password: 'StrongPass1!', + confirmPassword: 'StrongPass1!' + } + }; +}); // ############################ // MOCKS // ############################ -// import PocketBase Service -import * as pbService from '../../utils/pocketbase/handlePocketBase.js'; -// mock pbService -vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => { +// import Database Service +import * as dbService from '../../utils/handleDB.js'; +// mock dbService +vi.mock('../../utils/handleDB.js', async (importOriginal) => { return { ...await importOriginal(), - pbCreateRecord: vi.fn(() => 'mocked'), - pbRequestVerification: vi.fn(() => 'mocked') + dbConnection: vi.fn(() => 'mocked'), + findOneRecord: vi.fn(() => mockedVals.foundUser), + createRecord: vi.fn(() => mockedVals.foundUser) + }; +}); +// mock mailer +vi.mock('../../utils/handleMailer.js', async (importOriginal) => { + return { + ...await importOriginal(), + sendEmail: vi.fn(() => 'mocked') }; }); @@ -76,20 +81,20 @@ describe('user registration', () => { confirmPassword: 'StrongPass1!' }); }, BEFORE_ALL_TIMEOUT); - it('should call required mocks', () => { - // expect(pbCreateRecord).toHaveBeenCalled(1); - expect(pbService.pbCreateRecord()).toEqual('mocked'); - expect(pbService.pbRequestVerification()).toEqual('mocked'); - }); + // it('should call required mocks', () => { + // // expect(pbCreateRecord).toHaveBeenCalled(1); + // expect(pbService.pbCreateRecord()).toEqual('mocked'); + // expect(pbService.pbRequestVerification()).toEqual('mocked'); + // }); it('should return a proper status code', () => { - expect(response.status).toBe(200); + expect(response.status).toBe(201); }); - it('should respond with a proper message', () => { - expect(response.body.message).toEqual('Check your emails for the verification link.'); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); - // ############################ + // // ############################ describe('the request body is empty', () => { beforeAll(async () => { @@ -101,31 +106,27 @@ describe('user registration', () => { it('should return a proper status code status', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.message).toEqual("Validation errors. Please check the error messages."); + 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) .post(ROUTE) - .send({ - name: 'John Doe', - username: 'johndoe', - email: 'johndoe@local.local', - password: 'Password123!', - confirmPassword: 'Password456', - }); + .send(input); }, BEFORE_ALL_TIMEOUT); it('should return a proper status code status', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors).toEqual({ confirmPassword: "Passwords don't match" }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); @@ -133,21 +134,18 @@ describe('user registration', () => { describe('given required fields are missing', () => { beforeAll(async () => { + const { email, ...input } = mockedVals.validInput; + response = await supertest(app) .post(ROUTE) - .send({ - username: 'missingfields', - name: 'Missing Fields', - password: 'StrongPass123!', - confirmPassword: 'StrongPass123!' - }); + .send(input); }, BEFORE_ALL_TIMEOUT); it('should return a proper status code status', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors).toEqual({ email: 'Required' }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); @@ -155,22 +153,18 @@ describe('user registration', () => { describe('given the email format is invalid', () => { beforeAll(async () => { + const input = { ...mockedVals.validInput, email: 'invalid-email-format' }; + response = await supertest(app) .post(ROUTE) - .send({ - name: 'Invalid Email', - username: 'invalidemail', - email: 'invalid-email-format', - password: 'StrongPass123!', - confirmPassword: 'StrongPass123!' - }); + .send(input); }, BEFORE_ALL_TIMEOUT); it('should return a proper status code status', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors).toEqual({ email: 'Invalid email' }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); @@ -178,45 +172,18 @@ describe('user registration', () => { describe('given the password does not meet strength requirements', () => { beforeAll(async () => { - response = await supertest(app) - .post(ROUTE) - .send({ - name: 'Weak Password', - username: 'weakpassword', - email: 'weak.password@local.local', - password: 'weakpass', - confirmPassword: 'weakpass' - }); - }, BEFORE_ALL_TIMEOUT); - - it('should return a proper status code status', () => { - expect(response.status).toBe(400); - }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors).toEqual({ password: 'This value must be min 6 characters long and contain uppercase, lowercase, number, specialchar.' }); - }); - }); - - // ############################ + const input = { ...mockedVals.validInput, password: 'weakpass', confirmPassword: 'weakpass' }; - describe('given the password and confirmPassword do not match', () => { - beforeAll(async () => { response = await supertest(app) .post(ROUTE) - .send({ - name: 'John Doe', - username: 'johndoe', - email: 'johndoe@local.local', - password: 'Password123!', - confirmPassword: 'Password456', - }); + .send(input); }, BEFORE_ALL_TIMEOUT); it('should return a proper status code status', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors).toEqual({ confirmPassword: "Passwords don't match" }); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); @@ -225,32 +192,40 @@ describe('user registration', () => { describe('given signing up with duplicate email or username', () => { // set response by running route beforeEach(async () => { - - let error = new Error(); - error.name = 'PBError'; - error.response = userDoubletResponse; - error.status = 400; - - pbService.pbCreateRecord.mockImplementation(() => { throw error; }); + let error = new Error('Error: User validation failed: email: Record with this email already exists.'); + + error = { + "errors": { + "email": { + "name": "ValidatorError", + "message": "Record with this email already exists.", + "properties": { + "message": "Record with this email already exists.", + "type": "unique", + "path": "email", + "value": + "user@mail.local" + }, + "kind": "unique", + "path": "email", + "value": "user@mail.local" + } + }, + "_message": "User validation failed", + "name": "ValidationError", + "message": "User validation failed: email: Record with this email already exists." + }; + dbService.createRecord.mockImplementation(() => { throw error; }); response = await supertest(app) .post(ROUTE) - .send({ - name: 'John Doe', - username: 'johndoe', - email: 'john.doe@local.local', - password: 'StrongPass1!', - confirmPassword: 'StrongPass1!' - }); + .send(mockedVals.validInput); }, BEFORE_ALL_TIMEOUT); - it('should force pbCreateRecord to throw an error', () => { - expect(pbService.pbCreateRecord).toThrowError(); - }); it('should return a proper status code', () => { expect(response.status).toBe(400); }); - it('should respond with a proper message', () => { - expect(response.body.validationErrors.email).toEqual('The email is invalid or already in use.'); + it('should respond with a proper body', () => { + expect(response.body).toMatchSnapshot(); }); }); diff --git a/__tests__/ai/chat.test.js b/logs/__tests__/ai/chat.test.js similarity index 100% rename from __tests__/ai/chat.test.js rename to logs/__tests__/ai/chat.test.js diff --git a/__tests__/ai/chats.test.js b/logs/__tests__/ai/chats.test.js similarity index 100% rename from __tests__/ai/chats.test.js rename to logs/__tests__/ai/chats.test.js diff --git a/__tests__/ai/delete.test.js b/logs/__tests__/ai/delete.test.js similarity index 100% rename from __tests__/ai/delete.test.js rename to logs/__tests__/ai/delete.test.js diff --git a/__tests__/ai/model.test.js b/logs/__tests__/ai/model.test.js similarity index 100% rename from __tests__/ai/model.test.js rename to logs/__tests__/ai/model.test.js diff --git a/__tests__/ai/models.test.js b/logs/__tests__/ai/models.test.js similarity index 100% rename from __tests__/ai/models.test.js rename to logs/__tests__/ai/models.test.js diff --git a/__tests__/ai/pull.test.js b/logs/__tests__/ai/pull.test.js similarity index 100% rename from __tests__/ai/pull.test.js rename to logs/__tests__/ai/pull.test.js diff --git a/__tests__/ai/status.test.js b/logs/__tests__/ai/status.test.js similarity index 100% rename from __tests__/ai/status.test.js rename to logs/__tests__/ai/status.test.js diff --git a/__tests__/users/confirmemailchange.test.js b/logs/__tests__/users/confirmemailchange.test.js similarity index 100% rename from __tests__/users/confirmemailchange.test.js rename to logs/__tests__/users/confirmemailchange.test.js diff --git a/__tests__/users/confirmpasswordreset.test.js b/logs/__tests__/users/confirmpasswordreset.test.js similarity index 100% rename from __tests__/users/confirmpasswordreset.test.js rename to logs/__tests__/users/confirmpasswordreset.test.js diff --git a/__tests__/users/confirmverification.test.js b/logs/__tests__/users/confirmverification.test.js similarity index 100% rename from __tests__/users/confirmverification.test.js rename to logs/__tests__/users/confirmverification.test.js diff --git a/__tests__/users/login.test.js b/logs/__tests__/users/login.test.js similarity index 100% rename from __tests__/users/login.test.js rename to logs/__tests__/users/login.test.js diff --git a/__tests__/users/logout.test.js b/logs/__tests__/users/logout.test.js similarity index 100% rename from __tests__/users/logout.test.js rename to logs/__tests__/users/logout.test.js diff --git a/__tests__/users/refreshjwt.test.js b/logs/__tests__/users/refreshjwt.test.js similarity index 100% rename from __tests__/users/refreshjwt.test.js rename to logs/__tests__/users/refreshjwt.test.js diff --git a/__tests__/users/requestemailchange.test.js b/logs/__tests__/users/requestemailchange.test.js similarity index 100% rename from __tests__/users/requestemailchange.test.js rename to logs/__tests__/users/requestemailchange.test.js diff --git a/__tests__/users/requestpasswordreset.test.js b/logs/__tests__/users/requestpasswordreset.test.js similarity index 100% rename from __tests__/users/requestpasswordreset.test.js rename to logs/__tests__/users/requestpasswordreset.test.js diff --git a/__tests__/users/requestverification.test.js b/logs/__tests__/users/requestverification.test.js similarity index 100% rename from __tests__/users/requestverification.test.js rename to logs/__tests__/users/requestverification.test.js diff --git a/utils/handleDB.js b/utils/handleDB.js index 8f74c6a041dbd2d658fd297058968237c719856a..c6e95a8676a996b15b9f691ab70a6c4b58fee1bf 100644 --- a/utils/handleDB.js +++ b/utils/handleDB.js @@ -10,6 +10,9 @@ import { mapChatMessagesToStoredMessages } from "@langchain/core/messages"; * @return {object} database connection */ export const dbConnection = async () => { + // skip if testing + console.log(chalk.blue('connecting to db')); + // else try to connect try { // prevent deprication warning mongoose.set('strictQuery', true); @@ -44,9 +47,7 @@ export const dbConnection = async () => { */ export const createRecord = async (model, data) => { try { - const newRecord = await model.create(data); - // console.log('newRecord', document); - return newRecord; + return await model.create(data); } catch (error) { throw error; } diff --git a/utils/handleErrors.js b/utils/handleErrors.js index 751d0349f3cc95b542c87ee5eaabc02ce9e4e2b4..31116faa15c161bc78411ae91c60dc8700d20a4b 100755 --- a/utils/handleErrors.js +++ b/utils/handleErrors.js @@ -19,8 +19,6 @@ const generateErrorStatusCode = (error) => { switch (error.name) { // VALIDATION ERROR case "ValidationError": - // POCKETBASE ERROR - case "PBError": // ZOD VALIDATION ERROR case "zodError": { return 400; @@ -49,22 +47,6 @@ const generateErrorBody = (error) => { validationErrors }; } - // POCKETBASE ERROR - case "PBError": { - const detailedErrors = error.response.data; - // prepare object of all validation messages - let validationErrors = {}; - // loop through all fields - Object.keys(detailedErrors).forEach((key) => { - // store field errors into messages - validationErrors[key] = detailedErrors[key].message; - }); - // return error body - return { - message: 'Validation errors. Please check the error messages.', - validationErrors - }; - } // ZOD VALIDATION ERROR case "zodError": { let formattedErrors = error.format(); @@ -126,8 +108,8 @@ export const middlewareUnknownRoute = (req, res, next) => { */ export const middlewareErrorHandler = (error, req, res, next) => { - console.error("🚀 ~ middlewareErrorHandler ~ error:", error); - + // console.error("🚀 ~ middlewareErrorHandler ~ error:", error); + console.error("🚀 ~ middlewareErrorHandler ~ error:", JSON.stringify(error)); const customError = new CustomError(error); // return error to user diff --git a/utils/handleMailer.js b/utils/handleMailer.js index 73c2fcd019bb0580e53e8375f193bbe0ffe998d7..db7da824c8b7cc0569edc0b3791dc021da0d1161 100644 --- a/utils/handleMailer.js +++ b/utils/handleMailer.js @@ -2,6 +2,7 @@ import chalk from 'chalk'; import nodemailer from 'nodemailer'; export const sendEmail = async options => { + console.log('original sendEmail'); // create reusable transporter object using the default SMTP transport const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST,