208 lines
7.0 KiB
TypeScript
208 lines
7.0 KiB
TypeScript
import { describe, it, expect, vi, Mocked } from 'vitest';
|
|
import { ProtestService } from './ProtestService';
|
|
import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient';
|
|
import { ProtestViewModel } from '../../view-models/ProtestViewModel';
|
|
import { RaceViewModel } from '../../view-models/RaceViewModel';
|
|
import { ProtestDriverViewModel } from '../../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: 'upheld',
|
|
decisionNotes: 'Test notes',
|
|
};
|
|
|
|
mockApiClient.reviewProtest.mockResolvedValue(undefined);
|
|
|
|
await service.reviewProtest(input);
|
|
|
|
expect(mockApiClient.reviewProtest).toHaveBeenCalledWith({
|
|
...input,
|
|
enum: 'uphold',
|
|
});
|
|
});
|
|
});
|
|
|
|
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');
|
|
});
|
|
});
|
|
}); |