import 'reflect-metadata'; import { Reflector } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import request from 'supertest'; import { Mock, vi } from 'vitest'; import { AuthController } from './AuthController'; import { AuthService } from './AuthService'; import { AuthSessionDTO, LoginParamsDTO, SignupParamsDTO, SignupSponsorParamsDTO } from './dtos/AuthDto'; import type { CommandResultDTO } from './presenters/CommandResultPresenter'; import { AuthenticationGuard } from './AuthenticationGuard'; import { AuthorizationGuard } from './AuthorizationGuard'; import type { AuthorizationService } from './AuthorizationService'; import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; import type { PolicyService, PolicySnapshot } from '../policy/PolicyService'; describe('AuthController', () => { let controller: AuthController; let service: AuthService; beforeEach(() => { service = { signupWithEmail: vi.fn(), signupSponsor: vi.fn(), loginWithEmail: vi.fn(), getCurrentSession: vi.fn(), logout: vi.fn(), } as unknown as AuthService; controller = new AuthController(service); }); describe('signup', () => { it('should call service.signupWithEmail and return session DTO', async () => { const params: SignupParamsDTO = { email: 'test@example.com', password: 'password123', displayName: 'John Smith', iracingCustomerId: '12345', primaryDriverId: 'driver1', avatarUrl: 'http://example.com/avatar.jpg', }; const session: AuthSessionDTO = { token: 'token123', user: { userId: 'user1', email: 'test@example.com', displayName: 'John Smith', }, }; (service.signupWithEmail as Mock).mockResolvedValue(session); const result = await controller.signup(params); expect(service.signupWithEmail).toHaveBeenCalledWith(params); expect(result).toEqual(session); }); }); describe('signupSponsor', () => { it('should call service.signupSponsor and return session DTO', async () => { const params: SignupSponsorParamsDTO = { email: 'sponsor@example.com', password: 'Password123', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; const session: AuthSessionDTO = { token: 'token123', user: { userId: 'user1', email: 'sponsor@example.com', displayName: 'John Doe', companyId: 'company-123', }, }; (service.signupSponsor as Mock).mockResolvedValue(session); const result = await controller.signupSponsor(params); expect(service.signupSponsor).toHaveBeenCalledWith(params); expect(result).toEqual(session); }); }); describe('login', () => { it('should call service.loginWithEmail and return session DTO', async () => { const params: LoginParamsDTO = { email: 'test@example.com', password: 'password123', }; const session: AuthSessionDTO = { token: 'token123', user: { userId: 'user1', email: 'test@example.com', displayName: 'John Smith', }, }; (service.loginWithEmail as Mock).mockResolvedValue(session); const result = await controller.login(params); expect(service.loginWithEmail).toHaveBeenCalledWith(params); expect(result).toEqual(session); }); }); describe('getSession', () => { it('should call service.getCurrentSession and write JSON response', async () => { const session: AuthSessionDTO = { token: 'token123', user: { userId: 'user1', email: 'test@example.com', displayName: 'John Smith', }, }; (service.getCurrentSession as Mock).mockResolvedValue(session); const res = { json: vi.fn() } as any; await controller.getSession(res); expect(service.getCurrentSession).toHaveBeenCalled(); expect(res.json).toHaveBeenCalledWith(session); }); it('should write JSON null when no session', async () => { (service.getCurrentSession as Mock).mockResolvedValue(null); const res = { json: vi.fn() } as any; await controller.getSession(res); expect(service.getCurrentSession).toHaveBeenCalled(); expect(res.json).toHaveBeenCalledWith(null); }); }); describe('logout', () => { it('should call service.logout and return DTO', async () => { const dto: CommandResultDTO = { success: true }; (service.logout as Mock).mockResolvedValue(dto); const result = await controller.logout(); expect(service.logout).toHaveBeenCalled(); expect(result).toEqual(dto); }); }); describe('auth guards (HTTP)', () => { let app: any; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), }; const authorizationService: Pick = { getRolesForUser: vi.fn(() => []), }; const policyService: Pick = { getSnapshot: vi.fn(async (): Promise => ({ policyVersion: 1, operationalMode: 'normal', maintenanceAllowlist: { view: [], mutate: [] }, capabilities: {}, loadedFrom: 'defaults', loadedAtIso: new Date(0).toISOString(), })), }; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [AuthController], providers: [ { provide: AuthService, useValue: { getCurrentSession: vi.fn(async () => null), loginWithEmail: vi.fn(), signupWithEmail: vi.fn(), signupSponsor: vi.fn(), logout: vi.fn(), startIracingAuth: vi.fn(), iracingCallback: vi.fn(), }, }, ], }) .overrideProvider(AuthController) .useFactory({ factory: (authService: AuthService) => new AuthController(authService), inject: [AuthService], }) .compile(); app = module.createNestApplication(); const reflector = new Reflector(); app.useGlobalGuards( new AuthenticationGuard(sessionPort as any), new AuthorizationGuard(reflector, authorizationService as any), new FeatureAvailabilityGuard(reflector, policyService as any), ); await app.init(); }); afterEach(async () => { await app?.close(); vi.clearAllMocks(); }); it('allows @Public() endpoint without a session', async () => { await request(app.getHttpServer()).get('/auth/session').expect(200); }); }); });