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; 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; 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'); }); }); });