From 929a592751c8da565510d0c63f2f0034908befa4 Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:34:47 +0100 Subject: [PATCH 1/9] add tests for findAll --- src/users/users.controller.spec.ts | 187 +++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 90fe441..cbfb6f1 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -9,7 +9,13 @@ import { RegistrationMethod, UserRole, UserStatus, + ApiReq, } from 'src/shared/interfaces'; +// imported to handle the paginated response from findAll +import { Model, Document, Types } from 'mongoose'; +import { IPageable } from 'src/shared/utils'; +type UserDocument = Document & + User & { _id: Types.ObjectId }; describe('UsersAdminController', () => { let controller: UsersController; @@ -53,6 +59,187 @@ describe('UsersAdminController', () => { expect(usersService.findByEmail).toHaveBeenCalledWith('test@example.com'); }); }); + + describe('findAll', () => { + // clearing mock data between test to prevent data leaking + beforeEach(() => { + jest.clearAllMocks(); + }); + + // create a diffenent request for api + const createRequestMock = (query = {}): ApiReq => ({ + query: { + page: '1', + limit: '10', + order: 'DESC', + ...query, + }, + }); + + it('should return paginated users with default parameters', async () => { + // Create mock request + const requestMock = createRequestMock(); + + // mongoose record style and spreads a user data onto it + const mockRecords = [ + { + _id: new Types.ObjectId(), + ...createUserMock({ email: 'user1@example.com' }), + }, + { + _id: new Types.ObjectId(), + ...createUserMock({ email: 'user2@example.com' }), + }, + ]; + // expected findAll service response + const mockPaginatedResponse: IPageable = { + results: mockRecords, + totalRecords: 2, + perPageLimit: 10, + totalPages: 1, + currentPage: 1, + previousPage: null, + nextPage: null, + }; + jest + .spyOn(usersService, 'findAll') + .mockResolvedValue(mockPaginatedResponse); + const result = await adminController.findAll(requestMock); + expect(result).toEqual(mockPaginatedResponse); + expect(usersService.findAll).toHaveBeenCalledWith(requestMock); + }); + it('should handle filtering by user status', async () => { + const requestMock = createRequestMock({ + userByStatuses: `${UserStatus.ACTIVE},${UserStatus.DISABLE}`, + }); + + const mockRecords = [ + { + _id: new Types.ObjectId(), + ...createUserMock({ + email: 'active@example.com', + status: UserStatus.ACTIVE, + }), + }, + ]; + + const mockPaginatedResponse: IPageable = { + results: mockRecords, + totalRecords: 1, + perPageLimit: 10, + totalPages: 1, + currentPage: 1, + previousPage: null, + nextPage: null, + }; + + jest + .spyOn(usersService, 'findAll') + .mockResolvedValue(mockPaginatedResponse); + + const result = await adminController.findAll(requestMock); + + expect(result).toEqual(mockPaginatedResponse); + expect(usersService.findAll).toHaveBeenCalledWith(requestMock); + }); + + it('should handle filtering by user roles', async () => { + const requestMock = createRequestMock({ + userByRoles: `${UserRole.USER},${UserRole.ADMIN}`, + }); + + const mockRecords = [ + { + _id: new Types.ObjectId(), + ...createUserMock({ + email: 'admin@example.com', + role: [UserRole.ADMIN], + }), + }, + ]; + + const mockPaginatedResponse: IPageable = { + results: mockRecords, + totalRecords: 1, + perPageLimit: 10, + totalPages: 1, + currentPage: 1, + previousPage: null, + nextPage: null, + }; + + jest + .spyOn(usersService, 'findAll') + .mockResolvedValue(mockPaginatedResponse); + + const result = await adminController.findAll(requestMock); + + expect(result).toEqual(mockPaginatedResponse); + expect(usersService.findAll).toHaveBeenCalledWith(requestMock); + }); + + it('should handle multiple filters combined', async () => { + const requestMock = createRequestMock({ + userByStatuses: UserStatus.ACTIVE, + userByRoles: UserRole.USER, + userDateRange: '2023-01-01,2023-12-31', + page: '2', + limit: '5', + order: 'ASC', + }); + + const mockRecords = [ + { + _id: new Types.ObjectId(), + ...createUserMock(), + }, + ]; + + const mockPaginatedResponse: IPageable = { + results: mockRecords, + totalRecords: 6, + perPageLimit: 5, + totalPages: 2, + currentPage: 2, + previousPage: 1, + nextPage: null, + }; + + jest + .spyOn(usersService, 'findAll') + .mockResolvedValue(mockPaginatedResponse); + + const result = await adminController.findAll(requestMock); + + expect(result).toEqual(mockPaginatedResponse); + expect(usersService.findAll).toHaveBeenCalledWith(requestMock); + }); + + it('should handle empty results', async () => { + const requestMock = createRequestMock({ + userByStatuses: UserStatus.DEACTIVATED, + }); + + const mockPaginatedResponse: IPageable = { + results: [], + totalRecords: 0, + perPageLimit: 10, + totalPages: 0, + currentPage: 1, + previousPage: null, + nextPage: null, + }; + + jest + .spyOn(usersService, 'findAll') + .mockResolvedValue(mockPaginatedResponse); + + const result = await adminController.findAll(requestMock); + + expect(result).toEqual(mockPaginatedResponse); + expect(usersService.findAll).toHaveBeenCalledWith(requestMock); + }); + }); }); /* From 066e7e750256338203dff832b3b256cfb44c394a Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:24:22 +0100 Subject: [PATCH 2/9] add test for auth --- src/shared/auth/auth.controller.spec.ts | 114 +++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/src/shared/auth/auth.controller.spec.ts b/src/shared/auth/auth.controller.spec.ts index 71bb2ce..c295deb 100644 --- a/src/shared/auth/auth.controller.spec.ts +++ b/src/shared/auth/auth.controller.spec.ts @@ -2,21 +2,131 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { TestModule } from '../testkits'; +import { getModelToken } from '@nestjs/mongoose'; +import { JwtService } from '@nestjs/jwt'; +import { DBModule, User, UserDocument } from '../schema'; +import { + ApplicationStatus, + RegistrationMethod, + UserRole, + UserStatus, +} from '../interfaces'; +import { CreateUserDto } from '../dtos/create-user.dto'; +import { Model } from 'mongoose'; + +jest.setTimeout(10000) +// Create mock user data +const createUserMock = (overrides: Partial = {}): User => { + return { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + password: 'hashedpassword123', + profileSummary: 'Experienced software engineer', + jobTitle: 'Senior Developer', + currentCompany: 'TechCorp', + photo: 'profilephoto.jpg', + age: 30, + phone: '123-456-7890', + userHandle: 'johnDoe123', + gender: 'male', + location: { + type: 'Point', + coordinates: [40.7128, -74.006], + }, + deviceId: 'device12345', + deviceToken: 'deviceToken12345', + role: [UserRole.USER], + leadPosition: 'Tech Lead', + applicationStatus: ApplicationStatus.PENDING, + nextApplicationTime: new Date(), + joinMethod: RegistrationMethod.SIGN_UP, + status: UserStatus.ACTIVE, + emailVerification: true, + pendingInvitation: false, + socials: { + phoneNumber: '24242424', + email: 'balbal', + }, + nextVerificationRequestDate: new Date(), + ...overrides, + }; +}; describe('AuthController', () => { let controller: AuthController; + let authService: AuthService; + let userModel: Model; + + const mockUser = createUserMock(); + + // mock the user model services + const mockUserModel = { + signUp: jest.fn(), + verifyEmail: jest.fn(), + forgetPassword: jest.fn(), + }; + + // mock auth services + const mockAuthService = { + login: jest.fn(), + resendVerificationLink: jest.fn(), + sendEmailVerificationToken: jest.fn(), + }; + + // mock jwt services + const mockJwtService = { + sign: jest.fn().mockReturnValue('mocked-token'), + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [TestModule], + imports: [TestModule, DBModule], controllers: [AuthController], - providers: [AuthService], + providers: [ + { + provide: AuthService, + useValue: mockAuthService, + }, + { + provide: getModelToken(User.name), + useValue: mockUserModel, + }, + { + provide: JwtService, + useValue: mockJwtService, + }, + ], }).compile(); controller = module.get(AuthController); + authService = module.get(AuthService); + userModel = module.get>(getModelToken(User.name)); }); it('should be defined', () => { expect(controller).toBeDefined(); }); + + describe('register', () => { + it('should register a new user successfully', async () => { + const createUserDto: CreateUserDto = { + email: 'test@example.com', + password: 'Inventors@2024', + firstName: 'Test', + lastName: 'User', + joinMethod: RegistrationMethod.SIGN_UP, + }; + + const req = { user: { id: 'testId', email: 'test@example.com' } }; // Mock request + const expectedResult = { ...mockUser, password: undefined }; + + mockUserModel.signUp.mockResolvedValue(expectedResult); + + const result = await controller.register(req, createUserDto); + + expect(result).toEqual(expectedResult); + expect(mockUserModel.signUp).toHaveBeenCalledWith(req, createUserDto); + }); + }); }); From 67ccec58455bfebe96f308fbf669bc319deae13a Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Sun, 20 Oct 2024 19:19:37 +0100 Subject: [PATCH 3/9] add some auth test --- src/shared/auth/auth.controller.spec.ts | 239 +++++++++++++++++------- test/jest-e2e.json | 3 +- 2 files changed, 173 insertions(+), 69 deletions(-) diff --git a/src/shared/auth/auth.controller.spec.ts b/src/shared/auth/auth.controller.spec.ts index c295deb..5bc109e 100644 --- a/src/shared/auth/auth.controller.spec.ts +++ b/src/shared/auth/auth.controller.spec.ts @@ -1,87 +1,57 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { TestModule } from '../testkits'; -import { getModelToken } from '@nestjs/mongoose'; -import { JwtService } from '@nestjs/jwt'; +import { Model, Types } from 'mongoose'; import { DBModule, User, UserDocument } from '../schema'; -import { - ApplicationStatus, - RegistrationMethod, - UserRole, - UserStatus, -} from '../interfaces'; import { CreateUserDto } from '../dtos/create-user.dto'; -import { Model } from 'mongoose'; +import { UserLoginDto } from '../dtos/user-login.dto'; +import { JwtService } from '@nestjs/jwt'; +import { getModelToken } from '@nestjs/mongoose'; +import { BadRequestException } from '@nestjs/common'; +import { RegistrationMethod } from '../interfaces'; -jest.setTimeout(10000) -// Create mock user data -const createUserMock = (overrides: Partial = {}): User => { - return { - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - password: 'hashedpassword123', - profileSummary: 'Experienced software engineer', - jobTitle: 'Senior Developer', - currentCompany: 'TechCorp', - photo: 'profilephoto.jpg', - age: 30, - phone: '123-456-7890', - userHandle: 'johnDoe123', - gender: 'male', - location: { - type: 'Point', - coordinates: [40.7128, -74.006], - }, - deviceId: 'device12345', - deviceToken: 'deviceToken12345', - role: [UserRole.USER], - leadPosition: 'Tech Lead', - applicationStatus: ApplicationStatus.PENDING, - nextApplicationTime: new Date(), - joinMethod: RegistrationMethod.SIGN_UP, - status: UserStatus.ACTIVE, - emailVerification: true, - pendingInvitation: false, - socials: { - phoneNumber: '24242424', - email: 'balbal', - }, - nextVerificationRequestDate: new Date(), - ...overrides, - }; -}; +// Increase Jest timeout globally for this file +jest.setTimeout(15000); describe('AuthController', () => { let controller: AuthController; let authService: AuthService; let userModel: Model; - const mockUser = createUserMock(); + const mockUser = { + _id: new Types.ObjectId().toString(), + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + password: 'hashedPassword', + role: 'user', + }; - // mock the user model services const mockUserModel = { signUp: jest.fn(), verifyEmail: jest.fn(), forgetPassword: jest.fn(), + findById: jest.fn(), + findOne: jest.fn(), + }; + + const mockJwtService = { + sign: jest.fn().mockReturnValue('mock_token'), }; - // mock auth services const mockAuthService = { login: jest.fn(), resendVerificationLink: jest.fn(), sendEmailVerificationToken: jest.fn(), - }; - - // mock jwt services - const mockJwtService = { - sign: jest.fn().mockReturnValue('mocked-token'), + generateToken: jest.fn().mockReturnValue({ access_token: 'mock_token' }), }; beforeEach(async () => { + // Clear all mocks before each test + jest.clearAllMocks(); + const module: TestingModule = await Test.createTestingModule({ - imports: [TestModule, DBModule], + imports: [DBModule], controllers: [AuthController], providers: [ { @@ -102,6 +72,9 @@ describe('AuthController', () => { controller = module.get(AuthController); authService = module.get(AuthService); userModel = module.get>(getModelToken(User.name)); + + // Set up global.jwtService for the auth service + global.jwtService = mockJwtService; }); it('should be defined', () => { @@ -109,24 +82,154 @@ describe('AuthController', () => { }); describe('register', () => { - it('should register a new user successfully', async () => { + // it('should register a new user with valid password', async () => { + // const createUserDto: CreateUserDto = { + // email: 'test@example.com', + // password: 'Test123!@#', + // firstName: 'John', + // lastName: 'Doe', + // joinMethod: RegistrationMethod.SIGN_UP, + // }; + // const req = { headers: {} }; + + // mockUserModel.signUp.mockResolvedValue(mockUser); + + // // Add timeout and ensure promise resolution + // const result = await Promise.resolve( + // controller.register(req, createUserDto), + // ); + + // expect(mockUserModel.signUp).toHaveBeenCalledWith(req, createUserDto); + // expect(result).toEqual(mockUser); + // }, 15000); + + it('should throw BadRequestException for invalid password', async () => { const createUserDto: CreateUserDto = { email: 'test@example.com', - password: 'Inventors@2024', - firstName: 'Test', - lastName: 'User', + password: 'simple', + firstName: 'John', + lastName: 'Doe', joinMethod: RegistrationMethod.SIGN_UP, }; + const req = { headers: {} }; - const req = { user: { id: 'testId', email: 'test@example.com' } }; // Mock request - const expectedResult = { ...mockUser, password: undefined }; + mockUserModel.signUp.mockRejectedValue( + new BadRequestException( + 'Password must contain a number, special character, alphabet both upper and lower cased, and must be at least of 6 letters.', + ), + ); - mockUserModel.signUp.mockResolvedValue(expectedResult); + await expect(controller.register(req, createUserDto)).rejects.toThrow( + BadRequestException, + ); + }, 15000); + }); - const result = await controller.register(req, createUserDto); + describe('login', () => { + it('should login a user and return access token', async () => { + const userLoginDto: UserLoginDto = { + email: 'test@example.com', + password: 'Test123!@#', + }; + const req = { user: mockUser }; + const expectedResponse = { + access_token: 'mock_token', + ...mockUser, + }; + + mockAuthService.login.mockResolvedValue(expectedResponse); + + const result = await Promise.resolve(controller.login(req, userLoginDto)); + + expect(mockAuthService.login).toHaveBeenCalledWith(req); + expect(result).toEqual(expectedResponse); + }, 15000); + }); + + // describe('verifyEmail', () => { + // it('should verify user email', async () => { + // const userId = new Types.ObjectId().toString(); + // const token = 'verification_token'; + + // mockUserModel.verifyEmail.mockResolvedValue({ verified: true }); + + // const result = await Promise.resolve( + // controller.verifyEmail(userId, token), + // ); + + // expect(mockUserModel.verifyEmail).toHaveBeenCalledWith(userId, token); + // expect(result).toEqual({ verified: true }); + // }, 15000); + // }); + + describe('resendVerification', () => { + it('should resend verification email', async () => { + const email = 'test@example.com'; + const req = { headers: {} }; + const expectedResponse = { message: 'Verification email sent' }; + + mockAuthService.resendVerificationLink.mockResolvedValue( + expectedResponse, + ); + + const result = await Promise.resolve( + controller.resendVerification(req, email), + ); - expect(result).toEqual(expectedResult); - expect(mockUserModel.signUp).toHaveBeenCalledWith(req, createUserDto); - }); + expect(mockAuthService.resendVerificationLink).toHaveBeenCalledWith( + req, + email, + ); + expect(result).toEqual(expectedResponse); + }, 15000); }); + + describe('sendEmailVerificationToken', () => { + it('should send email verification token with valid userId', async () => { + const userId = new Types.ObjectId().toString(); + const req = { headers: {} }; + const expectedResponse = { message: 'Verification token sent' }; + + mockAuthService.sendEmailVerificationToken.mockResolvedValue( + expectedResponse, + ); + + const result = await Promise.resolve( + controller.sendEmailVerificationToken(req, userId), + ); + + expect(mockAuthService.sendEmailVerificationToken).toHaveBeenCalledWith( + req, + userId, + ); + expect(result).toEqual(expectedResponse); + }, 15000); + + it('should throw error for invalid userId format', async () => { + const userId = 'invalid-id'; + const req = { headers: {} }; + + mockAuthService.sendEmailVerificationToken.mockRejectedValue( + new Error('Invalid ObjectId'), + ); + + await expect( + controller.sendEmailVerificationToken(req, userId), + ).rejects.toThrow(); + }, 15000); + }); + + // describe('forgetPassword', () => { + // it('should handle forget password request', async () => { + // const email = 'test@example.com'; + // const expectedResponse = { message: 'Reset password email sent' }; + + // mockUserModel.forgetPassword.mockResolvedValue(expectedResponse); + + // const result = await Promise.resolve(controller.forgetPassword(email)); + + // expect(mockUserModel.forgetPassword).toHaveBeenCalledWith(email); + // expect(result).toEqual(expectedResponse); + // }, 15000); + // }); }); diff --git a/test/jest-e2e.json b/test/jest-e2e.json index 1580f5b..867dbd1 100644 --- a/test/jest-e2e.json +++ b/test/jest-e2e.json @@ -7,6 +7,7 @@ "^.+\\.(t|j)s$": "ts-jest" }, "moduleNameMapper": { - "^src/(.*)$": "/$1" + "^src/(.*)$": "/$1", + "testTimeout": 10000 } } From 295d6fe772c2f463f4485909462f827f332cc5ad Mon Sep 17 00:00:00 2001 From: Stefano <30480337+snebo@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:13:39 +0100 Subject: [PATCH 4/9] add passowrd test --- src/users/users.controller.spec.ts | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 90fe441..8886f52 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -10,6 +10,8 @@ import { UserRole, UserStatus, } from 'src/shared/interfaces'; +import { mock } from 'node:test'; +import { Types } from 'mongoose'; describe('UsersAdminController', () => { let controller: UsersController; @@ -38,7 +40,7 @@ describe('UsersAdminController', () => { const userMock = createUserMock({ email: 'test@example.com', }); - // redirecting the dbquery to user the userMock + // redirecting the dbquery to use the userMock jest.spyOn(usersService, 'findByEmail').mockResolvedValue(userMock); const result = await adminController.findByUsername(requestMock.email); @@ -53,6 +55,38 @@ describe('UsersAdminController', () => { expect(usersService.findByEmail).toHaveBeenCalledWith('test@example.com'); }); }); + describe('change Password', () => { + const mockUser = createUserMock({_id: new Types.ObjectId(),}); + const mockRequest = { + user: { + _id: mockUser._id, + email: mockUser.email, + role: [UserRole.ADMIN], + }, + }; + const UserChangePasswordDto = { + oldPassword: 'oldPassword@123' + newPassword: 'newPassword@123', + confirmPassword: 'newPassword@123', + }; + + it('should change the user password', async () => { + const updatedUser = { ...mockUser, password: 'newHashedPassword' }; + jest.spyOn(usersService, 'changePassword').mockResolvedValue(updatedUser); + const result = await adminController.changePassword( + mockRequest, + mockUser._id.toString(), + UserChangePasswordDto, + ); + expect(result).toEqual(updatedUser); + expect(usersService.changePassword).toHaveBeenLastCalledWith( + mockRequest, + mockUser._id.toString(), + UserChangePasswordDto, + true, + ); + }); + }); }); /* @@ -61,6 +95,7 @@ describe('UsersAdminController', () => { */ const createUserMock = (overrides: Partial = {}): User => { return { + _id: new Types.ObjectId(), firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com', From 62068a20fd6604c2aa616a7dfe60fb034f1638a4 Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:43:54 +0100 Subject: [PATCH 5/9] auth tests --- src/shared/auth/auth.controller.spec.ts | 200 ++++++++++++++++-------- src/shared/auth/db.module.mock.ts | 16 ++ 2 files changed, 152 insertions(+), 64 deletions(-) create mode 100644 src/shared/auth/db.module.mock.ts diff --git a/src/shared/auth/auth.controller.spec.ts b/src/shared/auth/auth.controller.spec.ts index 5bc109e..79b6395 100644 --- a/src/shared/auth/auth.controller.spec.ts +++ b/src/shared/auth/auth.controller.spec.ts @@ -8,7 +8,7 @@ import { UserLoginDto } from '../dtos/user-login.dto'; import { JwtService } from '@nestjs/jwt'; import { getModelToken } from '@nestjs/mongoose'; import { BadRequestException } from '@nestjs/common'; -import { RegistrationMethod } from '../interfaces'; +import { RegistrationMethod, UserRole } from '../interfaces'; // Increase Jest timeout globally for this file jest.setTimeout(15000); @@ -24,15 +24,27 @@ describe('AuthController', () => { firstName: 'John', lastName: 'Doe', password: 'hashedPassword', - role: 'user', + role: [UserRole.ADMIN], + userHandle: 'randomP', + emailVerification: false, }; + // Mock static methods that exist in the schema const mockUserModel = { + // Static methods signUp: jest.fn(), verifyEmail: jest.fn(), forgetPassword: jest.fn(), + generateUserHandle: jest.fn(), + sendEmailVerificationToken: jest.fn(), + + // Mongoose model methods findById: jest.fn(), findOne: jest.fn(), + create: jest.fn(), + updateOne: jest.fn(), + findOneAndUpdate: jest.fn(), + find: jest.fn(), }; const mockJwtService = { @@ -47,7 +59,6 @@ describe('AuthController', () => { }; beforeEach(async () => { - // Clear all mocks before each test jest.clearAllMocks(); const module: TestingModule = await Test.createTestingModule({ @@ -73,58 +84,109 @@ describe('AuthController', () => { authService = module.get(AuthService); userModel = module.get>(getModelToken(User.name)); - // Set up global.jwtService for the auth service - global.jwtService = mockJwtService; - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - describe('register', () => { - // it('should register a new user with valid password', async () => { - // const createUserDto: CreateUserDto = { - // email: 'test@example.com', - // password: 'Test123!@#', - // firstName: 'John', - // lastName: 'Doe', - // joinMethod: RegistrationMethod.SIGN_UP, - // }; - // const req = { headers: {} }; - - // mockUserModel.signUp.mockResolvedValue(mockUser); - - // // Add timeout and ensure promise resolution - // const result = await Promise.resolve( - // controller.register(req, createUserDto), - // ); - - // expect(mockUserModel.signUp).toHaveBeenCalledWith(req, createUserDto); - // expect(result).toEqual(mockUser); - // }, 15000); - - it('should throw BadRequestException for invalid password', async () => { - const createUserDto: CreateUserDto = { - email: 'test@example.com', - password: 'simple', - firstName: 'John', - lastName: 'Doe', - joinMethod: RegistrationMethod.SIGN_UP, + // Set up specific mock implementations + mockUserModel.signUp.mockImplementation( + async (req, createUserDto, sso = false) => { + const userHandle = await mockUserModel.generateUserHandle( + createUserDto.email, + ); + const user = { + ...mockUser, + ...createUserDto, + firstName: createUserDto.firstName.trim(), + lastName: createUserDto.lastName.trim(), + email: createUserDto.email.trim().toLowerCase(), + password: 'hashedPassword', + role: [UserRole.USER], + userHandle, + }; + + if (user.email === 'existing@example.com') { + throw new BadRequestException('User already exists.'); + } + + const verificationDetails = sso + ? {} + : { + emailVerificationCode: '123456', + emailVerificationUrl: 'http://example.com/verify', + }; + + return { ...verificationDetails, ...user }; + }, + ); + + mockUserModel.verifyEmail.mockImplementation(async (userId, token) => { + if (!userId || !token) { + throw new BadRequestException('Invalid userId or token'); + } + return { status: 200, message: 'Email Verification Successful' }; + }); + + mockUserModel.generateUserHandle.mockImplementation(async (email) => { + const username = email.substring(0, email.indexOf('@')).toLowerCase(); + return `${username}123`; + }); + + mockUserModel.sendEmailVerificationToken.mockImplementation( + async (req, userId) => { + return { + emailVerificationCode: '123456', + emailVerificationUrl: 'http://example.com/verify', + }; + }, + ); + + mockUserModel.findOne.mockImplementation(async ({ email }) => { + if (email === 'existing@example.com') { + return { + _id: new Types.ObjectId(), + ...mockUser, + }; + } + return null; + }); + + mockUserModel.findById.mockImplementation(async (id) => { + return { + _id: id, + ...mockUser, }; - const req = { headers: {} }; - - mockUserModel.signUp.mockRejectedValue( - new BadRequestException( - 'Password must contain a number, special character, alphabet both upper and lower cased, and must be at least of 6 letters.', - ), - ); - - await expect(controller.register(req, createUserDto)).rejects.toThrow( - BadRequestException, - ); - }, 15000); + }); }); + // describe('register', () => { + // it('should register a new user with valid password', async () => { + // const createUserDto: CreateUserDto = { + // email: 'test@example.com', + // password: 'Test123!@#', + // firstName: 'John', + // lastName: 'Doe', + // joinMethod: RegistrationMethod.SIGN_UP, + // }; + // const req = { + // headers: {}, + // query: {}, + // }; + + // const expectedResponse = { + // ...mockUser, + // ...createUserDto, + // password: 'hashedPassword', + // verificationToken: 'mocked-verification-token', + // }; + + // const result = await controller.register(req, createUserDto); + + // expect(mockUserModel.signUp).toHaveBeenCalledWith( + // req, + // createUserDto, + // false, + // ); + // expect(result).toMatchObject(expectedResponse); + // }, 15000); + // }); + describe('login', () => { it('should login a user and return access token', async () => { const userLoginDto: UserLoginDto = { @@ -146,21 +208,31 @@ describe('AuthController', () => { }, 15000); }); - // describe('verifyEmail', () => { - // it('should verify user email', async () => { - // const userId = new Types.ObjectId().toString(); - // const token = 'verification_token'; + describe('verifyEmail', () => { + // it('should verify user email', async () => { + // const userId = new Types.ObjectId().toString(); + // const token = 'verification_token'; - // mockUserModel.verifyEmail.mockResolvedValue({ verified: true }); + // const expectedResponse = { + // verified: true, + // message: 'Email verified successfully', + // }; - // const result = await Promise.resolve( - // controller.verifyEmail(userId, token), - // ); + // const result = await controller.verifyEmail(userId, token); - // expect(mockUserModel.verifyEmail).toHaveBeenCalledWith(userId, token); - // expect(result).toEqual({ verified: true }); - // }, 15000); - // }); + // expect(mockUserModel.verifyEmail).toHaveBeenCalledWith(userId, token); + // expect(result).toEqual(expectedResponse); + // }, 15000); + + it('should throw BadRequestException for invalid verification attempt', async () => { + const userId = ''; // Invalid userId + const token = 'verification_token'; + + await expect(controller.verifyEmail(userId, token)).rejects.toThrow( + BadRequestException, + ); + }, 15000); + }); describe('resendVerification', () => { it('should resend verification email', async () => { diff --git a/src/shared/auth/db.module.mock.ts b/src/shared/auth/db.module.mock.ts new file mode 100644 index 0000000..8d86f8e --- /dev/null +++ b/src/shared/auth/db.module.mock.ts @@ -0,0 +1,16 @@ +// db.module.mock.ts +import { Module } from '@nestjs/common'; +import { getModelToken } from '@nestjs/mongoose'; +import { User } from '../schema'; // Adjust the path as necessary +import { mockUserModel } from './auth.controller.spec'; + +@Module({ + providers: [ + { + provide: getModelToken(User.name), + useValue: mockUserModel, // Use your mocked user model here + }, + ], + exports: [getModelToken(User.name)], // Export the mocked model if needed +}) +export class MockDBModule {} From a65e679c8e648228c871a386dcd09324996e45eb Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:06:22 +0100 Subject: [PATCH 6/9] working snap --- src/users/users.controller.spec.ts | 68 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index e00664f..8709eff 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -16,8 +16,6 @@ import { Model, Document, Types } from 'mongoose'; import { IPageable } from 'src/shared/utils'; type UserDocument = Document & User & { _id: Types.ObjectId }; -import { mock } from 'node:test'; -import { Types } from 'mongoose'; describe('UsersAdminController', () => { let controller: UsersController; @@ -242,38 +240,38 @@ describe('UsersAdminController', () => { expect(usersService.findAll).toHaveBeenCalledWith(requestMock); }); }); - describe('change Password', () => { - const mockUser = createUserMock({_id: new Types.ObjectId(),}); - const mockRequest = { - user: { - _id: mockUser._id, - email: mockUser.email, - role: [UserRole.ADMIN], - }, - }; - const UserChangePasswordDto = { - oldPassword: 'oldPassword@123' - newPassword: 'newPassword@123', - confirmPassword: 'newPassword@123', - }; - - it('should change the user password', async () => { - const updatedUser = { ...mockUser, password: 'newHashedPassword' }; - jest.spyOn(usersService, 'changePassword').mockResolvedValue(updatedUser); - const result = await adminController.changePassword( - mockRequest, - mockUser._id.toString(), - UserChangePasswordDto, - ); - expect(result).toEqual(updatedUser); - expect(usersService.changePassword).toHaveBeenLastCalledWith( - mockRequest, - mockUser._id.toString(), - UserChangePasswordDto, - true, - ); - }); - }); + // describe('change Password', () => { + // const mockUser = createUserMock({_id: new Types.ObjectId(),}); + // const mockRequest = { + // user: { + // _id: mockUser._id, + // email: mockUser.email, + // role: [UserRole.ADMIN], + // }, + // }; + // const UserChangePasswordDto = { + // oldPassword: 'oldPassword@123' + // newPassword: 'newPassword@123', + // confirmPassword: 'newPassword@123', + // }; + + // it('should change the user password', async () => { + // const updatedUser = { ...mockUser, password: 'newHashedPassword' }; + // jest.spyOn(usersService, 'changePassword').mockResolvedValue(updatedUser); + // const result = await adminController.changePassword( + // mockRequest, + // mockUser._id.toString(), + // UserChangePasswordDto, + // ); + // expect(result).toEqual(updatedUser); + // expect(usersService.changePassword).toHaveBeenLastCalledWith( + // mockRequest, + // mockUser._id.toString(), + // UserChangePasswordDto, + // true, + // ); + // }); + // }); }); /* @@ -282,7 +280,7 @@ describe('UsersAdminController', () => { */ const createUserMock = (overrides: Partial = {}): User => { return { - _id: new Types.ObjectId(), + // _id: new Types.ObjectId(), firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com', From 8f8c9806867343fd8883c0c78ef0203071b8b46f Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:27:44 +0100 Subject: [PATCH 7/9] add auth controller tests --- src/shared/auth/auth.controller.spec.ts | 515 ++++++++++++++---------- 1 file changed, 299 insertions(+), 216 deletions(-) diff --git a/src/shared/auth/auth.controller.spec.ts b/src/shared/auth/auth.controller.spec.ts index 79b6395..6b80b63 100644 --- a/src/shared/auth/auth.controller.spec.ts +++ b/src/shared/auth/auth.controller.spec.ts @@ -1,68 +1,146 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { Model, Types } from 'mongoose'; -import { DBModule, User, UserDocument } from '../schema'; -import { CreateUserDto } from '../dtos/create-user.dto'; -import { UserLoginDto } from '../dtos/user-login.dto'; -import { JwtService } from '@nestjs/jwt'; -import { getModelToken } from '@nestjs/mongoose'; +import { User, UserDocument } from '../schema'; +import { Model } from 'mongoose'; import { BadRequestException } from '@nestjs/common'; -import { RegistrationMethod, UserRole } from '../interfaces'; - -// Increase Jest timeout globally for this file -jest.setTimeout(15000); +import { RegistrationMethod, UserRole, UserStatus } from '../interfaces'; +import { JwtService } from '@nestjs/jwt'; +// extending mongoose model ot allow us to use custom mocks +// used jest.mock becasue i'll mock them in the test +interface UserModel extends Model { + verifyEmail: jest.Mock; + signUp: jest.Mock; + forgetPassword: jest.Mock; + generateUserHandle: jest.Mock; + sendEmailVerificationToken: jest.Mock; +} + +// requiered to tell typescript which methods we are mocking +// if removed, things like "authserice.resendverificationlink.mockRejectedValues" will not work +interface MockAuthService { + login: jest.Mock; + resendVerificationLink: jest.Mock; + sendEmailVerificationToken: jest.Mock; + findByUsername: jest.Mock; + validateUser: jest.Mock; +} + +// Main test container describe('AuthController', () => { let controller: AuthController; - let authService: AuthService; - let userModel: Model; + let userModel: UserModel; + let authService: MockAuthService; + let jwtService: JwtService; + // Mock data for typical user object const mockUser = { - _id: new Types.ObjectId().toString(), - email: 'test@example.com', + _id: '507f1f77bcf86cd799439011', firstName: 'John', lastName: 'Doe', - password: 'hashedPassword', - role: [UserRole.ADMIN], - userHandle: 'randomP', + email: 'john@example.com', + userHandle: 'johndoe', emailVerification: false, + joinMethod: RegistrationMethod.SIGN_UP, + role: [UserRole.ADMIN], + status: UserStatus.ACTIVE, + }; + + // mock a typical Http request object + const mockReq = { + protocol: 'http', + get: jest.fn().mockReturnValue('localhost'), + originalUrl: '/api/v1/auth', + query: {}, }; - // Mock static methods that exist in the schema + // to avoid conflict like services calling redis and db static functions, mocked + // services are defined here + const mockAuthService: MockAuthService = { + login: jest.fn().mockImplementation((req) => { + return Promise.resolve({ + access_token: 'mock_token', // will be what is returned as the token + ...mockUser, + }); + }), + resendVerificationLink: jest.fn().mockImplementation((req, email) => { + return Promise.resolve({ + message: 'Verification email sent successfully', + }); + }), + sendEmailVerificationToken: jest.fn().mockImplementation((req, userId) => { + return Promise.resolve({ + emailVerificationCode: '123456', + emailVerificationUrl: 'http://localhost/verify/123456', + }); + }), + findByUsername: jest.fn(), + validateUser: jest.fn(), + }; + // mock for the static methods in the user schema const mockUserModel = { - // Static methods - signUp: jest.fn(), - verifyEmail: jest.fn(), - forgetPassword: jest.fn(), - generateUserHandle: jest.fn(), - sendEmailVerificationToken: jest.fn(), + verifyEmail: jest + .fn() + .mockImplementation((userId: string, token: string) => { + if (token === '123456') { + return Promise.resolve({ + status: 200, + message: 'Email Verification Successful', + }); + } + return Promise.reject( + new BadRequestException('Invalid email verification token supplied'), + ); + }), - // Mongoose model methods - findById: jest.fn(), + signUp: jest + .fn() + .mockImplementation((req: any, createUserDto: any, sso = false) => { + return Promise.resolve({ + ...mockUser, + ...createUserDto, + emailVerificationCode: '123456', + emailVerificationUrl: 'http://localhost/verify/123456', + }); + }), + + forgetPassword: jest.fn().mockImplementation((email: string) => { + if (email === 'john@example.com') { + return Promise.resolve({ + email: 'john@example.com', + firstName: 'John', + lastName: 'Doe', + }); + } + return Promise.reject( + new BadRequestException('Email supplied cannot be found'), + ); + }), + + sendEmailVerificationToken: jest.fn().mockImplementation((req, userId) => { + return Promise.resolve({ + emailVerificationCode: '123456', + emailVerificationUrl: 'http://localhost/verify/123456', + }); + }), + + // Base mongoose model methods + generateUserHandle: jest.fn(), findOne: jest.fn(), + findById: jest.fn(), create: jest.fn(), - updateOne: jest.fn(), - findOneAndUpdate: jest.fn(), - find: jest.fn(), - }; + } as unknown as UserModel; // bypass typescript type checking || i know... but it works + // ensure that the jwt always returns the same token const mockJwtService = { sign: jest.fn().mockReturnValue('mock_token'), }; - const mockAuthService = { - login: jest.fn(), - resendVerificationLink: jest.fn(), - sendEmailVerificationToken: jest.fn(), - generateToken: jest.fn().mockReturnValue({ access_token: 'mock_token' }), - }; - beforeEach(async () => { - jest.clearAllMocks(); + jest.clearAllMocks(); // clear all mock implementaions const module: TestingModule = await Test.createTestingModule({ - imports: [DBModule], controllers: [AuthController], providers: [ { @@ -70,7 +148,7 @@ describe('AuthController', () => { useValue: mockAuthService, }, { - provide: getModelToken(User.name), + provide: User.name, useValue: mockUserModel, }, { @@ -81,227 +159,232 @@ describe('AuthController', () => { }).compile(); controller = module.get(AuthController); - authService = module.get(AuthService); - userModel = module.get>(getModelToken(User.name)); - - // Set up specific mock implementations - mockUserModel.signUp.mockImplementation( - async (req, createUserDto, sso = false) => { - const userHandle = await mockUserModel.generateUserHandle( - createUserDto.email, - ); - const user = { - ...mockUser, - ...createUserDto, - firstName: createUserDto.firstName.trim(), - lastName: createUserDto.lastName.trim(), - email: createUserDto.email.trim().toLowerCase(), - password: 'hashedPassword', - role: [UserRole.USER], - userHandle, - }; - - if (user.email === 'existing@example.com') { - throw new BadRequestException('User already exists.'); - } - - const verificationDetails = sso - ? {} - : { - emailVerificationCode: '123456', - emailVerificationUrl: 'http://example.com/verify', - }; + userModel = module.get(User.name); + authService = module.get(AuthService); + jwtService = module.get(JwtService); + }); - return { ...verificationDetails, ...user }; - }, - ); + // check if controller is created + it('should be defined', () => { + expect(controller).toBeDefined(); + }); - mockUserModel.verifyEmail.mockImplementation(async (userId, token) => { - if (!userId || !token) { - throw new BadRequestException('Invalid userId or token'); - } - return { status: 200, message: 'Email Verification Successful' }; + // test for registration + describe('register', () => { + // basic user registration request + interface CreateUserDto { + firstName: string; + lastName: string; + email: string; + password: string; + joinMethod: RegistrationMethod; + } + + const createUserDto: CreateUserDto = { + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + password: 'Password123!', + joinMethod: RegistrationMethod.SIGN_UP, + }; + + it('should register a new user', async () => { + const result = await controller.register(mockReq, createUserDto); + + expect(result).toHaveProperty('email', createUserDto.email); + expect(userModel.signUp).toHaveBeenCalledWith(mockReq, createUserDto); }); - mockUserModel.generateUserHandle.mockImplementation(async (email) => { - const username = email.substring(0, email.indexOf('@')).toLowerCase(); - return `${username}123`; - }); + it('should throw an error if user already exists', async () => { + userModel.signUp.mockRejectedValueOnce( + new BadRequestException('User already exists'), + ); - mockUserModel.sendEmailVerificationToken.mockImplementation( - async (req, userId) => { - return { - emailVerificationCode: '123456', - emailVerificationUrl: 'http://example.com/verify', - }; - }, - ); - - mockUserModel.findOne.mockImplementation(async ({ email }) => { - if (email === 'existing@example.com') { - return { - _id: new Types.ObjectId(), - ...mockUser, - }; - } - return null; + await expect(controller.register(mockReq, createUserDto)).rejects.toThrow( + BadRequestException, + ); }); - - mockUserModel.findById.mockImplementation(async (id) => { - return { - _id: id, - ...mockUser, + it('should throw an error if registration data is invalid', async () => { + const invalidUserDto = { + ...createUserDto, + email: 'invalid-email', // Invalid email format }; + + userModel.signUp.mockRejectedValueOnce( + new BadRequestException('Invalid email format'), + ); + + await expect( + controller.register(mockReq, invalidUserDto), + ).rejects.toThrow(BadRequestException); }); }); - // describe('register', () => { - // it('should register a new user with valid password', async () => { - // const createUserDto: CreateUserDto = { - // email: 'test@example.com', - // password: 'Test123!@#', - // firstName: 'John', - // lastName: 'Doe', - // joinMethod: RegistrationMethod.SIGN_UP, - // }; - // const req = { - // headers: {}, - // query: {}, - // }; - - // const expectedResponse = { - // ...mockUser, - // ...createUserDto, - // password: 'hashedPassword', - // verificationToken: 'mocked-verification-token', - // }; - - // const result = await controller.register(req, createUserDto); - - // expect(mockUserModel.signUp).toHaveBeenCalledWith( - // req, - // createUserDto, - // false, - // ); - // expect(result).toMatchObject(expectedResponse); - // }, 15000); - // }); - + // test for login describe('login', () => { - it('should login a user and return access token', async () => { - const userLoginDto: UserLoginDto = { - email: 'test@example.com', - password: 'Test123!@#', + const loginDto = { + email: 'john@example.com', + password: 'Password123!', + }; + + it('should login successfully', async () => { + const mockReqWithUser = { + ...mockReq, + user: mockUser, }; - const req = { user: mockUser }; - const expectedResponse = { - access_token: 'mock_token', - ...mockUser, - }; - - mockAuthService.login.mockResolvedValue(expectedResponse); - const result = await Promise.resolve(controller.login(req, userLoginDto)); + const result = await controller.login(mockReqWithUser, loginDto); - expect(mockAuthService.login).toHaveBeenCalledWith(req); - expect(result).toEqual(expectedResponse); - }, 15000); - }); + expect(result).toHaveProperty('access_token'); + expect(result).toHaveProperty('email', mockUser.email); + expect(authService.login).toHaveBeenCalledWith(mockReqWithUser); + }); - describe('verifyEmail', () => { - // it('should verify user email', async () => { - // const userId = new Types.ObjectId().toString(); - // const token = 'verification_token'; + it('should include user details in response', async () => { + const mockReqWithUser = { + ...mockReq, + user: mockUser, + }; - // const expectedResponse = { - // verified: true, - // message: 'Email verified successfully', - // }; + const result = await controller.login(mockReqWithUser, loginDto); - // const result = await controller.verifyEmail(userId, token); + expect(result).toMatchObject({ + access_token: expect.any(String), + email: mockUser.email, + firstName: mockUser.firstName, + lastName: mockUser.lastName, + }); + }); + }); - // expect(mockUserModel.verifyEmail).toHaveBeenCalledWith(userId, token); - // expect(result).toEqual(expectedResponse); - // }, 15000); + describe('verifyEmail', () => { + const userId = '507f1f77bcf86cd799439011'; + const validToken = '123456'; + const invalidToken = 'invalid-token'; + + it('should verify email with valid token', async () => { + const result = await controller.verifyEmail(userId, validToken); + + expect(result).toEqual({ + status: 200, + message: 'Email Verification Successful', + }); + expect(userModel.verifyEmail).toHaveBeenCalledWith(userId, validToken); + }); - it('should throw BadRequestException for invalid verification attempt', async () => { - const userId = ''; // Invalid userId - const token = 'verification_token'; + it('should throw BadRequestException for invalid token', async () => { + await expect( + controller.verifyEmail(userId, invalidToken), + ).rejects.toThrow(BadRequestException); + expect(userModel.verifyEmail).toHaveBeenCalledWith(userId, invalidToken); + }); - await expect(controller.verifyEmail(userId, token)).rejects.toThrow( - BadRequestException, + it('should handle non-existent user ID', async () => { + const nonExistentUserId = 'non-existent-id'; + userModel.verifyEmail.mockRejectedValueOnce( + new BadRequestException('User not found'), ); - }, 15000); + + await expect( + controller.verifyEmail(nonExistentUserId, validToken), + ).rejects.toThrow(BadRequestException); + }); }); describe('resendVerification', () => { - it('should resend verification email', async () => { - const email = 'test@example.com'; - const req = { headers: {} }; - const expectedResponse = { message: 'Verification email sent' }; + const validEmail = 'john@example.com'; + const invalidEmail = 'nonexistent@example.com'; - mockAuthService.resendVerificationLink.mockResolvedValue( - expectedResponse, - ); + it('should resend verification email successfully', async () => { + const expectedResponse = { + message: 'Verification email sent successfully', + }; - const result = await Promise.resolve( - controller.resendVerification(req, email), + const result = await controller.resendVerification(mockReq, validEmail); + + expect(result).toEqual(expectedResponse); + expect(authService.resendVerificationLink).toHaveBeenCalledWith( + mockReq, + validEmail, ); + }); - expect(mockAuthService.resendVerificationLink).toHaveBeenCalledWith( - req, - email, + it('should handle non-existent email', async () => { + authService.resendVerificationLink.mockRejectedValueOnce( + new BadRequestException( + `Account with email ${invalidEmail} does not exist`, + ), ); - expect(result).toEqual(expectedResponse); - }, 15000); + + await expect( + controller.resendVerification(mockReq, invalidEmail), + ).rejects.toThrow(BadRequestException); + }); }); describe('sendEmailVerificationToken', () => { - it('should send email verification token with valid userId', async () => { - const userId = new Types.ObjectId().toString(); - const req = { headers: {} }; - const expectedResponse = { message: 'Verification token sent' }; + const userId = '507f1f77bcf86cd799439011'; - mockAuthService.sendEmailVerificationToken.mockResolvedValue( - expectedResponse, - ); + it('should send email verification token successfully', async () => { + const expectedResponse = { + emailVerificationCode: '123456', + emailVerificationUrl: 'http://localhost/verify/123456', + }; - const result = await Promise.resolve( - controller.sendEmailVerificationToken(req, userId), + const result = await controller.sendEmailVerificationToken( + mockReq, + userId, ); - expect(mockAuthService.sendEmailVerificationToken).toHaveBeenCalledWith( - req, + expect(result).toEqual(expectedResponse); + expect(authService.sendEmailVerificationToken).toHaveBeenCalledWith( + mockReq, userId, ); - expect(result).toEqual(expectedResponse); - }, 15000); - - it('should throw error for invalid userId format', async () => { - const userId = 'invalid-id'; - const req = { headers: {} }; + }); - mockAuthService.sendEmailVerificationToken.mockRejectedValue( - new Error('Invalid ObjectId'), + it('should handle invalid user ID', async () => { + const invalidUserId = 'invalid-user-id'; + authService.sendEmailVerificationToken.mockRejectedValueOnce( + new BadRequestException('User not found'), ); await expect( - controller.sendEmailVerificationToken(req, userId), - ).rejects.toThrow(); - }, 15000); + controller.sendEmailVerificationToken(mockReq, invalidUserId), + ).rejects.toThrow(BadRequestException); + }); }); - // describe('forgetPassword', () => { - // it('should handle forget password request', async () => { - // const email = 'test@example.com'; - // const expectedResponse = { message: 'Reset password email sent' }; + describe('forgetPassword', () => { + const validEmail = 'john@example.com'; + const invalidEmail = 'nonexistent@example.com'; + + it('should process forget password request for valid email', async () => { + const result = await controller.forgetPassword(validEmail); - // mockUserModel.forgetPassword.mockResolvedValue(expectedResponse); + expect(result).toMatchObject({ + email: validEmail, + firstName: 'John', + lastName: 'Doe', + }); + expect(userModel.forgetPassword).toHaveBeenCalledWith(validEmail); + }); - // const result = await Promise.resolve(controller.forgetPassword(email)); + it('should throw BadRequestException for non-existent email', async () => { + await expect(controller.forgetPassword(invalidEmail)).rejects.toThrow( + BadRequestException, + ); + expect(userModel.forgetPassword).toHaveBeenCalledWith(invalidEmail); + }); - // expect(mockUserModel.forgetPassword).toHaveBeenCalledWith(email); - // expect(result).toEqual(expectedResponse); - // }, 15000); - // }); + it('should handle error during password reset process', async () => { + userModel.forgetPassword.mockRejectedValueOnce( + new Error('Failed to process password reset'), + ); + + await expect(controller.forgetPassword(validEmail)).rejects.toThrow( + Error, + ); + }); + }); }); From 23a4a10c4c0cf5d7d0a2899c9f366eaf6d931e76 Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:25:31 +0100 Subject: [PATCH 8/9] fix change password test --- src/users/users.controller.spec.ts | 151 +++++++++++++++-------------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 8709eff..b485980 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -17,6 +17,51 @@ import { IPageable } from 'src/shared/utils'; type UserDocument = Document & User & { _id: Types.ObjectId }; +/* +'*generates a mock user object that can be used across multiple tests + *choose which part you want to override using createMock({parameter:new_value}) +*/ + +const createUserMock = ( + overrides: Partial = {}, +): UserDocument => { + return { + _id: new Types.ObjectId(), + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + password: 'hashedpassword123', + profileSummary: 'Experienced software engineer', + jobTitle: 'Senior Developer', + currentCompany: 'TechCorp', + photo: 'profilephoto.jpg', + age: 30, + phone: '123-456-7890', + userHandle: 'johnDoe123', + gender: 'male', + location: { + type: 'Point', + coordinates: [40.7128, -74.006], + }, + deviceId: 'device12345', + deviceToken: 'deviceToken12345', + role: [UserRole.USER], + leadPosition: 'Tech Lead', + applicationStatus: ApplicationStatus.PENDING, + nextApplicationTime: new Date(), + joinMethod: RegistrationMethod.SIGN_UP, + status: UserStatus.ACTIVE, + emailVerification: true, + pendingInvitation: false, + socials: { + phoneNumber: '24242424', + email: 'balbal', + }, + nextVerificationRequestDate: new Date(), + ...overrides, // Overrides allow customization of the mock + } as UserDocument; +}; + describe('UsersAdminController', () => { let controller: UsersController; let adminController: UsersAdminsController; @@ -240,78 +285,36 @@ describe('UsersAdminController', () => { expect(usersService.findAll).toHaveBeenCalledWith(requestMock); }); }); - // describe('change Password', () => { - // const mockUser = createUserMock({_id: new Types.ObjectId(),}); - // const mockRequest = { - // user: { - // _id: mockUser._id, - // email: mockUser.email, - // role: [UserRole.ADMIN], - // }, - // }; - // const UserChangePasswordDto = { - // oldPassword: 'oldPassword@123' - // newPassword: 'newPassword@123', - // confirmPassword: 'newPassword@123', - // }; - - // it('should change the user password', async () => { - // const updatedUser = { ...mockUser, password: 'newHashedPassword' }; - // jest.spyOn(usersService, 'changePassword').mockResolvedValue(updatedUser); - // const result = await adminController.changePassword( - // mockRequest, - // mockUser._id.toString(), - // UserChangePasswordDto, - // ); - // expect(result).toEqual(updatedUser); - // expect(usersService.changePassword).toHaveBeenLastCalledWith( - // mockRequest, - // mockUser._id.toString(), - // UserChangePasswordDto, - // true, - // ); - // }); - // }); + describe('change Password', () => { + const mockUser = createUserMock({ _id: new Types.ObjectId() }); + const mockRequest = { + user: { + _id: mockUser._id, + email: mockUser.email, + role: [UserRole.ADMIN], + }, + }; + const UserChangePasswordDto = { + oldPassword: 'oldPassword@123', + newPassword: 'newPassword@123', + confirmPassword: 'newPassword@123', + }; + + it('should change the user password', async () => { + const updatedUser = { ...mockUser, password: 'newHashedPassword' }; + jest.spyOn(usersService, 'changePassword').mockResolvedValue(updatedUser); + const result = await adminController.changePassword( + mockRequest, + mockUser._id.toString(), + UserChangePasswordDto, + ); + expect(result).toEqual(updatedUser); + expect(usersService.changePassword).toHaveBeenLastCalledWith( + mockRequest, + mockUser._id.toString(), + UserChangePasswordDto, + true, + ); + }); + }); }); - -/* -'*generates a mock user object that can be used across multiple tests - *choose which part you want to override using createMock({parameter:new_value}) -*/ -const createUserMock = (overrides: Partial = {}): User => { - return { - // _id: new Types.ObjectId(), - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - password: 'hashedpassword123', - profileSummary: 'Experienced software engineer', - jobTitle: 'Senior Developer', - currentCompany: 'TechCorp', - photo: 'profilephoto.jpg', - age: 30, - phone: '123-456-7890', - userHandle: 'johnDoe123', - gender: 'male', - location: { - type: 'Point', - coordinates: [40.7128, -74.006], - }, - deviceId: 'device12345', - deviceToken: 'deviceToken12345', - role: [UserRole.USER], - leadPosition: 'Tech Lead', - applicationStatus: ApplicationStatus.PENDING, - nextApplicationTime: new Date(), - joinMethod: RegistrationMethod.SIGN_UP, - status: UserStatus.ACTIVE, - emailVerification: true, - pendingInvitation: false, - socials: { - phoneNumber: '24242424', - email: 'balbal', - }, - nextVerificationRequestDate: new Date(), - ...overrides, // Overrides will allow you to customize the mock as needed - }; -}; From 8e053fa3910131aa19be9b5e783ed4c8dd007b32 Mon Sep 17 00:00:00 2001 From: stefano <30480337+snebo@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:47:25 +0100 Subject: [PATCH 9/9] fix: removed redudant mock model db file renamed controller for better representation slight prettier adjustments --- src/shared/auth/auth.controller.spec.ts | 43 +++++++++++++------------ src/shared/auth/db.module.mock.ts | 16 --------- 2 files changed, 23 insertions(+), 36 deletions(-) delete mode 100644 src/shared/auth/db.module.mock.ts diff --git a/src/shared/auth/auth.controller.spec.ts b/src/shared/auth/auth.controller.spec.ts index 6b80b63..1f25675 100644 --- a/src/shared/auth/auth.controller.spec.ts +++ b/src/shared/auth/auth.controller.spec.ts @@ -29,7 +29,7 @@ interface MockAuthService { // Main test container describe('AuthController', () => { - let controller: AuthController; + let authController: AuthController; let userModel: UserModel; let authService: MockAuthService; let jwtService: JwtService; @@ -158,7 +158,7 @@ describe('AuthController', () => { ], }).compile(); - controller = module.get(AuthController); + authController = module.get(AuthController); userModel = module.get(User.name); authService = module.get(AuthService); jwtService = module.get(JwtService); @@ -166,7 +166,7 @@ describe('AuthController', () => { // check if controller is created it('should be defined', () => { - expect(controller).toBeDefined(); + expect(authController).toBeDefined(); }); // test for registration @@ -189,7 +189,7 @@ describe('AuthController', () => { }; it('should register a new user', async () => { - const result = await controller.register(mockReq, createUserDto); + const result = await authController.register(mockReq, createUserDto); expect(result).toHaveProperty('email', createUserDto.email); expect(userModel.signUp).toHaveBeenCalledWith(mockReq, createUserDto); @@ -200,9 +200,9 @@ describe('AuthController', () => { new BadRequestException('User already exists'), ); - await expect(controller.register(mockReq, createUserDto)).rejects.toThrow( - BadRequestException, - ); + await expect( + authController.register(mockReq, createUserDto), + ).rejects.toThrow(BadRequestException); }); it('should throw an error if registration data is invalid', async () => { const invalidUserDto = { @@ -215,7 +215,7 @@ describe('AuthController', () => { ); await expect( - controller.register(mockReq, invalidUserDto), + authController.register(mockReq, invalidUserDto), ).rejects.toThrow(BadRequestException); }); }); @@ -233,7 +233,7 @@ describe('AuthController', () => { user: mockUser, }; - const result = await controller.login(mockReqWithUser, loginDto); + const result = await authController.login(mockReqWithUser, loginDto); expect(result).toHaveProperty('access_token'); expect(result).toHaveProperty('email', mockUser.email); @@ -246,7 +246,7 @@ describe('AuthController', () => { user: mockUser, }; - const result = await controller.login(mockReqWithUser, loginDto); + const result = await authController.login(mockReqWithUser, loginDto); expect(result).toMatchObject({ access_token: expect.any(String), @@ -263,7 +263,7 @@ describe('AuthController', () => { const invalidToken = 'invalid-token'; it('should verify email with valid token', async () => { - const result = await controller.verifyEmail(userId, validToken); + const result = await authController.verifyEmail(userId, validToken); expect(result).toEqual({ status: 200, @@ -274,7 +274,7 @@ describe('AuthController', () => { it('should throw BadRequestException for invalid token', async () => { await expect( - controller.verifyEmail(userId, invalidToken), + authController.verifyEmail(userId, invalidToken), ).rejects.toThrow(BadRequestException); expect(userModel.verifyEmail).toHaveBeenCalledWith(userId, invalidToken); }); @@ -286,7 +286,7 @@ describe('AuthController', () => { ); await expect( - controller.verifyEmail(nonExistentUserId, validToken), + authController.verifyEmail(nonExistentUserId, validToken), ).rejects.toThrow(BadRequestException); }); }); @@ -300,7 +300,10 @@ describe('AuthController', () => { message: 'Verification email sent successfully', }; - const result = await controller.resendVerification(mockReq, validEmail); + const result = await authController.resendVerification( + mockReq, + validEmail, + ); expect(result).toEqual(expectedResponse); expect(authService.resendVerificationLink).toHaveBeenCalledWith( @@ -317,7 +320,7 @@ describe('AuthController', () => { ); await expect( - controller.resendVerification(mockReq, invalidEmail), + authController.resendVerification(mockReq, invalidEmail), ).rejects.toThrow(BadRequestException); }); }); @@ -331,7 +334,7 @@ describe('AuthController', () => { emailVerificationUrl: 'http://localhost/verify/123456', }; - const result = await controller.sendEmailVerificationToken( + const result = await authController.sendEmailVerificationToken( mockReq, userId, ); @@ -350,7 +353,7 @@ describe('AuthController', () => { ); await expect( - controller.sendEmailVerificationToken(mockReq, invalidUserId), + authController.sendEmailVerificationToken(mockReq, invalidUserId), ).rejects.toThrow(BadRequestException); }); }); @@ -360,7 +363,7 @@ describe('AuthController', () => { const invalidEmail = 'nonexistent@example.com'; it('should process forget password request for valid email', async () => { - const result = await controller.forgetPassword(validEmail); + const result = await authController.forgetPassword(validEmail); expect(result).toMatchObject({ email: validEmail, @@ -371,7 +374,7 @@ describe('AuthController', () => { }); it('should throw BadRequestException for non-existent email', async () => { - await expect(controller.forgetPassword(invalidEmail)).rejects.toThrow( + await expect(authController.forgetPassword(invalidEmail)).rejects.toThrow( BadRequestException, ); expect(userModel.forgetPassword).toHaveBeenCalledWith(invalidEmail); @@ -382,7 +385,7 @@ describe('AuthController', () => { new Error('Failed to process password reset'), ); - await expect(controller.forgetPassword(validEmail)).rejects.toThrow( + await expect(authController.forgetPassword(validEmail)).rejects.toThrow( Error, ); }); diff --git a/src/shared/auth/db.module.mock.ts b/src/shared/auth/db.module.mock.ts deleted file mode 100644 index 8d86f8e..0000000 --- a/src/shared/auth/db.module.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -// db.module.mock.ts -import { Module } from '@nestjs/common'; -import { getModelToken } from '@nestjs/mongoose'; -import { User } from '../schema'; // Adjust the path as necessary -import { mockUserModel } from './auth.controller.spec'; - -@Module({ - providers: [ - { - provide: getModelToken(User.name), - useValue: mockUserModel, // Use your mocked user model here - }, - ], - exports: [getModelToken(User.name)], // Export the mocked model if needed -}) -export class MockDBModule {}