authentication authorization
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import request from 'supertest';
|
||||
import { RaceController } from './RaceController';
|
||||
import { RaceService } from './RaceService';
|
||||
import { vi, Mocked } from 'vitest';
|
||||
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
|
||||
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
|
||||
import type { AuthorizationService } from '../auth/AuthorizationService';
|
||||
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
|
||||
import type { PolicyService, PolicySnapshot } from '../policy/PolicyService';
|
||||
|
||||
describe('RaceController', () => {
|
||||
let controller: RaceController;
|
||||
@@ -70,4 +79,75 @@ describe('RaceController', () => {
|
||||
expect(result).toEqual(mockPresenter.viewModel);
|
||||
});
|
||||
});
|
||||
|
||||
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: [RaceController],
|
||||
providers: [
|
||||
{
|
||||
provide: RaceService,
|
||||
useValue: {
|
||||
getAllRaces: vi.fn(async () => ({ viewModel: { races: [], filters: { statuses: [], leagues: [] } } })),
|
||||
registerForRace: vi.fn(async () => ({ viewModel: { success: true } })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).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() read without a session', async () => {
|
||||
await request(app.getHttpServer()).get('/races/all').expect(200);
|
||||
});
|
||||
|
||||
it('denies mutation by default when not authenticated (401)', async () => {
|
||||
await request(app.getHttpServer()).post('/races/r1/register').send({}).expect(401);
|
||||
});
|
||||
|
||||
it('allows mutation when authenticated via session port', async () => {
|
||||
vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
|
||||
token: 't',
|
||||
user: { id: 'user-1' },
|
||||
});
|
||||
|
||||
await request(app.getHttpServer()).post('/races/r1/register').send({}).expect(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Body, Controller, Get, HttpCode, HttpStatus, InternalServerErrorException, Param, Post, Query, Inject } from '@nestjs/common';
|
||||
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Public } from '../auth/Public';
|
||||
import { RaceService } from './RaceService';
|
||||
import { AllRacesPageDTO } from './dtos/AllRacesPageDTO';
|
||||
import { RaceStatsDTO } from './dtos/RaceStatsDTO';
|
||||
@@ -24,6 +25,7 @@ import { PenaltyTypesReferenceDTO } from './dtos/PenaltyTypesReferenceDTO';
|
||||
export class RaceController {
|
||||
constructor(@Inject(RaceService) private readonly raceService: RaceService) {}
|
||||
|
||||
@Public()
|
||||
@Get('all')
|
||||
@ApiOperation({ summary: 'Get all races' })
|
||||
@ApiResponse({ status: 200, description: 'List of all races', type: AllRacesPageDTO })
|
||||
@@ -32,6 +34,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('total-races')
|
||||
@ApiOperation({ summary: 'Get the total number of races' })
|
||||
@ApiResponse({ status: 200, description: 'Total number of races', type: RaceStatsDTO })
|
||||
@@ -40,6 +43,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('page-data')
|
||||
@ApiOperation({ summary: 'Get races page data' })
|
||||
@ApiQuery({ name: 'leagueId', description: 'League ID' })
|
||||
@@ -49,6 +53,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('all/page-data')
|
||||
@ApiOperation({ summary: 'Get all races page data' })
|
||||
@ApiResponse({ status: 200, description: 'All races page data', type: AllRacesPageDTO })
|
||||
@@ -57,6 +62,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':raceId')
|
||||
@ApiOperation({ summary: 'Get race detail' })
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@@ -64,12 +70,14 @@ export class RaceController {
|
||||
@ApiResponse({ status: 200, description: 'Race detail', type: RaceDetailDTO })
|
||||
async getRaceDetail(
|
||||
@Param('raceId') raceId: string,
|
||||
@Query('driverId') driverId: string,
|
||||
@Query('driverId') driverId?: string,
|
||||
): Promise<RaceDetailDTO> {
|
||||
const presenter = await this.raceService.getRaceDetail({ raceId, driverId });
|
||||
const params = driverId ? { raceId, driverId } : { raceId };
|
||||
const presenter = await this.raceService.getRaceDetail(params);
|
||||
return await presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':raceId/results')
|
||||
@ApiOperation({ summary: 'Get race results detail' })
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@@ -79,6 +87,7 @@ export class RaceController {
|
||||
return await presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':raceId/sof')
|
||||
@ApiOperation({ summary: 'Get race with strength of field' })
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@@ -88,6 +97,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':raceId/protests')
|
||||
@ApiOperation({ summary: 'Get race protests' })
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
@@ -97,6 +107,7 @@ export class RaceController {
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('reference/penalty-types')
|
||||
@ApiOperation({ summary: 'Get allowed penalty types and semantics' })
|
||||
@ApiResponse({ status: 200, description: 'Penalty types reference', type: PenaltyTypesReferenceDTO })
|
||||
@@ -104,6 +115,7 @@ export class RaceController {
|
||||
return this.raceService.getPenaltyTypesReference();
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':raceId/penalties')
|
||||
@ApiOperation({ summary: 'Get race penalties' })
|
||||
@ApiParam({ name: 'raceId', description: 'Race ID' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
import { ApiPropertyOptional, ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
|
||||
export class GetRaceDetailParamsDTO {
|
||||
@ApiProperty()
|
||||
@@ -7,8 +7,8 @@ export class GetRaceDetailParamsDTO {
|
||||
@IsNotEmpty()
|
||||
raceId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
@ApiPropertyOptional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
driverId!: string;
|
||||
@IsOptional()
|
||||
driverId?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user