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

205 lines
6.9 KiB
TypeScript

import { describe, it, expect, vi, Mocked } from 'vitest';
import { ProtestService } from './ProtestService';
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
import { ProtestViewModel } from '@/lib/view-models/ProtestViewModel';
import { RaceViewModel } from '@/lib/view-models/RaceViewModel';
import { ProtestDriverViewModel } from '@/lib/view-models/ProtestDriverViewModel';
describe('ProtestService', () => {
let mockApiClient: Mocked<ProtestsApiClient>;
let service: ProtestService;
beforeEach(() => {
mockApiClient = {
getLeagueProtests: vi.fn(),
getLeagueProtest: vi.fn(),
applyPenalty: vi.fn(),
requestDefense: vi.fn(),
reviewProtest: vi.fn(),
getRaceProtests: vi.fn(),
} as unknown as Mocked<ProtestsApiClient>;
service = new ProtestService(mockApiClient);
});
describe('getLeagueProtests', () => {
it('should call apiClient.getLeagueProtests and return transformed data', async () => {
const leagueId = 'league-123';
const mockDto = {
protests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Contact at turn 1' },
filedAt: '2023-10-01T10:00:00Z',
status: 'pending',
},
],
racesById: { 'race-1': { id: 'race-1', track: 'Monza' } },
driversById: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
},
};
mockApiClient.getLeagueProtests.mockResolvedValue(mockDto as any);
const result = await service.getLeagueProtests(leagueId);
expect(mockApiClient.getLeagueProtests).toHaveBeenCalledWith(leagueId);
expect(result.protests).toHaveLength(1);
expect(result.protests[0]).toBeInstanceOf(ProtestViewModel);
expect(result.racesById).toEqual(mockDto.racesById);
expect(result.driversById).toEqual(mockDto.driversById);
});
it('should throw error when apiClient.getLeagueProtests fails', async () => {
const leagueId = 'league-123';
const error = new Error('API call failed');
mockApiClient.getLeagueProtests.mockRejectedValue(error);
await expect(service.getLeagueProtests(leagueId)).rejects.toThrow('API call failed');
});
});
describe('getProtestById', () => {
it('should call apiClient.getLeagueProtest and return transformed data', async () => {
const leagueId = 'league-123';
const protestId = 'protest-1';
const mockDto = {
protests: [
{
id: protestId,
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: { lap: 5, description: 'Contact at turn 1' },
filedAt: '2023-10-01T10:00:00Z',
status: 'pending',
},
],
racesById: { 'race-1': { id: 'race-1', track: 'Monza', scheduledAt: '2023-10-01T10:00:00Z' } },
driversById: {
'driver-1': { id: 'driver-1', name: 'Driver 1', avatarUrl: 'https://example.com/avatar1.jpg' },
'driver-2': { id: 'driver-2', name: 'Driver 2', avatarUrl: 'https://example.com/avatar2.jpg' },
},
};
mockApiClient.getLeagueProtest.mockResolvedValue(mockDto as any);
const result = await service.getProtestById(leagueId, protestId);
expect(mockApiClient.getLeagueProtest).toHaveBeenCalledWith(leagueId, protestId);
expect(result).not.toBeNull();
expect(result?.protest).toBeInstanceOf(ProtestViewModel);
expect(result?.race).toBeInstanceOf(RaceViewModel);
expect(result?.protestingDriver).toBeInstanceOf(ProtestDriverViewModel);
expect(result?.accusedDriver).toBeInstanceOf(ProtestDriverViewModel);
});
it('should return null when protest not found', async () => {
const leagueId = 'league-123';
const protestId = 'protest-999';
const mockDto = {
protests: [],
racesById: {},
driversById: {},
};
mockApiClient.getLeagueProtest.mockResolvedValue(mockDto);
const result = await service.getProtestById(leagueId, protestId);
expect(result).toBeNull();
});
it('should throw error when apiClient.getLeagueProtest fails', async () => {
const leagueId = 'league-123';
const protestId = 'protest-1';
const error = new Error('API call failed');
mockApiClient.getLeagueProtest.mockRejectedValue(error);
await expect(service.getProtestById(leagueId, protestId)).rejects.toThrow('API call failed');
});
});
describe('applyPenalty', () => {
it('should call apiClient.applyPenalty', async () => {
const input = {
protestId: 'protest-123',
driverId: 'driver-456',
type: 'time',
value: 10,
reason: 'Test reason',
};
mockApiClient.applyPenalty.mockResolvedValue(undefined);
await service.applyPenalty(input as any);
expect(mockApiClient.applyPenalty).toHaveBeenCalledWith(input);
});
});
describe('requestDefense', () => {
it('should call apiClient.requestDefense', async () => {
const input = {
protestId: 'protest-123',
driverId: 'driver-456',
message: 'Please provide defense',
};
mockApiClient.requestDefense.mockResolvedValue(undefined);
await service.requestDefense(input as any);
expect(mockApiClient.requestDefense).toHaveBeenCalledWith(input);
});
});
describe('reviewProtest', () => {
it('should call apiClient.reviewProtest', async () => {
const input = {
protestId: 'protest-123',
stewardId: 'steward-456',
decision: 'uphold',
decisionNotes: 'Test notes',
};
mockApiClient.reviewProtest.mockResolvedValue(undefined);
await service.reviewProtest(input);
expect(mockApiClient.reviewProtest).toHaveBeenCalledWith(input);
});
});
describe('findByRaceId', () => {
it('should call apiClient.getRaceProtests and return protests array', async () => {
const raceId = 'race-123';
const mockDto = {
protests: [
{ id: 'protest-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', status: 'pending' },
{ id: 'protest-2', protestingDriverId: 'driver-3', accusedDriverId: 'driver-4', status: 'resolved' },
],
driverMap: {},
};
mockApiClient.getRaceProtests.mockResolvedValue(mockDto as any);
const result = await service.findByRaceId(raceId);
expect(mockApiClient.getRaceProtests).toHaveBeenCalledWith(raceId);
expect(result).toEqual(mockDto.protests);
expect(result).toHaveLength(2);
});
it('should throw error when apiClient.getRaceProtests fails', async () => {
const raceId = 'race-123';
const error = new Error('API call failed');
mockApiClient.getRaceProtests.mockRejectedValue(error);
await expect(service.findByRaceId(raceId)).rejects.toThrow('API call failed');
});
});
});