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; 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", diff --git a/src/modules/admin/account/account.controller.spec.ts b/src/modules/admin/account/account.controller.spec.ts index 47b7b170..e3237835 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 { 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; + 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); + }); }); }); diff --git a/src/modules/admin/account/account.controller.ts b/src/modules/admin/account/account.controller.ts index ac6b5209..8613b7e9 100644 --- a/src/modules/admin/account/account.controller.ts +++ b/src/modules/admin/account/account.controller.ts @@ -10,10 +10,10 @@ 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 { 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'; diff --git a/src/modules/admin/account/account.service.spec.ts b/src/modules/admin/account/account.service.spec.ts index ef75dcbc..4781bab8 100644 --- a/src/modules/admin/account/account.service.spec.ts +++ b/src/modules/admin/account/account.service.spec.ts @@ -1,18 +1,192 @@ -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 '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; + 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(); + + 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), + expect.any(Object), + ]); + }); + }); + + 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' }); }); }); diff --git a/src/modules/admin/account/account.service.ts b/src/modules/admin/account/account.service.ts index a46dafe1..e02cf240 100644 --- a/src/modules/admin/account/account.service.ts +++ b/src/modules/admin/account/account.service.ts @@ -1,6 +1,6 @@ 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 'src/modules/prisma/prisma.service'; import { RoleType } from '@prisma/client'; 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' }); }); }); diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts new file mode 100644 index 00000000..815a1610 --- /dev/null +++ b/src/modules/auth/auth.controller.spec.ts @@ -0,0 +1,131 @@ +// auth.controller.spec.ts + +import { Test, TestingModule } from '@nestjs/testing'; +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', () => { + let controller: AuthController; + + 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); + }); + + 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`, + ); + }); + }); +}); 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 { 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/*"] + } } }