From 5dc373fc4118fa0b30c8c282aeeec1d2ea653586 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Mon, 7 Apr 2025 17:15:22 +0300 Subject: [PATCH 01/19] chore: update dependencies for account module testing --- package-lock.json | 63 +++++++++++++++++++++++++++++++---------------- package.json | 8 +++--- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index dea1c069..3e88e523 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,11 +49,11 @@ "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.4.15", "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/faker": "^6.6.8", - "@types/jest": "^29.5.2", + "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.7", "@types/node": "^20.3.1", "@types/nodemailer": "^6.4.17", @@ -64,11 +64,11 @@ "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", - "jest": "^29.5.0", + "jest": "^29.7.0", "prettier": "^3.4.2", "source-map-support": "^0.5.21", "supertest": "^6.3.3", - "ts-jest": "^29.1.0", + "ts-jest": "^29.3.1", "ts-loader": "^9.4.3", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", @@ -2401,13 +2401,13 @@ "license": "MIT" }, "node_modules/@nestjs/testing": { - "version": "10.4.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.8.tgz", - "integrity": "sha512-VusUnVgfY6KUc0gKU7ER9QQ2QyCoO770wcAfgLhtqydezt/w07FvqT6uOtb/Tf4SMfUbxx6AJwte6UUmkewbnQ==", + "version": "10.4.15", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.15.tgz", + "integrity": "sha512-eGlWESkACMKti+iZk1hs6FUY/UqObmMaa8HAN9JLnaYkoLf1Jeh+EuHlGnfqo/Rq77oznNLIyaA3PFjrFDlNUg==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "2.7.0" + "tslib": "2.8.1" }, "funding": { "type": "opencollective", @@ -2428,6 +2428,13 @@ } } }, + "node_modules/@nestjs/testing/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/@nestjs/typeorm": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", @@ -4681,9 +4688,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, "license": "MIT" }, @@ -11658,9 +11665,9 @@ } }, "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, "license": "MIT", "engines": { @@ -12051,9 +12058,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12996,9 +13003,9 @@ } }, "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.1.tgz", + "integrity": "sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13009,7 +13016,8 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.6.3", + "semver": "^7.7.1", + "type-fest": "^4.38.0", "yargs-parser": "^21.1.1" }, "bin": { @@ -13044,6 +13052,19 @@ } } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-loader": { "version": "9.5.1", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", diff --git a/package.json b/package.json index 0872a862..8107511a 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,11 @@ "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.4.15", "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/faker": "^6.6.8", - "@types/jest": "^29.5.2", + "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.7", "@types/node": "^20.3.1", "@types/nodemailer": "^6.4.17", @@ -77,11 +77,11 @@ "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", - "jest": "^29.5.0", + "jest": "^29.7.0", "prettier": "^3.4.2", "source-map-support": "^0.5.21", "supertest": "^6.3.3", - "ts-jest": "^29.1.0", + "ts-jest": "^29.3.1", "ts-loader": "^9.4.3", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", From 61b2f2e1984598b496448f36f4988d2c0e08b7d2 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Mon, 7 Apr 2025 17:15:37 +0300 Subject: [PATCH 02/19] chore: update tsconfig for test configuration --- tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 95f5641c..9f60f436 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,9 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "src/*": ["./src/*"] + } } } From 383d239b9d6ca65433ce42728b9433983c7e47ee Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Mon, 7 Apr 2025 17:16:50 +0300 Subject: [PATCH 03/19] feat(account): updated the relative import inaccount.service --- src/modules/admin/account/account.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/admin/account/account.service.ts b/src/modules/admin/account/account.service.ts index a46dafe1..258470aa 100644 --- a/src/modules/admin/account/account.service.ts +++ b/src/modules/admin/account/account.service.ts @@ -2,10 +2,10 @@ import { Inject, Injectable } from '@nestjs/common'; import { CreateAccountDto } from './dto/create-account.dto'; import { UpdateAccountDto } from './dto/update-account.dto'; import { REQUEST } from '@nestjs/core'; -import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { PrismaService } from '../../prisma/prisma.service'; import { RoleType } from '@prisma/client'; -import { PaginationDto } from 'src/common/dto/pagination.dto'; -import { paginate } from 'src/common/helpers/pagination'; +import { PaginationDto } from '../../../common/dto/pagination.dto'; +import { paginate } from '../../../common/helpers/pagination'; @Injectable() export class AccountService { From db54331c647f160cfe55692af68f2b20a4c5e2ad Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Mon, 7 Apr 2025 17:17:25 +0300 Subject: [PATCH 04/19] test(account): add comprehensive test suite for account service --- .../admin/account/account.service.spec.ts | 183 +++++++++++++++++- 1 file changed, 179 insertions(+), 4 deletions(-) diff --git a/src/modules/admin/account/account.service.spec.ts b/src/modules/admin/account/account.service.spec.ts index ef75dcbc..895cd9f8 100644 --- a/src/modules/admin/account/account.service.spec.ts +++ b/src/modules/admin/account/account.service.spec.ts @@ -1,18 +1,193 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { AccountService } from './account.service'; +import { Test, TestingModule } from '@nestjs/testing'; +import { REQUEST } from '@nestjs/core'; +import { RoleType } from '@prisma/client'; +import { PrismaService } from '../../prisma/prisma.service'; +import { PaginationDto } from '../../../common/dto/pagination.dto'; +import * as paginationHelper from '../../../common/helpers/pagination'; describe('AccountService', () => { let service: AccountService; + const mockRequest = { + user: { id: 'test-user-id' }, + }; + + const mockPrismaService = { + $transaction: jest.fn(), + account: { + findFirst: jest.fn(), + findUnique: jest.fn(), + create: jest.fn(), + update: jest.fn(), + }, + role: { + findFirst: jest.fn(), + }, + accountUser: { + updateMany: jest.fn(), + }, + }; + beforeEach(async () => { + jest.clearAllMocks(); + + // Add this mock implementation + mockPrismaService.$transaction.mockImplementation(async (arg) => { + if (typeof arg === 'function') { + return arg(mockPrismaService); + } + if (Array.isArray(arg)) { + return Promise.all(arg); + } + throw new Error('Unsupported argument to $transaction'); + }); + const module: TestingModule = await Test.createTestingModule({ - providers: [AccountService], + providers: [ + AccountService, + { provide: PrismaService, useValue: mockPrismaService }, + { provide: REQUEST, useValue: mockRequest }, + ], }).compile(); service = module.get(AccountService); }); - it('should be defined', () => { - expect(service).toBeDefined(); + describe('create()', () => { + it('should create an account with default owner role', async () => { + const createDto = { name: 'Test Account', domain: null }; + const role = { id: 'role-id', type: RoleType.OWNER, isDefault: true }; + const createdAccount = { id: 'acc-id', name: 'Test Account' }; + + mockPrismaService.$transaction.mockImplementation(async (arg) => { + if (typeof arg === 'function') { + return arg(mockPrismaService); + } + if (Array.isArray(arg)) { + return Promise.all(arg); + } + throw new Error('Unsupported argument to $transaction'); + }); + + mockPrismaService.role.findFirst.mockResolvedValue(role); + mockPrismaService.account.create.mockResolvedValue(createdAccount); + + const result = await service.create(createDto); + expect(result).toEqual(createdAccount); + }); + + it('should throw error if default owner role is not found', async () => { + mockPrismaService.role.findFirst.mockResolvedValue(null); + + await expect(service.create({ name: 'X', domain: null })).rejects.toThrow( + 'Default owner role not found', + ); + }); + }); + + describe('findAll()', () => { + it('should return paginated accounts for the user', async () => { + const mockPagination: PaginationDto = { page: 1, limit: 10 }; + const mockPaginateResult = { + data: [], + meta: { + total: 0, + page: 1, + limit: 10, + totalPages: 0, + }, + }; + + jest + .spyOn(paginationHelper, 'paginate') + .mockResolvedValue(mockPaginateResult); + + const result = await service.findAll(mockPagination); + expect(result.meta).toHaveProperty('page', 1); + expect(result.meta).toHaveProperty('limit', 10); + }); + }); + + describe('findOne()', () => { + it('should return an account if it exists and belongs to the user', async () => { + const mockAccount = { id: 'acc-id', name: 'Test Account' }; + + mockPrismaService.account.findFirst.mockResolvedValue(mockAccount); + const result = await service.findOne('acc-id'); + + expect(result).toEqual(mockAccount); + }); + + it('should throw error if account is not found', async () => { + mockPrismaService.account.findFirst.mockResolvedValue(null); + + await expect(service.findOne('invalid-id')).rejects.toThrow( + 'Account not found', + ); + }); + }); + + describe('update()', () => { + it('should update and return the account', async () => { + const updateDto = { name: 'Updated Name' }; + const updatedAccount = { id: 'acc-id', name: 'Updated Name' }; + + mockPrismaService.account.findFirst.mockResolvedValue(updatedAccount); + mockPrismaService.account.update.mockResolvedValue(updatedAccount); + + const result = await service.update('acc-id', updateDto); + + expect(result).toEqual(updatedAccount); + }); + + it('should throw error if account is not found', async () => { + mockPrismaService.account.findFirst.mockResolvedValue(null); + + await expect( + service.update('invalid-id', { name: 'Updated Name' }), + ).rejects.toThrow('Account not found'); + }); + }); + + describe('remove()', () => { + it('should delete the account and return a success message', async () => { + const accountId = 'acc-id'; + const account = { id: accountId, deletedAt: null }; + + mockPrismaService.account.findUnique.mockResolvedValue(account); + mockPrismaService.account.update.mockResolvedValue({ + ...account, + deletedAt: new Date(), + }); + mockPrismaService.accountUser.updateMany.mockResolvedValue({ count: 1 }); + + const result = await service.remove(accountId); + + expect(result).toEqual({ message: 'Account deleted successfully' }); + expect(mockPrismaService.$transaction).toHaveBeenCalledWith([ + expect.any(Object), // Account update + expect.any(Object), // AccountUser update + ]); + }); + }); + + it('should throw error if account is not found', async () => { + mockPrismaService.account.findUnique.mockResolvedValue(null); + + await expect(service.remove('invalid-id')).rejects.toThrow( + 'Account not found', + ); + }); + + it('should return a message if the account is already deleted', async () => { + const accountId = 'acc-id'; + const account = { id: accountId, deletedAt: new Date() }; + + mockPrismaService.account.findUnique.mockResolvedValue(account); + + const result = await service.remove(accountId); + + expect(result).toEqual({ message: 'Account is already deleted' }); }); }); From 5fc55500e30a14f5d7ebb0cc602cd7fb746269f3 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Fri, 11 Apr 2025 18:46:25 +0300 Subject: [PATCH 05/19] test(admin/account): update test cases for account.controller --- .../admin/account/account.controller.spec.ts | 118 +++++++++++++++++- 1 file changed, 114 insertions(+), 4 deletions(-) diff --git a/src/modules/admin/account/account.controller.spec.ts b/src/modules/admin/account/account.controller.spec.ts index 47b7b170..3163b592 100644 --- a/src/modules/admin/account/account.controller.spec.ts +++ b/src/modules/admin/account/account.controller.spec.ts @@ -1,20 +1,130 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AccountController } from './account.controller'; import { AccountService } from './account.service'; +import { CreateAccountDto } from './dto/create-account.dto'; +import { UpdateAccountDto } from './dto/update-account.dto'; +import { PaginationDto } from '../../../common/dto/pagination.dto'; +import { AuthGuard } from '../../auth/guard/auth/auth.guard'; +import { PrismaService } from '../../../modules/prisma/prisma.service'; describe('AccountController', () => { let controller: AccountController; + let accountService: AccountService; + + const mockAccountService = { + create: jest.fn(), + findAll: jest.fn(), + findOne: jest.fn(), + update: jest.fn(), + remove: jest.fn(), + }; + + const mockPrismaService = { + account: { + findFirst: jest.fn(), + }, + }; + + const mockRequest = { + user: { id: 'test-user-id' }, + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AccountController], - providers: [AccountService], - }).compile(); + providers: [ + { + provide: AccountService, + useValue: mockAccountService, + }, + { + provide: PrismaService, + useValue: mockPrismaService, + }, + { + provide: 'REQUEST', + useValue: mockRequest, + }, + ], + }) + .overrideGuard(AuthGuard) + .useValue({ canActivate: () => true }) + .compile(); controller = module.get(AccountController); + accountService = module.get(AccountService); + jest.clearAllMocks(); + }); + + describe('POST /admin/account', () => { + it('should create an account', async () => { + const createDto: CreateAccountDto = { + name: 'Test Account', + domain: null, + }; + const expectedResult = { id: 'acc-id', name: 'Test Account' }; + + mockAccountService.create.mockResolvedValue(expectedResult); + + const result = await controller.create(createDto); + expect(result).toEqual(expectedResult); + expect(accountService.create).toHaveBeenCalledWith(createDto); + }); + }); + + describe('GET /admin/account', () => { + it('should return paginated accounts', async () => { + const paginationDto: PaginationDto = { page: 1, limit: 10 }; + const expectedResult = { + data: [{ id: 'acc1' }, { id: 'acc2' }], + meta: { total: 2, page: 1, limit: 10 }, + }; + + mockAccountService.findAll.mockResolvedValue(expectedResult); + + const result = await controller.findAll(paginationDto); + expect(result).toEqual(expectedResult); + expect(accountService.findAll).toHaveBeenCalledWith(paginationDto); + }); }); - it('should be defined', () => { - expect(controller).toBeDefined(); + describe('GET /admin/account/:id', () => { + it('should return a single account', async () => { + const accountId = 'acc-id'; + const expectedResult = { id: accountId, name: 'Test Account' }; + + mockAccountService.findOne.mockResolvedValue(expectedResult); + + const result = await controller.findOne(accountId); + expect(result).toEqual(expectedResult); + expect(accountService.findOne).toHaveBeenCalledWith(accountId); + }); + }); + + describe('PATCH /admin/account/:id', () => { + it('should update an account', async () => { + const accountId = 'acc-id'; + const updateDto: UpdateAccountDto = { name: 'Updated Name' }; + const expectedResult = { id: accountId, ...updateDto }; + + mockAccountService.update.mockResolvedValue(expectedResult); + + const result = await controller.update(accountId, updateDto); + expect(result).toEqual(expectedResult); + expect(accountService.update).toHaveBeenCalledWith(accountId, updateDto); + }); + }); + + describe('DELETE /admin/account/:id', () => { + it('should delete an account', async () => { + const accountId = 'acc-id'; + const expectedResult = { message: 'Account deleted successfully' }; + + mockAccountService.remove.mockResolvedValue(expectedResult); + + const result = await controller.remove(accountId); + expect(result).toEqual(expectedResult); + expect(accountService.remove).toHaveBeenCalledWith(accountId); + }); }); }); From 1be713e14974ef49d4948fc7f86ae9b0815f5878 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Fri, 11 Apr 2025 18:47:47 +0300 Subject: [PATCH 06/19] use relative path in the import --- src/modules/admin/account/account.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/admin/account/account.controller.ts b/src/modules/admin/account/account.controller.ts index ac6b5209..ced5009c 100644 --- a/src/modules/admin/account/account.controller.ts +++ b/src/modules/admin/account/account.controller.ts @@ -14,8 +14,8 @@ import { AccountService } from './account.service'; import { CreateAccountDto } from './dto/create-account.dto'; import { UpdateAccountDto } from './dto/update-account.dto'; import { AccountIdPipe } from './pipe/account-id/account-id.pipe'; -import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; -import { PaginationDto } from 'src/common/dto/pagination.dto'; +import { AuthGuard } from '../../auth/guard/auth/auth.guard'; +import { PaginationDto } from '../../../common/dto/pagination.dto'; @Controller('admin/account') @UseGuards(AuthGuard) From ebc8c889b7eb029d68517d5fa3a8484d631e2bae Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Fri, 11 Apr 2025 18:48:13 +0300 Subject: [PATCH 07/19] test(admin/account): add service layer test coverage --- src/modules/admin/account/account.service.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/admin/account/account.service.spec.ts b/src/modules/admin/account/account.service.spec.ts index 895cd9f8..61da0bfb 100644 --- a/src/modules/admin/account/account.service.spec.ts +++ b/src/modules/admin/account/account.service.spec.ts @@ -32,7 +32,6 @@ describe('AccountService', () => { beforeEach(async () => { jest.clearAllMocks(); - // Add this mock implementation mockPrismaService.$transaction.mockImplementation(async (arg) => { if (typeof arg === 'function') { return arg(mockPrismaService); @@ -166,8 +165,8 @@ describe('AccountService', () => { expect(result).toEqual({ message: 'Account deleted successfully' }); expect(mockPrismaService.$transaction).toHaveBeenCalledWith([ - expect.any(Object), // Account update - expect.any(Object), // AccountUser update + expect.any(Object), + expect.any(Object), ]); }); }); From 42cfabb6c02ada9627c1565bb5321c3fe25a4ed1 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Fri, 11 Apr 2025 18:48:35 +0300 Subject: [PATCH 08/19] test(admin/account): add unit tests for AccountIdPipe --- .../pipe/account-id/account-id.pipe.spec.ts | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/modules/admin/account/pipe/account-id/account-id.pipe.spec.ts b/src/modules/admin/account/pipe/account-id/account-id.pipe.spec.ts index 55b7a3f7..75333062 100644 --- a/src/modules/admin/account/pipe/account-id/account-id.pipe.spec.ts +++ b/src/modules/admin/account/pipe/account-id/account-id.pipe.spec.ts @@ -1,10 +1,35 @@ -import { PrismaService } from 'src/modules/prisma/prisma.service'; import { AccountIdPipe } from './account-id.pipe'; describe('AccountIdPipe', () => { - it('should be defined', () => { - const prismaService = {} as PrismaService; - const request = {}; - expect(new AccountIdPipe(prismaService, request)).toBeDefined(); + let pipe: AccountIdPipe; + + const mockPrismaService = { + account: { + findFirst: jest.fn(), + }, + }; + + const mockRequest = { + user: { id: 'test-user-id' }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + pipe = new AccountIdPipe(mockPrismaService as any, mockRequest); + }); + + it('should throw if account not found', async () => { + mockPrismaService.account.findFirst.mockResolvedValue(null); + + await expect(pipe.transform({ accountId: 'invalid-id' })).rejects.toThrow( + 'Account not found!', + ); + }); + + it('should return value if account exists', async () => { + mockPrismaService.account.findFirst.mockResolvedValue({ id: 'acc-id' }); + + const result = await pipe.transform({ accountId: 'acc-id' }); + expect(result).toEqual({ accountId: 'acc-id' }); }); }); From fad650aa664e7ffc53a5046474217e0fd76645d4 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Fri, 11 Apr 2025 18:49:28 +0300 Subject: [PATCH 09/19] use relative path for the import --- src/modules/admin/account/pipe/account-id/account-id.pipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/admin/account/pipe/account-id/account-id.pipe.ts b/src/modules/admin/account/pipe/account-id/account-id.pipe.ts index eb61e78f..f61d64f4 100644 --- a/src/modules/admin/account/pipe/account-id/account-id.pipe.ts +++ b/src/modules/admin/account/pipe/account-id/account-id.pipe.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, PipeTransform } from '@nestjs/common'; import { User } from 'src/modules/admin/user/entities/user.entity'; -import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { PrismaService } from '../../../../../modules/prisma/prisma.service'; @Injectable() export class AccountIdPipe implements PipeTransform { From a97edab627559bd62ccdeed1cbdbc3e01683f1dc Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 15 Apr 2025 10:12:45 +0300 Subject: [PATCH 10/19] test: add initial spec file for AuthController --- src/modules/auth/auth.controller.spec.ts | 133 +++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/modules/auth/auth.controller.spec.ts diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts new file mode 100644 index 00000000..83d62e77 --- /dev/null +++ b/src/modules/auth/auth.controller.spec.ts @@ -0,0 +1,133 @@ +// auth.controller.spec.ts + +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { SignInUserDto } from './dto/sign-in-auth.dto'; +import { SignUpUserDto } from './dto/sign-up-auth.dto'; +import { AuthDto } from './dto/auth.dto'; +import { UserDto } from '../admin/user/dto/user.dto'; +import { Response } from 'express'; + +describe('AuthController', () => { + let controller: AuthController; + let service: AuthService; + + const mockAuthService = { + signIn: jest.fn(), + signUp: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuthController], + providers: [{ provide: AuthService, useValue: mockAuthService }], + }).compile(); + + controller = module.get(AuthController); + service = module.get(AuthService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('signIn', () => { + it('should return AuthDto on successful sign in', async () => { + const signInDto: SignInUserDto = { + email: 'test@example.com', + password: 'securePass123', + }; + + const user = new UserDto({ + id: '1', + name: 'Test User', + email: 'test@example.com', + }); + + const authResult: AuthDto = { + token: 'mock_token', + user, + accounts: [ + { + id: 'acc-1', + name: 'Main Account', + role: { id: 'r1', name: 'OWNER' }, + }, + ], + }; + + mockAuthService.signIn.mockResolvedValue(authResult); + + const result = await controller.signIn(signInDto); + expect(result).toEqual(authResult); + expect(mockAuthService.signIn).toHaveBeenCalledWith(signInDto); + }); + }); + + describe('signUp', () => { + it('should return AuthDto on successful sign up', async () => { + const signUpDto: SignUpUserDto = { + email: 'new@example.com', + password: 'pass123', + name: 'New User', + }; + + const user = new UserDto({ + id: '2', + name: 'New User', + email: 'new@example.com', + }); + + const authResult: AuthDto = { + token: 'mock_signup_token', + user, + accounts: [ + { + id: 'acc-2', + name: 'Second Account', + role: { id: 'r2', name: 'OWNER' }, + }, + ], + }; + + mockAuthService.signUp.mockResolvedValue(authResult); + + const result = await controller.signUp(signUpDto); + expect(result).toEqual(authResult); + expect(mockAuthService.signUp).toHaveBeenCalledWith(signUpDto); + }); + }); + + describe('googleAuthRedirect', () => { + it('should redirect with token for production callback', async () => { + const req: any = { user: 'mock_google_token' }; + const res: Partial = { + redirect: jest.fn(), + }; + + process.env.WEB_CALLBACK_URL = 'https://yourapp.com/callback'; + + await controller.googleAuthRedirect(req, res as Response); + expect(res.redirect).toHaveBeenCalledWith( + `https://yourapp.com/callback?token=mock_google_token`, + ); + }); + }); + + describe('googleLocalAuthRedirect', () => { + it('should redirect with token for local callback', async () => { + const req: any = { user: 'mock_local_token' }; + const res: Partial = { + redirect: jest.fn(), + }; + + process.env.LOCAL_WEB_CALLBACK_URL = 'http://localhost:3000/callback'; + + await controller.googleLocalAuthRedirect(req, res as Response); + expect(res.redirect).toHaveBeenCalledWith( + `http://localhost:3000/callback?token=mock_local_token`, + ); + }); + }); +}); From 14e56504de430dafafc469ffb2e2644720197033 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 15 Apr 2025 11:54:12 +0300 Subject: [PATCH 11/19] correct lint error --- src/modules/auth/auth.controller.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 83d62e77..4ca34a00 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -11,7 +11,6 @@ import { Response } from 'express'; describe('AuthController', () => { let controller: AuthController; - let service: AuthService; const mockAuthService = { signIn: jest.fn(), @@ -25,7 +24,6 @@ describe('AuthController', () => { }).compile(); controller = module.get(AuthController); - service = module.get(AuthService); }); it('should be defined', () => { From c936213de3a443e7c3383ce27c6202b97009ab59 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:39:39 +0300 Subject: [PATCH 12/19] remove relative imports for AccountController --- .../admin/account/account.controller.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/admin/account/account.controller.spec.ts b/src/modules/admin/account/account.controller.spec.ts index 3163b592..e3237835 100644 --- a/src/modules/admin/account/account.controller.spec.ts +++ b/src/modules/admin/account/account.controller.spec.ts @@ -1,11 +1,11 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AccountController } from './account.controller'; -import { AccountService } from './account.service'; -import { CreateAccountDto } from './dto/create-account.dto'; -import { UpdateAccountDto } from './dto/update-account.dto'; -import { PaginationDto } from '../../../common/dto/pagination.dto'; -import { AuthGuard } from '../../auth/guard/auth/auth.guard'; -import { PrismaService } from '../../../modules/prisma/prisma.service'; +import { AccountController } from 'src/modules/admin/account/account.controller'; +import { AccountService } from 'src/modules/admin/account/account.service'; +import { CreateAccountDto } from 'src/modules/admin/account/dto/create-account.dto'; +import { UpdateAccountDto } from 'src/modules/admin/account/dto/update-account.dto'; +import { PaginationDto } from 'src/common/dto/pagination.dto'; +import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; describe('AccountController', () => { let controller: AccountController; From 691dd70b83327052627f22a52bc1a0942c048f4d Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:41:13 +0300 Subject: [PATCH 13/19] remove relative imports --- src/modules/admin/account/account.controller.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/admin/account/account.controller.ts b/src/modules/admin/account/account.controller.ts index ced5009c..8613b7e9 100644 --- a/src/modules/admin/account/account.controller.ts +++ b/src/modules/admin/account/account.controller.ts @@ -10,12 +10,12 @@ import { UsePipes, Query, } from '@nestjs/common'; -import { AccountService } from './account.service'; -import { CreateAccountDto } from './dto/create-account.dto'; -import { UpdateAccountDto } from './dto/update-account.dto'; -import { AccountIdPipe } from './pipe/account-id/account-id.pipe'; -import { AuthGuard } from '../../auth/guard/auth/auth.guard'; -import { PaginationDto } from '../../../common/dto/pagination.dto'; +import { AccountService } from 'src/modules/admin/account/account.service'; +import { CreateAccountDto } from 'src/modules/admin/account/dto/create-account.dto'; +import { UpdateAccountDto } from 'src/modules/admin/account/dto/update-account.dto'; +import { AccountIdPipe } from 'src/modules/admin/account/pipe/account-id/account-id.pipe'; +import { AuthGuard } from 'src/modules/auth/guard/auth/auth.guard'; +import { PaginationDto } from 'src/common/dto/pagination.dto'; @Controller('admin/account') @UseGuards(AuthGuard) From d4b3de5d05d4de88506ed5ea24d513654c5654c9 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:42:07 +0300 Subject: [PATCH 14/19] remove relative imports --- src/modules/admin/account/account.service.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/admin/account/account.service.spec.ts b/src/modules/admin/account/account.service.spec.ts index 61da0bfb..4781bab8 100644 --- a/src/modules/admin/account/account.service.spec.ts +++ b/src/modules/admin/account/account.service.spec.ts @@ -2,9 +2,9 @@ import { AccountService } from './account.service'; import { Test, TestingModule } from '@nestjs/testing'; import { REQUEST } from '@nestjs/core'; import { RoleType } from '@prisma/client'; -import { PrismaService } from '../../prisma/prisma.service'; -import { PaginationDto } from '../../../common/dto/pagination.dto'; -import * as paginationHelper from '../../../common/helpers/pagination'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { PaginationDto } from 'src/common/dto/pagination.dto'; +import * as paginationHelper from 'src/common/helpers/pagination'; describe('AccountService', () => { let service: AccountService; From 5ac29174eed832eacea603cecaa39f2b3766b17c Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:43:19 +0300 Subject: [PATCH 15/19] remove relative imports --- src/modules/admin/account/account.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/admin/account/account.service.ts b/src/modules/admin/account/account.service.ts index 258470aa..e02cf240 100644 --- a/src/modules/admin/account/account.service.ts +++ b/src/modules/admin/account/account.service.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; -import { CreateAccountDto } from './dto/create-account.dto'; -import { UpdateAccountDto } from './dto/update-account.dto'; +import { CreateAccountDto } from 'src/modules/admin/account/dto/create-account.dto'; +import { UpdateAccountDto } from 'src/modules/admin/account/dto/update-account.dto'; import { REQUEST } from '@nestjs/core'; -import { PrismaService } from '../../prisma/prisma.service'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; import { RoleType } from '@prisma/client'; -import { PaginationDto } from '../../../common/dto/pagination.dto'; -import { paginate } from '../../../common/helpers/pagination'; +import { PaginationDto } from 'src/common/dto/pagination.dto'; +import { paginate } from 'src/common/helpers/pagination'; @Injectable() export class AccountService { From f06e43adc6dffdfaeaffaba66a45630f544828c8 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:43:55 +0300 Subject: [PATCH 16/19] remove relative imports --- src/modules/admin/account/pipe/account-id/account-id.pipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/admin/account/pipe/account-id/account-id.pipe.ts b/src/modules/admin/account/pipe/account-id/account-id.pipe.ts index f61d64f4..eb61e78f 100644 --- a/src/modules/admin/account/pipe/account-id/account-id.pipe.ts +++ b/src/modules/admin/account/pipe/account-id/account-id.pipe.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, PipeTransform } from '@nestjs/common'; import { User } from 'src/modules/admin/user/entities/user.entity'; -import { PrismaService } from '../../../../../modules/prisma/prisma.service'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; @Injectable() export class AccountIdPipe implements PipeTransform { From d27ffee8ca1009bad7a599aa0ee9bb581021cf06 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 11:44:12 +0300 Subject: [PATCH 17/19] chore: add standalone Jest config file --- jest.config.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 jest.config.ts diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..ce9e05b2 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,17 @@ +// jest.config.ts +import { Config } from 'jest'; + +const config: Config = { + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: '.', + testRegex: '.spec.ts$', + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + moduleNameMapper: { + '^src/(.*)$': '/src/$1', + }, + testEnvironment: 'node', +}; + +export default config; From 70812e8dd0b86578bfda2545d727e9149b58ff61 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 14:45:25 +0300 Subject: [PATCH 18/19] remove relative path --- src/modules/auth/auth.controller.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 4ca34a00..815a1610 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -1,12 +1,12 @@ // auth.controller.spec.ts import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; -import { SignInUserDto } from './dto/sign-in-auth.dto'; -import { SignUpUserDto } from './dto/sign-up-auth.dto'; -import { AuthDto } from './dto/auth.dto'; -import { UserDto } from '../admin/user/dto/user.dto'; +import { AuthController } from 'src/modules/auth/auth.controller'; +import { AuthService } from 'src/modules/auth/auth.service'; +import { SignInUserDto } from 'src/modules/auth/dto/sign-in-auth.dto'; +import { SignUpUserDto } from 'src/modules/auth/dto/sign-up-auth.dto'; +import { AuthDto } from 'src/modules/auth/dto/auth.dto'; +import { UserDto } from 'src/modules/admin/user/dto/user.dto'; import { Response } from 'express'; describe('AuthController', () => { From 2ef524ff78a79b4dc652c50db6b2d217834548b8 Mon Sep 17 00:00:00 2001 From: jerrygirmaa Date: Tue, 13 May 2025 14:45:25 +0300 Subject: [PATCH 19/19] remove relative path --- src/modules/auth/auth.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 26633d3d..1499edbe 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -7,12 +7,12 @@ import { Req, Res, } from '@nestjs/common'; -import { AuthService } from './auth.service'; -import { SignInUserDto } from './dto/sign-in-auth.dto'; -import { SignUpUserDto } from './dto/sign-up-auth.dto'; +import { AuthService } from 'src/modules/auth/auth.service'; +import { SignInUserDto } from 'src/modules/auth/dto/sign-in-auth.dto'; +import { SignUpUserDto } from 'src/modules/auth/dto/sign-up-auth.dto'; import { AuthGuard } from '@nestjs/passport'; import { Response } from 'express'; -import { AuthDto } from './dto/auth.dto'; +import { AuthDto } from 'src/modules/auth/dto/auth.dto'; @Controller('auth') export class AuthController {