Files
gridpilot.gg/apps/api/src/domain/auth/AuthController.test.ts
2026-01-07 13:26:31 +01:00

221 lines
6.7 KiB
TypeScript

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<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null),
};
const authorizationService: Pick<AuthorizationService, 'getRolesForUser'> = {
getRolesForUser: vi.fn(() => []),
};
const policyService: Pick<PolicyService, 'getSnapshot'> = {
getSnapshot: vi.fn(async (): Promise<PolicySnapshot> => ({
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);
});
});
});