test apps api

This commit is contained in:
2025-12-23 23:14:51 +01:00
parent 16cd572c63
commit efcdbd17f2
71 changed files with 3924 additions and 913 deletions

View File

@@ -1,4 +1,4 @@
import { Body, Controller, Get, HttpCode, HttpStatus, InternalServerErrorException, Param, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, HttpCode, HttpStatus, InternalServerErrorException, Param, Post, Query, Inject } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { RaceService } from './RaceService';
import { AllRacesPageDTO } from './dtos/AllRacesPageDTO';
@@ -21,7 +21,7 @@ import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCom
@ApiTags('races')
@Controller('races')
export class RaceController {
constructor(private readonly raceService: RaceService) {}
constructor(@Inject(RaceService) private readonly raceService: RaceService) {}
@Get('all')
@ApiOperation({ summary: 'Get all races' })

View File

@@ -5,7 +5,7 @@ import { RaceProviders } from './RaceProviders';
@Module({
controllers: [RaceController],
providers: RaceProviders,
providers: [RaceService, ...RaceProviders],
exports: [RaceService],
})
export class RaceModule {}

View File

@@ -90,32 +90,33 @@ import { RaceResultsDetailPresenter } from './presenters/RaceResultsDetailPresen
import { RacesPageDataPresenter } from './presenters/RacesPageDataPresenter';
import { RaceWithSOFPresenter } from './presenters/RaceWithSOFPresenter';
// Define injection tokens
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const PENALTY_REPOSITORY_TOKEN = 'IPenaltyRepository';
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
export const DRIVER_RATING_PROVIDER_TOKEN = 'DriverRatingProvider';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const LOGGER_TOKEN = 'Logger';
import {
RACE_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
DRIVER_REPOSITORY_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
PENALTY_REPOSITORY_TOKEN,
PROTEST_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
DRIVER_RATING_PROVIDER_TOKEN,
IMAGE_SERVICE_TOKEN,
LOGGER_TOKEN,
GET_ALL_RACES_PRESENTER_TOKEN,
GET_TOTAL_RACES_PRESENTER_TOKEN,
IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
RACE_DETAIL_PRESENTER_TOKEN,
RACES_PAGE_DATA_PRESENTER_TOKEN,
ALL_RACES_PAGE_DATA_PRESENTER_TOKEN,
RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
RACE_WITH_SOF_PRESENTER_TOKEN,
RACE_PROTESTS_PRESENTER_TOKEN,
RACE_PENALTIES_PRESENTER_TOKEN,
COMMAND_RESULT_PRESENTER_TOKEN,
} from './RaceTokens';
// Presenter tokens
export const GET_ALL_RACES_PRESENTER_TOKEN = 'GetAllRacesPresenter';
export const GET_TOTAL_RACES_PRESENTER_TOKEN = 'GetTotalRacesPresenter';
export const IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN = 'ImportRaceResultsApiPresenter';
export const RACE_DETAIL_PRESENTER_TOKEN = 'RaceDetailPresenter';
export const RACES_PAGE_DATA_PRESENTER_TOKEN = 'RacesPageDataPresenter';
export const ALL_RACES_PAGE_DATA_PRESENTER_TOKEN = 'AllRacesPageDataPresenter';
export const RACE_RESULTS_DETAIL_PRESENTER_TOKEN = 'RaceResultsDetailPresenter';
export const RACE_WITH_SOF_PRESENTER_TOKEN = 'RaceWithSOFPresenter';
export const RACE_PROTESTS_PRESENTER_TOKEN = 'RaceProtestsPresenter';
export const RACE_PENALTIES_PRESENTER_TOKEN = 'RacePenaltiesPresenter';
export const COMMAND_RESULT_PRESENTER_TOKEN = 'CommandResultPresenter';
export * from './RaceTokens';
// Adapter classes to bridge presenters with UseCaseOutputPort interface
class GetAllRacesOutputAdapter implements UseCaseOutputPort<GetAllRacesResult> {
@@ -295,7 +296,6 @@ class ReviewProtestOutputAdapter implements UseCaseOutputPort<ReviewProtestResul
}
export const RaceProviders: Provider[] = [
RaceService,
{
provide: RACE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryRaceRepository(logger),
@@ -471,16 +471,15 @@ export const RaceProviders: Provider[] = [
leagueMembershipRepo: ILeagueMembershipRepository,
presenter: RaceDetailPresenter,
) => {
const useCase = new GetRaceDetailUseCase(
return new GetRaceDetailUseCase(
raceRepo,
leagueRepo,
driverRepo,
raceRegRepo,
resultRepo,
leagueMembershipRepo,
new RaceDetailOutputAdapter(presenter),
);
useCase.setOutput(new RaceDetailOutputAdapter(presenter));
return useCase;
},
inject: [
RACE_REPOSITORY_TOKEN,

View File

@@ -0,0 +1,138 @@
import { describe, expect, it, vi } from 'vitest';
import { RaceService } from './RaceService';
describe('RaceService', () => {
it('invokes each use case and returns the corresponding presenter', async () => {
const mkUseCase = () => ({ execute: vi.fn(async () => {}) });
const getAllRacesUseCase = mkUseCase();
const getTotalRacesUseCase = mkUseCase();
const importRaceResultsApiUseCase = mkUseCase();
const getRaceDetailUseCase = mkUseCase();
const getRacesPageDataUseCase = mkUseCase();
const getAllRacesPageDataUseCase = mkUseCase();
const getRaceResultsDetailUseCase = mkUseCase();
const getRaceWithSOFUseCase = mkUseCase();
const getRaceProtestsUseCase = mkUseCase();
const getRacePenaltiesUseCase = mkUseCase();
const registerForRaceUseCase = mkUseCase();
const withdrawFromRaceUseCase = mkUseCase();
const cancelRaceUseCase = mkUseCase();
const completeRaceUseCase = mkUseCase();
const fileProtestUseCase = mkUseCase();
const quickPenaltyUseCase = mkUseCase();
const applyPenaltyUseCase = mkUseCase();
const requestProtestDefenseUseCase = mkUseCase();
const reviewProtestUseCase = mkUseCase();
const reopenRaceUseCase = mkUseCase();
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
const getAllRacesPresenter = {} as any;
const getTotalRacesPresenter = {} as any;
const importRaceResultsApiPresenter = {} as any;
const raceDetailPresenter = {} as any;
const racesPageDataPresenter = {} as any;
const allRacesPageDataPresenter = {} as any;
const raceResultsDetailPresenter = {} as any;
const raceWithSOFPresenter = {} as any;
const raceProtestsPresenter = {} as any;
const racePenaltiesPresenter = {} as any;
const commandResultPresenter = {} as any;
const service = new RaceService(
getAllRacesUseCase as any,
getTotalRacesUseCase as any,
importRaceResultsApiUseCase as any,
getRaceDetailUseCase as any,
getRacesPageDataUseCase as any,
getAllRacesPageDataUseCase as any,
getRaceResultsDetailUseCase as any,
getRaceWithSOFUseCase as any,
getRaceProtestsUseCase as any,
getRacePenaltiesUseCase as any,
registerForRaceUseCase as any,
withdrawFromRaceUseCase as any,
cancelRaceUseCase as any,
completeRaceUseCase as any,
fileProtestUseCase as any,
quickPenaltyUseCase as any,
applyPenaltyUseCase as any,
requestProtestDefenseUseCase as any,
reviewProtestUseCase as any,
reopenRaceUseCase as any,
logger as any,
getAllRacesPresenter,
getTotalRacesPresenter,
importRaceResultsApiPresenter,
raceDetailPresenter,
racesPageDataPresenter,
allRacesPageDataPresenter,
raceResultsDetailPresenter,
raceWithSOFPresenter,
raceProtestsPresenter,
racePenaltiesPresenter,
commandResultPresenter,
);
expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
expect(getAllRacesUseCase.execute).toHaveBeenCalledWith({});
expect(await service.getTotalRaces()).toBe(getTotalRacesPresenter);
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter);
expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter);
expect(getRaceDetailUseCase.execute).toHaveBeenCalled();
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
expect(getRacesPageDataUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
expect(await service.getAllRacesPageData()).toBe(allRacesPageDataPresenter);
expect(getAllRacesPageDataUseCase.execute).toHaveBeenCalledWith({});
expect(await service.getRaceResultsDetail('r1')).toBe(raceResultsDetailPresenter);
expect(getRaceResultsDetailUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(await service.getRaceWithSOF('r1')).toBe(raceWithSOFPresenter);
expect(getRaceWithSOFUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(await service.getRaceProtests('r1')).toBe(raceProtestsPresenter);
expect(getRaceProtestsUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(await service.getRacePenalties('r1')).toBe(racePenaltiesPresenter);
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(registerForRaceUseCase.execute).toHaveBeenCalled();
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter);
expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
expect(await service.fileProtest({} as any)).toBe(commandResultPresenter);
expect(fileProtestUseCase.execute).toHaveBeenCalled();
expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter);
expect(quickPenaltyUseCase.execute).toHaveBeenCalled();
expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter);
expect(applyPenaltyUseCase.execute).toHaveBeenCalled();
expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter);
expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter);
expect(reviewProtestUseCase.execute).toHaveBeenCalled();
});
});

View File

@@ -66,7 +66,7 @@ import {
RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
RACE_WITH_SOF_PRESENTER_TOKEN,
RACES_PAGE_DATA_PRESENTER_TOKEN
} from './RaceProviders';
} from './RaceTokens';
@Injectable()
export class RaceService {

View File

@@ -0,0 +1,24 @@
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const PENALTY_REPOSITORY_TOKEN = 'IPenaltyRepository';
export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository';
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
export const DRIVER_RATING_PROVIDER_TOKEN = 'DriverRatingProvider';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const LOGGER_TOKEN = 'Logger';
export const GET_ALL_RACES_PRESENTER_TOKEN = 'GetAllRacesPresenter';
export const GET_TOTAL_RACES_PRESENTER_TOKEN = 'GetTotalRacesPresenter';
export const IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN = 'ImportRaceResultsApiPresenter';
export const RACE_DETAIL_PRESENTER_TOKEN = 'RaceDetailPresenter';
export const RACES_PAGE_DATA_PRESENTER_TOKEN = 'RacesPageDataPresenter';
export const ALL_RACES_PAGE_DATA_PRESENTER_TOKEN = 'AllRacesPageDataPresenter';
export const RACE_RESULTS_DETAIL_PRESENTER_TOKEN = 'RaceResultsDetailPresenter';
export const RACE_WITH_SOF_PRESENTER_TOKEN = 'RaceWithSOFPresenter';
export const RACE_PROTESTS_PRESENTER_TOKEN = 'RaceProtestsPresenter';
export const RACE_PENALTIES_PRESENTER_TOKEN = 'RacePenaltiesPresenter';
export const COMMAND_RESULT_PRESENTER_TOKEN = 'CommandResultPresenter';