Files
gridpilot.gg/apps/website/lib/services/drivers/DriverRegistrationService.test.ts
2025-12-17 22:17:02 +01:00

292 lines
9.8 KiB
TypeScript

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