import { describe, it, expect, vi, beforeEach } from 'vitest'; import { DriverRegistrationService } from './DriverRegistrationService'; import type { DriversApiClient } from '../../api/drivers/DriversApiClient'; import type { DriverRegistrationStatusPresenter } from '../../presenters/DriverRegistrationStatusPresenter'; import type { DriverRegistrationStatusDto } from '../../dtos'; import type { DriverRegistrationStatusViewModel } from '../../view-models'; describe('DriverRegistrationService', () => { let service: DriverRegistrationService; let mockApiClient: DriversApiClient; let mockStatusPresenter: DriverRegistrationStatusPresenter; beforeEach(() => { mockApiClient = { getRegistrationStatus: vi.fn(), } as unknown as DriversApiClient; mockStatusPresenter = { present: vi.fn(), } as unknown as DriverRegistrationStatusPresenter; service = new DriverRegistrationService( mockApiClient, mockStatusPresenter ); }); describe('constructor', () => { it('should create instance with injected dependencies', () => { expect(service).toBeInstanceOf(DriverRegistrationService); }); }); describe('getDriverRegistrationStatus', () => { it('should fetch registration status from API and transform via presenter', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const mockDto: DriverRegistrationStatusDto = { isRegistered: true, raceId: 'race-456', driverId: 'driver-123', }; const mockViewModel = { isRegistered: true, raceId: 'race-456', driverId: 'driver-123', statusMessage: 'Registered for this race', statusColor: 'green', statusBadgeVariant: 'success', registrationButtonText: 'Withdraw', canRegister: false, } as DriverRegistrationStatusViewModel; vi.mocked(mockApiClient.getRegistrationStatus).mockResolvedValue(mockDto); vi.mocked(mockStatusPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getDriverRegistrationStatus(driverId, raceId); // Assert expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); expect(mockStatusPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should handle unregistered driver status', async () => { // Arrange const driverId = 'driver-789'; const raceId = 'race-101'; const mockDto: DriverRegistrationStatusDto = { isRegistered: false, raceId: 'race-101', driverId: 'driver-789', }; const mockViewModel = { isRegistered: false, raceId: 'race-101', driverId: 'driver-789', statusMessage: 'Not registered', statusColor: 'red', statusBadgeVariant: 'warning', registrationButtonText: 'Register', canRegister: true, } as DriverRegistrationStatusViewModel; vi.mocked(mockApiClient.getRegistrationStatus).mockResolvedValue(mockDto); vi.mocked(mockStatusPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getDriverRegistrationStatus(driverId, raceId); // Assert expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); expect(mockStatusPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result.canRegister).toBe(true); }); it('should propagate errors from API client', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const error = new Error('Failed to fetch registration status'); vi.mocked(mockApiClient.getRegistrationStatus).mockRejectedValue(error); // Act & Assert await expect(service.getDriverRegistrationStatus(driverId, raceId)) .rejects.toThrow('Failed to fetch registration status'); expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); expect(mockStatusPresenter.present).not.toHaveBeenCalled(); }); it('should handle network errors gracefully', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const networkError = new Error('Network request failed'); vi.mocked(mockApiClient.getRegistrationStatus).mockRejectedValue(networkError); // Act & Assert await expect(service.getDriverRegistrationStatus(driverId, raceId)) .rejects.toThrow('Network request failed'); expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); }); it('should handle API errors with proper error propagation', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const apiError = new Error('Race not found'); vi.mocked(mockApiClient.getRegistrationStatus).mockRejectedValue(apiError); // Act & Assert await expect(service.getDriverRegistrationStatus(driverId, raceId)) .rejects.toThrow('Race not found'); expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); expect(mockStatusPresenter.present).not.toHaveBeenCalled(); }); it('should handle multiple consecutive calls correctly', async () => { // Arrange const driverId = 'driver-123'; const raceId1 = 'race-456'; const raceId2 = 'race-789'; const mockDto1: DriverRegistrationStatusDto = { isRegistered: true, raceId: raceId1, driverId, }; const mockDto2: DriverRegistrationStatusDto = { isRegistered: false, raceId: raceId2, driverId, }; const mockViewModel1 = { isRegistered: true, raceId: raceId1, driverId, } as DriverRegistrationStatusViewModel; const mockViewModel2 = { isRegistered: false, raceId: raceId2, driverId, } as DriverRegistrationStatusViewModel; vi.mocked(mockApiClient.getRegistrationStatus) .mockResolvedValueOnce(mockDto1) .mockResolvedValueOnce(mockDto2); vi.mocked(mockStatusPresenter.present) .mockReturnValueOnce(mockViewModel1) .mockReturnValueOnce(mockViewModel2); // Act const result1 = await service.getDriverRegistrationStatus(driverId, raceId1); const result2 = await service.getDriverRegistrationStatus(driverId, raceId2); // Assert expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledTimes(2); expect(mockApiClient.getRegistrationStatus).toHaveBeenNthCalledWith(1, driverId, raceId1); expect(mockApiClient.getRegistrationStatus).toHaveBeenNthCalledWith(2, driverId, raceId2); expect(result1.isRegistered).toBe(true); expect(result2.isRegistered).toBe(false); }); it('should handle different driver IDs for same race', async () => { // Arrange const driverId1 = 'driver-123'; const driverId2 = 'driver-456'; const raceId = 'race-789'; const mockDto1: DriverRegistrationStatusDto = { isRegistered: true, raceId, driverId: driverId1, }; const mockDto2: DriverRegistrationStatusDto = { isRegistered: false, raceId, driverId: driverId2, }; const mockViewModel1 = { isRegistered: true, raceId, driverId: driverId1, } as DriverRegistrationStatusViewModel; const mockViewModel2 = { isRegistered: false, raceId, driverId: driverId2, } as DriverRegistrationStatusViewModel; vi.mocked(mockApiClient.getRegistrationStatus) .mockResolvedValueOnce(mockDto1) .mockResolvedValueOnce(mockDto2); vi.mocked(mockStatusPresenter.present) .mockReturnValueOnce(mockViewModel1) .mockReturnValueOnce(mockViewModel2); // Act const result1 = await service.getDriverRegistrationStatus(driverId1, raceId); const result2 = await service.getDriverRegistrationStatus(driverId2, raceId); // Assert expect(result1.driverId).toBe(driverId1); expect(result1.isRegistered).toBe(true); expect(result2.driverId).toBe(driverId2); expect(result2.isRegistered).toBe(false); }); it('should handle unauthorized access errors', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const authError = new Error('Unauthorized: Driver not found'); vi.mocked(mockApiClient.getRegistrationStatus).mockRejectedValue(authError); // Act & Assert await expect(service.getDriverRegistrationStatus(driverId, raceId)) .rejects.toThrow('Unauthorized: Driver not found'); expect(mockApiClient.getRegistrationStatus).toHaveBeenCalledWith(driverId, raceId); expect(mockStatusPresenter.present).not.toHaveBeenCalled(); }); it('should call presenter only after successful API response', async () => { // Arrange const driverId = 'driver-123'; const raceId = 'race-456'; const mockDto: DriverRegistrationStatusDto = { isRegistered: true, raceId: 'race-456', driverId: 'driver-123', }; const mockViewModel = { isRegistered: true, raceId: 'race-456', driverId: 'driver-123', } as DriverRegistrationStatusViewModel; vi.mocked(mockApiClient.getRegistrationStatus).mockResolvedValue(mockDto); vi.mocked(mockStatusPresenter.present).mockReturnValue(mockViewModel); // Act await service.getDriverRegistrationStatus(driverId, raceId); // Assert - verify call order expect(mockApiClient.getRegistrationStatus).toHaveBeenCalled(); expect(mockStatusPresenter.present).toHaveBeenCalledAfter( mockApiClient.getRegistrationStatus as any ); }); }); });