Files
gridpilot.gg/apps/website/lib/services/leagues/LeagueStewardingService.test.ts
2026-01-12 01:01:49 +01:00

180 lines
6.4 KiB
TypeScript

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<RaceService>;
let mockProtestService: Mocked<ProtestService>;
let mockPenaltyService: Mocked<PenaltyService>;
let mockDriverService: Mocked<DriverService>;
let mockLeagueMembershipService: Mocked<LeagueMembershipService>;
let service: LeagueStewardingService;
beforeEach(() => {
mockRaceService = {
findByLeagueId: vi.fn(),
} as Mocked<RaceService>;
mockProtestService = {
findByRaceId: vi.fn(),
getProtestById: vi.fn(),
} as Mocked<ProtestService>;
mockPenaltyService = {
findByRaceId: vi.fn(),
getPenaltyTypesReference: vi.fn(),
} as Mocked<PenaltyService>;
mockDriverService = {
findByIds: vi.fn(),
} as Mocked<DriverService>;
mockLeagueMembershipService = {} as Mocked<LeagueMembershipService>;
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');
});
});
});