import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; import { GetLeagueProtestsUseCase, GetLeagueProtestsResult, GetLeagueProtestsInput, GetLeagueProtestsErrorCode, } from './GetLeagueProtestsUseCase'; import { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import { IProtestRepository } from '../../domain/repositories/IProtestRepository'; import { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import { Race } from '../../domain/entities/Race'; import { Protest } from '../../domain/entities/Protest'; import { Driver } from '../../domain/entities/Driver'; import { League } from '../../domain/entities/League'; import type { UseCaseOutputPort } from '@core/shared/application'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; describe('GetLeagueProtestsUseCase', () => { let useCase: GetLeagueProtestsUseCase; let raceRepository: { findByLeagueId: Mock; }; let protestRepository: { findByRaceId: Mock; }; let driverRepository: { findById: Mock; }; let leagueRepository: { findById: Mock; }; let output: UseCaseOutputPort & { present: Mock }; beforeEach(() => { raceRepository = { findByLeagueId: vi.fn(), }; protestRepository = { findByRaceId: vi.fn(), }; driverRepository = { findById: vi.fn(), }; leagueRepository = { findById: vi.fn(), }; output = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; useCase = new GetLeagueProtestsUseCase( raceRepository as unknown as IRaceRepository, protestRepository as unknown as IProtestRepository, driverRepository as unknown as IDriverRepository, leagueRepository as unknown as ILeagueRepository, output, ); }); it('should return protests with races and drivers', async () => { const leagueId = 'league-1'; const league = League.create({ id: leagueId, name: 'Test League', ownerId: 'owner-1', description: 'A test league', }); const race = Race.create({ id: 'race-1', leagueId, scheduledAt: new Date(), track: 'Track 1', car: 'Car 1', }); const protest = Protest.create({ id: 'protest-1', raceId: 'race-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', incident: { lap: 1, description: 'Incident' }, status: 'pending', filedAt: new Date(), }); const driver1 = Driver.create({ id: 'driver-1', iracingId: '123', name: 'Driver 1', country: 'US', }); const driver2 = Driver.create({ id: 'driver-2', iracingId: '456', name: 'Driver 2', country: 'UK', }); leagueRepository.findById.mockResolvedValue(league); raceRepository.findByLeagueId.mockResolvedValue([race]); protestRepository.findByRaceId.mockResolvedValue([protest]); driverRepository.findById.mockImplementation((id: string) => { if (id === 'driver-1') return Promise.resolve(driver1); if (id === 'driver-2') return Promise.resolve(driver2); return Promise.resolve(null); }); const input: GetLeagueProtestsInput = { leagueId }; const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); const presented = output.present.mock.calls[0]?.[0] as GetLeagueProtestsResult; expect(presented?.league).toEqual(league); expect(presented?.protests).toHaveLength(1); const presentedProtest = presented?.protests[0]; expect(presentedProtest?.protest).toEqual(protest); expect(presentedProtest?.race).toEqual(race); expect(presentedProtest?.protestingDriver).toEqual(driver1); expect(presentedProtest?.accusedDriver).toEqual(driver2); }); it('should return empty protests when no races', async () => { const leagueId = 'league-1'; const league = League.create({ id: leagueId, name: 'Test League', ownerId: 'owner-1', description: 'A test league', }); leagueRepository.findById.mockResolvedValue(league); raceRepository.findByLeagueId.mockResolvedValue([]); const input: GetLeagueProtestsInput = { leagueId }; const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(result.unwrap()).toBeUndefined(); expect(output.present).toHaveBeenCalledTimes(1); const presented = output.present.mock.calls[0]?.[0] as GetLeagueProtestsResult; expect(presented?.league).toEqual(league); expect(presented?.protests).toEqual([]); }); it('should return LEAGUE_NOT_FOUND when league does not exist', async () => { const leagueId = 'missing-league'; leagueRepository.findById.mockResolvedValue(null); const input: GetLeagueProtestsInput = { leagueId }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetLeagueProtestsErrorCode, { message: string } >; expect(err.code).toBe('LEAGUE_NOT_FOUND'); expect(err.details.message).toBe('League not found'); expect(output.present).not.toHaveBeenCalled(); }); it('should return REPOSITORY_ERROR when repository throws', async () => { const leagueId = 'league-1'; const league = League.create({ id: leagueId, name: 'Test League', ownerId: 'owner-1', description: 'A test league', }); leagueRepository.findById.mockResolvedValue(league); raceRepository.findByLeagueId.mockRejectedValue(new Error('DB error')); const input: GetLeagueProtestsInput = { leagueId }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< GetLeagueProtestsErrorCode, { message: string } >; expect(err.code).toBe('REPOSITORY_ERROR'); expect(err.details.message).toBe('DB error'); expect(output.present).not.toHaveBeenCalled(); }); });