import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import { Race } from '../../domain/entities/Race'; import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository'; import type { RaceRepository } from '../../domain/repositories/RaceRepository'; import type { ResultRepository } from '../../domain/repositories/ResultRepository'; import { SessionType } from '../../domain/value-objects/SessionType'; import { GetRaceWithSOFUseCase, type GetRaceWithSOFErrorCode, type GetRaceWithSOFInput, } from './GetRaceWithSOFUseCase'; describe('GetRaceWithSOFUseCase', () => { let useCase: GetRaceWithSOFUseCase; let raceRepository: { findById: Mock; }; let registrationRepository: { getRegisteredDrivers: Mock; }; let resultRepository: { findByRaceId: Mock; }; let getDriverRating: Mock; beforeEach(() => { raceRepository = { findById: vi.fn(), }; registrationRepository = { getRegisteredDrivers: vi.fn(), }; resultRepository = { findByRaceId: vi.fn(), }; getDriverRating = vi.fn(); useCase = new GetRaceWithSOFUseCase( raceRepository as unknown as RaceRepository, registrationRepository as unknown as RaceRegistrationRepository, resultRepository as unknown as ResultRepository, getDriverRating as unknown as (input: { driverId: string }) => Promise<{ rating: number | null; ratingChange: number | null }> ); }); it('should return error when race not found', async () => { raceRepository.findById.mockResolvedValue(null); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetRaceWithSOFErrorCode, { message: string } >; expect(err.code).toBe('RACE_NOT_FOUND'); expect(err.details?.message).toBe('Race with id race-1 not found'); }); it('should return race with stored SOF when available', async () => { const race = Race.create({ id: 'race-1', leagueId: 'league-1', scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', sessionType: SessionType.main(), status: 'scheduled', strengthOfField: 1500, registeredCount: 10, maxParticipants: 20, }); raceRepository.findById.mockResolvedValue(race); registrationRepository.getRegisteredDrivers.mockResolvedValue([ 'driver-1', 'driver-2', 'driver-3', 'driver-4', 'driver-5', 'driver-6', 'driver-7', 'driver-8', 'driver-9', 'driver-10', ]); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.race.id).toBe('race-1'); expect(presented.race.leagueId).toBe('league-1'); expect(presented.strengthOfField).toBe(1500); expect(presented.registeredCount).toBe(10); expect(presented.maxParticipants).toBe(20); expect(presented.participantCount).toBe(10); }); it('should calculate SOF for upcoming race using registrations', async () => { const race = Race.create({ id: 'race-1', leagueId: 'league-1', scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', sessionType: SessionType.main(), status: 'scheduled', }); raceRepository.findById.mockResolvedValue(race); registrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']); getDriverRating.mockImplementation((input) => { if (input.driverId === 'driver-1') { return Promise.resolve({ rating: 1400, ratingChange: null }); } if (input.driverId === 'driver-2') { return Promise.resolve({ rating: 1600, ratingChange: null }); } return Promise.resolve({ rating: null, ratingChange: null }); }); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.strengthOfField).toBe(1500); // average expect(presented.participantCount).toBe(2); expect(registrationRepository.getRegisteredDrivers).toHaveBeenCalledWith('race-1'); expect(resultRepository.findByRaceId).not.toHaveBeenCalled(); }); it('should calculate SOF for completed race using results', async () => { const race = Race.create({ id: 'race-1', leagueId: 'league-1', scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', sessionType: SessionType.main(), status: 'completed', }); raceRepository.findById.mockResolvedValue(race); resultRepository.findByRaceId.mockResolvedValue([ { driverId: 'driver-1' }, { driverId: 'driver-2' }, ]); getDriverRating.mockImplementation((input) => { if (input.driverId === 'driver-1') { return Promise.resolve({ rating: 1400, ratingChange: null }); } if (input.driverId === 'driver-2') { return Promise.resolve({ rating: 1600, ratingChange: null }); } return Promise.resolve({ rating: null, ratingChange: null }); }); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.strengthOfField).toBe(1500); expect(presented.participantCount).toBe(2); expect(resultRepository.findByRaceId).toHaveBeenCalledWith('race-1'); expect(registrationRepository.getRegisteredDrivers).not.toHaveBeenCalled(); }); it('should handle missing ratings gracefully', async () => { const race = Race.create({ id: 'race-1', leagueId: 'league-1', scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', sessionType: SessionType.main(), status: 'scheduled', }); raceRepository.findById.mockResolvedValue(race); registrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']); getDriverRating.mockImplementation((input) => { if (input.driverId === 'driver-1') { return Promise.resolve({ rating: 1400, ratingChange: null }); } // driver-2 missing return Promise.resolve({ rating: null, ratingChange: null }); }); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.strengthOfField).toBe(1400); // only one rating expect(presented.participantCount).toBe(2); }); it('should return null SOF when no participants', async () => { const race = Race.create({ id: 'race-1', leagueId: 'league-1', scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', sessionType: SessionType.main(), status: 'scheduled', }); raceRepository.findById.mockResolvedValue(race); registrationRepository.getRegisteredDrivers.mockResolvedValue([]); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.strengthOfField).toBe(null); expect(presented.participantCount).toBe(0); }); it('should wrap repository errors in REPOSITORY_ERROR', async () => { raceRepository.findById.mockRejectedValue(new Error('boom')); const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetRaceWithSOFErrorCode, { message: string } >; expect(err.code).toBe('REPOSITORY_ERROR'); expect(err.details?.message).toBe('boom'); }); });