import { describe, it, expect, vi, Mocked } from 'vitest'; import { LeagueStewardingService } from './LeagueStewardingService'; import { RaceService } from '../races/RaceService'; import { ProtestService } from '../protests/ProtestService'; import { PenaltyService } from '../penalties/PenaltyService'; import { DriverService } from '../drivers/DriverService'; import { LeagueMembershipService } from './LeagueMembershipService'; import { LeagueStewardingViewModel } from '@/lib/view-models/LeagueStewardingViewModel'; describe('LeagueStewardingService', () => { let mockRaceService: Mocked; let mockProtestService: Mocked; let mockPenaltyService: Mocked; let mockDriverService: Mocked; let mockLeagueMembershipService: Mocked; let service: LeagueStewardingService; beforeEach(() => { mockRaceService = { findByLeagueId: vi.fn(), } as Mocked; mockProtestService = { findByRaceId: vi.fn(), getProtestById: vi.fn(), } as Mocked; mockPenaltyService = { findByRaceId: vi.fn(), getPenaltyTypesReference: vi.fn(), } as Mocked; mockDriverService = { findByIds: vi.fn(), } as Mocked; mockLeagueMembershipService = {} as Mocked; service = new LeagueStewardingService( mockRaceService, mockProtestService, mockPenaltyService, mockDriverService, mockLeagueMembershipService ); }); describe('getLeagueStewardingData', () => { it('should return stewarding data with all races and their protests/penalties', async () => { const leagueId = 'league-123'; // Mock races mockRaceService.findByLeagueId.mockResolvedValue([ { id: 'race-1', track: 'Monza', scheduledAt: '2023-10-01T10:00:00Z' }, { id: 'race-2', track: 'Silverstone', scheduledAt: '2023-09-15T10:00:00Z' }, ]); // Mock protests for race-1 mockProtestService.findByRaceId.mockImplementation(async (raceId) => { if (raceId === 'race-1') { return [ { id: 'protest-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', status: 'pending' }, { id: 'protest-2', protestingDriverId: 'driver-3', accusedDriverId: 'driver-4', status: 'upheld' }, ]; } return []; }); // Mock penalties for race-1 mockPenaltyService.findByRaceId.mockImplementation(async (raceId) => { if (raceId === 'race-1') { return [ { id: 'penalty-1', driverId: 'driver-2', type: 'time', value: 5, reason: 'Incident' }, ]; } return []; }); // Mock driver service mockDriverService.findByIds.mockResolvedValue([ { id: 'driver-1', name: 'Driver 1' }, { id: 'driver-2', name: 'Driver 2' }, { id: 'driver-3', name: 'Driver 3' }, { id: 'driver-4', name: 'Driver 4' }, ]); const result = await service.getLeagueStewardingData(leagueId); expect(result).toBeInstanceOf(LeagueStewardingViewModel); expect(result.racesWithData).toHaveLength(2); expect(result.totalPending).toBe(1); expect(result.totalResolved).toBe(1); expect(result.totalPenalties).toBe(1); expect(result.pendingRaces).toHaveLength(1); expect(result.historyRaces).toHaveLength(1); }); it('should handle empty races array', async () => { const leagueId = 'league-123'; mockRaceService.findByLeagueId.mockResolvedValue([]); mockProtestService.findByRaceId.mockResolvedValue([]); mockPenaltyService.findByRaceId.mockResolvedValue([]); mockDriverService.findByIds.mockResolvedValue([]); const result = await service.getLeagueStewardingData(leagueId); expect(result.racesWithData).toHaveLength(0); expect(result.totalPending).toBe(0); expect(result.totalResolved).toBe(0); expect(result.totalPenalties).toBe(0); }); }); describe('reviewProtest', () => { it('should call protestService.reviewProtest', async () => { const input = { protestId: 'protest-123', stewardId: 'steward-456', decision: 'upheld', decisionNotes: 'Test notes', }; mockProtestService.reviewProtest = vi.fn().mockResolvedValue(undefined); await service.reviewProtest(input); expect(mockProtestService.reviewProtest).toHaveBeenCalledWith(input); }); }); describe('applyPenalty', () => { it('should call penaltyService.applyPenalty', async () => { const input = { driverId: 'driver-123', raceId: 'race-456', type: 'time', value: 10, reason: 'Test reason', }; mockPenaltyService.applyPenalty = vi.fn().mockResolvedValue(undefined); await service.applyPenalty(input); expect(mockPenaltyService.applyPenalty).toHaveBeenCalledWith(input); }); }); describe('getProtestDetailViewModel', () => { it('should combine protest details + penalty types into a page-ready view model', async () => { const leagueId = 'league-123'; const protestId = 'protest-1'; mockProtestService.getProtestById.mockResolvedValue({ protest: { id: protestId, raceId: 'race-1', protestingDriverId: 'd1', accusedDriverId: 'd2', status: 'pending', submittedAt: '2023-10-01T10:00:00Z', description: 'desc' } as any, race: { id: 'race-1' } as any, protestingDriver: { id: 'd1', name: 'Driver 1' } as any, accusedDriver: { id: 'd2', name: 'Driver 2' } as any, }); mockPenaltyService.getPenaltyTypesReference.mockResolvedValue({ penaltyTypes: [ { type: 'time_penalty', requiresValue: true, valueKind: 'seconds' }, { type: 'warning', requiresValue: false, valueKind: 'none' }, ], defaultReasons: { upheld: 'Upheld reason', dismissed: 'Dismissed reason' }, } as any); const result = await service.getProtestDetailViewModel(leagueId, protestId); expect(mockProtestService.getProtestById).toHaveBeenCalledWith(leagueId, protestId); expect(mockPenaltyService.getPenaltyTypesReference).toHaveBeenCalled(); expect(result.protest.id).toBe(protestId); expect(result.penaltyTypes.length).toBe(2); expect(result.defaultReasons.upheld).toBe('Upheld reason'); expect(result.initialPenaltyType).toBe('time_penalty'); }); }); });