import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository'; import type { RaceRepository } from '../../domain/repositories/RaceRepository'; import type { ResultRepository } from '../../domain/repositories/ResultRepository'; import type { StandingRepository } from '../../domain/repositories/StandingRepository'; import { CompleteRaceUseCase, type CompleteRaceInput } from './CompleteRaceUseCase'; describe('CompleteRaceUseCase', () => { let useCase: CompleteRaceUseCase; let leagueRepository: { findById: Mock; }; let raceRepository: { findById: Mock; update: Mock; }; let raceRegistrationRepository: { getRegisteredDrivers: Mock; }; let resultRepository: { create: Mock; }; let standingRepository: { findByDriverIdAndLeagueId: Mock; save: Mock; }; let getDriverRating: Mock; beforeEach(() => { leagueRepository = { findById: vi.fn(), }; raceRepository = { findById: vi.fn(), update: vi.fn(), }; raceRegistrationRepository = { getRegisteredDrivers: vi.fn(), }; resultRepository = { create: vi.fn(), }; standingRepository = { findByDriverIdAndLeagueId: vi.fn(), save: vi.fn(), }; getDriverRating = vi.fn(); useCase = new CompleteRaceUseCase( leagueRepository as unknown as LeagueRepository, raceRepository as unknown as RaceRepository, raceRegistrationRepository as unknown as RaceRegistrationRepository, resultRepository as unknown as ResultRepository, standingRepository as unknown as StandingRepository, getDriverRating ); }); it('should complete race successfully when race exists and has registered drivers', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; const mockLeague = { id: 'league-1', settings: { pointsSystem: 'f1-2024', customPoints: null }, }; const mockRace = { id: 'race-1', leagueId: 'league-1', status: 'scheduled', complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }), }; leagueRepository.findById.mockResolvedValue(mockLeague); raceRepository.findById.mockResolvedValue(mockRace); raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']); getDriverRating.mockImplementation((input) => { if (input.driverId === 'driver-1') return Promise.resolve({ rating: 1600, ratingChange: null }); if (input.driverId === 'driver-2') return Promise.resolve({ rating: 1500, ratingChange: null }); return Promise.resolve({ rating: null, ratingChange: null }); }); resultRepository.create.mockResolvedValue(undefined); standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null); standingRepository.save.mockResolvedValue(undefined); raceRepository.update.mockResolvedValue(undefined); const result = await useCase.execute(command); expect(result.isOk()).toBe(true); const presented = result.unwrap(); expect(presented.raceId).toBe('race-1'); expect(leagueRepository.findById).toHaveBeenCalledWith('league-1'); expect(raceRepository.findById).toHaveBeenCalledWith('race-1'); expect(raceRegistrationRepository.getRegisteredDrivers).toHaveBeenCalledWith('race-1'); expect(getDriverRating).toHaveBeenCalledTimes(2); expect(resultRepository.create).toHaveBeenCalledTimes(2); expect(standingRepository.save).toHaveBeenCalledTimes(2); expect(mockRace.complete).toHaveBeenCalled(); expect(raceRepository.update).toHaveBeenCalledWith({ id: 'race-1', status: 'completed' }); }); it('should return error when race does not exist', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; raceRepository.findById.mockResolvedValue(null); const result = await useCase.execute(command); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND'); }); it('should return error when no registered drivers', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; const mockRace = { id: 'race-1', leagueId: 'league-1', status: 'scheduled', complete: vi.fn(), }; raceRepository.findById.mockResolvedValue(mockRace); raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue([]); const result = await useCase.execute(command); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('NO_REGISTERED_DRIVERS'); }); it('should return error when repository throws', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; const mockRace = { id: 'race-1', leagueId: 'league-1', status: 'scheduled', complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }), }; raceRepository.findById.mockResolvedValue(mockRace); raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1']); getDriverRating.mockResolvedValue({ rating: 1600, ratingChange: null }); resultRepository.create.mockRejectedValue(new Error('DB error')); const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('REPOSITORY_ERROR'); expect(error.details?.message).toBe('DB error'); }); it('should use league\'s points system when calculating standings', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; const mockLeague = { id: 'league-1', settings: { pointsSystem: 'indycar', customPoints: null }, }; const mockRace = { id: 'race-1', leagueId: 'league-1', status: 'scheduled', complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }), }; leagueRepository.findById.mockResolvedValue(mockLeague); raceRepository.findById.mockResolvedValue(mockRace); raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1']); getDriverRating.mockResolvedValue({ rating: 1600, ratingChange: null }); resultRepository.create.mockResolvedValue(undefined); standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null); standingRepository.save.mockResolvedValue(undefined); raceRepository.update.mockResolvedValue(undefined); const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(standingRepository.save).toHaveBeenCalledTimes(1); // Verify that the standing was saved with indycar points (50 for 1st place) const savedStanding = standingRepository.save.mock.calls[0][0]; expect(savedStanding.points.toNumber()).toBe(50); }); it('should use custom points system when calculating standings', async () => { const command: CompleteRaceInput = { raceId: 'race-1', }; const mockLeague = { id: 'league-1', settings: { pointsSystem: 'custom', customPoints: { 1: 100, 2: 80 } }, }; const mockRace = { id: 'race-1', leagueId: 'league-1', status: 'scheduled', complete: vi.fn().mockReturnValue({ id: 'race-1', status: 'completed' }), }; leagueRepository.findById.mockResolvedValue(mockLeague); raceRepository.findById.mockResolvedValue(mockRace); raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1']); getDriverRating.mockResolvedValue({ rating: 1600, ratingChange: null }); resultRepository.create.mockResolvedValue(undefined); standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null); standingRepository.save.mockResolvedValue(undefined); raceRepository.update.mockResolvedValue(undefined); const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(standingRepository.save).toHaveBeenCalledTimes(1); // Verify that the standing was saved with custom points (100 for 1st place) const savedStanding = standingRepository.save.mock.calls[0][0]; expect(savedStanding.points.toNumber()).toBe(100); }); });