import { beforeEach, describe, expect, it } from 'vitest'; import { LeaguesTestContext } from '../LeaguesTestContext'; import { League as RacingLeague } from '../../../../core/racing/domain/entities/League'; import { Race } from '../../../../core/racing/domain/entities/Race'; import { Driver } from '../../../../core/racing/domain/entities/Driver'; import { Protest } from '../../../../core/racing/domain/entities/Protest'; import { Penalty } from '../../../../core/racing/domain/entities/penalty/Penalty'; import { GetLeagueProtestsUseCase } from '../../../../core/racing/application/use-cases/GetLeagueProtestsUseCase'; import { GetRacePenaltiesUseCase } from '../../../../core/racing/application/use-cases/GetRacePenaltiesUseCase'; describe('League Stewarding - GetLeagueStewarding', () => { let context: LeaguesTestContext; let getLeagueProtestsUseCase: GetLeagueProtestsUseCase; let getRacePenaltiesUseCase: GetRacePenaltiesUseCase; beforeEach(() => { context = new LeaguesTestContext(); context.clear(); getLeagueProtestsUseCase = new GetLeagueProtestsUseCase( context.raceRepository, context.protestRepository, context.racingDriverRepository, context.racingLeagueRepository, ); getRacePenaltiesUseCase = new GetRacePenaltiesUseCase( context.penaltyRepository, context.racingDriverRepository, ); }); const seedRacingLeague = async (params: { leagueId: string }) => { const league = RacingLeague.create({ id: params.leagueId, name: 'Racing League', description: 'League used for stewarding integration tests', ownerId: 'driver-123', }); await context.racingLeagueRepository.create(league); return league; }; const seedRace = async (params: { raceId: string; leagueId: string }) => { const race = Race.create({ id: params.raceId, leagueId: params.leagueId, track: 'Track 1', car: 'GT3', scheduledAt: new Date('2025-01-10T20:00:00Z'), status: 'completed', }); await context.raceRepository.create(race); return race; }; const seedDriver = async (params: { driverId: string; iracingId?: string }) => { const driver = Driver.create({ id: params.driverId, name: 'Driver Name', iracingId: params.iracingId || `ir-${params.driverId}`, country: 'US', }); await context.racingDriverRepository.create(driver); return driver; }; const seedProtest = async (params: { protestId: string; raceId: string; protestingDriverId: string; accusedDriverId: string; status?: string; }) => { const protest = Protest.create({ id: params.protestId, raceId: params.raceId, protestingDriverId: params.protestingDriverId, accusedDriverId: params.accusedDriverId, incident: { lap: 5, description: 'Contact on corner entry', }, status: params.status || 'pending', }); await context.protestRepository.create(protest); return protest; }; const seedPenalty = async (params: { penaltyId: string; leagueId: string; driverId: string; raceId?: string; status?: string; }) => { const penalty = Penalty.create({ id: params.penaltyId, leagueId: params.leagueId, driverId: params.driverId, type: 'time_penalty', value: 5, reason: 'Contact on corner entry', issuedBy: 'steward-1', status: params.status || 'pending', raceId: params.raceId || 'default-race-id', }); await context.penaltyRepository.create(penalty); return penalty; }; describe('Success Path', () => { it('should retrieve league protests with driver and race details', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const protestingDriverId = 'driver-1'; const accusedDriverId = 'driver-2'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId: protestingDriverId }); await seedDriver({ driverId: accusedDriverId }); await seedProtest({ protestId: 'protest-1', raceId, protestingDriverId, accusedDriverId, status: 'pending', }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.league.id.toString()).toBe(leagueId); expect(data.protests).toHaveLength(1); expect(data.protests[0].protest.id.toString()).toBe('protest-1'); expect(data.protests[0].protest.status.toString()).toBe('pending'); expect(data.protests[0].race?.id.toString()).toBe(raceId); expect(data.protests[0].protestingDriver?.id.toString()).toBe(protestingDriverId); expect(data.protests[0].accusedDriver?.id.toString()).toBe(accusedDriverId); }); it('should retrieve penalties with driver details', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const driverId = 'driver-1'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId }); await seedPenalty({ penaltyId: 'penalty-1', leagueId, driverId, raceId, status: 'pending', }); const result = await getRacePenaltiesUseCase.execute({ raceId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.penalties).toHaveLength(1); expect(data.penalties[0].id.toString()).toBe('penalty-1'); expect(data.penalties[0].status.toString()).toBe('pending'); expect(data.drivers).toHaveLength(1); expect(data.drivers[0].id.toString()).toBe(driverId); }); it('should retrieve multiple protests for a league', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const protestingDriverId = 'driver-1'; const accusedDriverId = 'driver-2'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId: protestingDriverId }); await seedDriver({ driverId: accusedDriverId }); await seedProtest({ protestId: 'protest-1', raceId, protestingDriverId, accusedDriverId, status: 'pending', }); await seedProtest({ protestId: 'protest-2', raceId, protestingDriverId: accusedDriverId, accusedDriverId: protestingDriverId, status: 'under_review', }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.protests).toHaveLength(2); expect(data.protests.map(p => p.protest.id.toString()).sort()).toEqual(['protest-1', 'protest-2']); }); it('should retrieve multiple penalties for a race', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const driverId1 = 'driver-1'; const driverId2 = 'driver-2'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId: driverId1 }); await seedDriver({ driverId: driverId2 }); await seedPenalty({ penaltyId: 'penalty-1', leagueId, driverId: driverId1, raceId, status: 'pending', }); await seedPenalty({ penaltyId: 'penalty-2', leagueId, driverId: driverId2, raceId, status: 'applied', }); const result = await getRacePenaltiesUseCase.execute({ raceId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.penalties).toHaveLength(2); expect(data.penalties.map(p => p.id.toString()).sort()).toEqual(['penalty-1', 'penalty-2']); expect(data.drivers).toHaveLength(2); }); it('should retrieve resolved protests', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const protestingDriverId = 'driver-1'; const accusedDriverId = 'driver-2'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId: protestingDriverId }); await seedDriver({ driverId: accusedDriverId }); await seedProtest({ protestId: 'protest-1', raceId, protestingDriverId, accusedDriverId, status: 'upheld', }); await seedProtest({ protestId: 'protest-2', raceId, protestingDriverId, accusedDriverId, status: 'dismissed', }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.protests).toHaveLength(2); expect(data.protests.filter(p => p.protest.status.toString() === 'upheld')).toHaveLength(1); expect(data.protests.filter(p => p.protest.status.toString() === 'dismissed')).toHaveLength(1); }); it('should retrieve applied penalties', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const driverId = 'driver-1'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); await seedDriver({ driverId }); await seedPenalty({ penaltyId: 'penalty-1', leagueId, driverId, raceId, status: 'applied', }); const result = await getRacePenaltiesUseCase.execute({ raceId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.penalties).toHaveLength(1); expect(data.penalties[0].status.toString()).toBe('applied'); }); }); describe('Edge Cases', () => { it('should handle league with no protests', async () => { const leagueId = 'league-empty'; await seedRacingLeague({ leagueId }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.protests).toHaveLength(0); }); it('should handle race with no penalties', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); const result = await getRacePenaltiesUseCase.execute({ raceId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.penalties).toHaveLength(0); expect(data.drivers).toHaveLength(0); }); it('should handle league with no races', async () => { const leagueId = 'league-empty'; await seedRacingLeague({ leagueId }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.protests).toHaveLength(0); }); it('should handle protest with missing driver details', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const protestingDriverId = 'driver-1'; const accusedDriverId = 'driver-2'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); // Don't seed drivers await seedProtest({ protestId: 'protest-1', raceId, protestingDriverId, accusedDriverId, status: 'pending', }); const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.protests).toHaveLength(1); expect(data.protests[0].protestingDriver).toBeNull(); expect(data.protests[0].accusedDriver).toBeNull(); }); it('should handle penalty with missing driver details', async () => { const leagueId = 'league-1'; const raceId = 'race-1'; const driverId = 'driver-1'; await seedRacingLeague({ leagueId }); await seedRace({ raceId, leagueId }); // Don't seed driver await seedPenalty({ penaltyId: 'penalty-1', leagueId, driverId, raceId, status: 'pending', }); const result = await getRacePenaltiesUseCase.execute({ raceId }); expect(result.isOk()).toBe(true); const data = result.unwrap(); expect(data.penalties).toHaveLength(1); expect(data.drivers).toHaveLength(0); }); }); describe('Error Handling', () => { it('should return LEAGUE_NOT_FOUND when league does not exist', async () => { const result = await getLeagueProtestsUseCase.execute({ leagueId: 'missing-league' }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('LEAGUE_NOT_FOUND'); }); it('should handle repository errors gracefully', async () => { const leagueId = 'league-1'; await seedRacingLeague({ leagueId }); // Mock repository error context.raceRepository.findByLeagueId = async () => { throw new Error('Database error'); }; const result = await getLeagueProtestsUseCase.execute({ leagueId }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR'); }); }); });