Files
gridpilot.gg/apps/website/lib/services/races/RaceService.test.ts
2025-12-28 12:04:12 +01:00

190 lines
6.3 KiB
TypeScript

import { describe, it, expect, vi, Mocked } from 'vitest';
import { RaceService } from './RaceService';
import { RacesApiClient } from '../../api/races/RacesApiClient';
import { RaceDetailViewModel } from '../../view-models/RaceDetailViewModel';
import { RacesPageViewModel } from '../../view-models/RacesPageViewModel';
import { RaceStatsViewModel } from '../../view-models/RaceStatsViewModel';
import type { RaceDetailsViewModel } from '../../view-models/RaceDetailsViewModel';
describe('RaceService', () => {
let mockApiClient: Mocked<RacesApiClient>;
let service: RaceService;
beforeEach(() => {
mockApiClient = {
getDetail: vi.fn(),
getPageData: vi.fn(),
getTotal: vi.fn(),
register: vi.fn(),
withdraw: vi.fn(),
cancel: vi.fn(),
complete: vi.fn(),
reopen: vi.fn(),
} as unknown as Mocked<RacesApiClient>;
service = new RaceService(mockApiClient);
});
describe('getRaceDetail', () => {
it('should call apiClient.getDetail and return RaceDetailViewModel', async () => {
const raceId = 'race-123';
const driverId = 'driver-456';
const mockDto = {
race: { id: raceId, track: 'Test Track' },
league: { id: 'league-1', name: 'Test League' },
entryList: [],
registration: { isRegistered: true, canRegister: false },
userResult: null,
};
mockApiClient.getDetail.mockResolvedValue(mockDto as any);
const result = await service.getRaceDetail(raceId, driverId);
expect(mockApiClient.getDetail).toHaveBeenCalledWith(raceId, driverId);
expect(result).toBeInstanceOf(RaceDetailViewModel);
expect(result.race?.id).toBe(raceId);
});
it('should throw error when apiClient.getDetail fails', async () => {
const raceId = 'race-123';
const driverId = 'driver-456';
const error = new Error('API call failed');
mockApiClient.getDetail.mockRejectedValue(error);
await expect(service.getRaceDetail(raceId, driverId)).rejects.toThrow('API call failed');
});
});
describe('getRaceDetails', () => {
it('should call apiClient.getDetail and return a ViewModel-shaped object (no DTOs)', async () => {
const raceId = 'race-123';
const driverId = 'driver-456';
const mockDto = {
race: {
id: raceId,
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2023-12-31T20:00:00Z',
status: 'completed',
sessionType: 'race',
},
league: { id: 'league-1', name: 'Test League', description: 'Desc', settings: { maxDrivers: 32 } },
entryList: [],
registration: { isUserRegistered: true, canRegister: false },
userResult: null,
};
mockApiClient.getDetail.mockResolvedValue(mockDto as any);
const result: RaceDetailsViewModel = await service.getRaceDetails(raceId, driverId);
expect(mockApiClient.getDetail).toHaveBeenCalledWith(raceId, driverId);
expect(result.race?.id).toBe(raceId);
expect(result.league?.id).toBe('league-1');
expect(result.registration.isUserRegistered).toBe(true);
expect(result.canReopenRace).toBe(true);
});
});
describe('getRacesPageData', () => {
it('should call apiClient.getPageData and return RacesPageViewModel with transformed data', async () => {
const mockDto = {
races: [
{
id: 'race-1',
track: 'Monza',
car: 'Ferrari',
scheduledAt: '2023-10-01T10:00:00Z',
status: 'upcoming',
leagueId: 'league-1',
leagueName: 'Test League',
},
{
id: 'race-2',
track: 'Silverstone',
car: 'Mercedes',
scheduledAt: '2023-09-15T10:00:00Z',
status: 'completed',
leagueId: 'league-1',
leagueName: 'Test League',
},
],
};
mockApiClient.getPageData.mockResolvedValue(mockDto as any);
const result = await service.getRacesPageData();
expect(mockApiClient.getPageData).toHaveBeenCalled();
expect(result).toBeInstanceOf(RacesPageViewModel);
expect(result.upcomingRaces).toHaveLength(1);
expect(result.completedRaces).toHaveLength(1);
expect(result.totalCount).toBe(2);
expect(result.upcomingRaces[0].title).toBe('Monza - Ferrari');
expect(result.completedRaces[0].title).toBe('Silverstone - Mercedes');
});
it('should handle empty races array', async () => {
const mockDto = { races: [] };
mockApiClient.getPageData.mockResolvedValue(mockDto as any);
const result = await service.getRacesPageData();
expect(result.upcomingRaces).toHaveLength(0);
expect(result.completedRaces).toHaveLength(0);
expect(result.totalCount).toBe(0);
});
it('should throw error when apiClient.getPageData fails', async () => {
const error = new Error('API call failed');
mockApiClient.getPageData.mockRejectedValue(error);
await expect(service.getRacesPageData()).rejects.toThrow('API call failed');
});
});
describe('getRacesTotal', () => {
it('should call apiClient.getTotal and return RaceStatsViewModel', async () => {
const mockDto = { totalRaces: 42 };
mockApiClient.getTotal.mockResolvedValue(mockDto as any);
const result = await service.getRacesTotal();
expect(mockApiClient.getTotal).toHaveBeenCalled();
expect(result).toBeInstanceOf(RaceStatsViewModel);
expect(result.totalRaces).toBe(42);
expect(result.formattedTotalRaces).toBe('42');
});
it('should throw error when apiClient.getTotal fails', async () => {
const error = new Error('API call failed');
mockApiClient.getTotal.mockRejectedValue(error);
await expect(service.getRacesTotal()).rejects.toThrow('API call failed');
});
});
describe('reopenRace', () => {
it('should call apiClient.reopen with raceId', async () => {
const raceId = 'race-123';
await service.reopenRace(raceId);
expect(mockApiClient.reopen).toHaveBeenCalledWith(raceId);
});
it('should propagate errors from apiClient.reopen', async () => {
const raceId = 'race-123';
const error = new Error('API call failed');
mockApiClient.reopen.mockRejectedValue(error);
await expect(service.reopenRace(raceId)).rejects.toThrow('API call failed');
});
});
});