221 lines
6.7 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
}); |