view models
This commit is contained in:
@@ -1,256 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { RaceResultsService } from './RaceResultsService';
|
||||
import { RacesApiClient } from '../../api/races/RacesApiClient';
|
||||
import { RaceResultsDetailPresenter } from '../../presenters/RaceResultsDetailPresenter';
|
||||
import { RaceWithSOFPresenter } from '../../presenters/RaceWithSOFPresenter';
|
||||
import { ImportRaceResultsPresenter } from '../../presenters/ImportRaceResultsPresenter';
|
||||
import type { RaceResultsDetailDto, RaceWithSOFDto, ImportRaceResultsSummaryDto } from '../../dtos';
|
||||
import type { RaceResultsDetailViewModel } from '../../view-models/RaceResultsDetailViewModel';
|
||||
import type { RaceWithSOFViewModel } from '../../presenters/RaceWithSOFPresenter';
|
||||
import type { ImportRaceResultsSummaryViewModel } from '../../presenters/ImportRaceResultsPresenter';
|
||||
|
||||
describe('RaceResultsService', () => {
|
||||
let service: RaceResultsService;
|
||||
let mockApiClient: RacesApiClient;
|
||||
let mockResultsDetailPresenter: RaceResultsDetailPresenter;
|
||||
let mockSOFPresenter: RaceWithSOFPresenter;
|
||||
let mockImportPresenter: ImportRaceResultsPresenter;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiClient = {
|
||||
getResultsDetail: vi.fn(),
|
||||
getWithSOF: vi.fn(),
|
||||
importResults: vi.fn(),
|
||||
} as unknown as RacesApiClient;
|
||||
|
||||
mockResultsDetailPresenter = {
|
||||
present: vi.fn(),
|
||||
} as unknown as RaceResultsDetailPresenter;
|
||||
|
||||
mockSOFPresenter = {
|
||||
present: vi.fn(),
|
||||
} as unknown as RaceWithSOFPresenter;
|
||||
|
||||
mockImportPresenter = {
|
||||
present: vi.fn(),
|
||||
} as unknown as ImportRaceResultsPresenter;
|
||||
|
||||
service = new RaceResultsService(
|
||||
mockApiClient,
|
||||
mockResultsDetailPresenter,
|
||||
mockSOFPresenter,
|
||||
mockImportPresenter
|
||||
);
|
||||
});
|
||||
|
||||
describe('getResultsDetail', () => {
|
||||
it('should fetch race results detail and transform via presenter', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const currentUserId = 'user-456';
|
||||
const mockDto: Partial<RaceResultsDetailDto> = {
|
||||
raceId,
|
||||
results: [],
|
||||
};
|
||||
const mockViewModel: Partial<RaceResultsDetailViewModel> = {
|
||||
raceId,
|
||||
results: [],
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.getResultsDetail).mockResolvedValue(mockDto as RaceResultsDetailDto);
|
||||
vi.mocked(mockResultsDetailPresenter.present).mockReturnValue(mockViewModel as RaceResultsDetailViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.getResultsDetail(raceId, currentUserId);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.getResultsDetail).toHaveBeenCalledWith(raceId);
|
||||
expect(mockResultsDetailPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
});
|
||||
|
||||
it('should fetch race results detail without currentUserId', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const mockDto: Partial<RaceResultsDetailDto> = {
|
||||
raceId,
|
||||
results: [],
|
||||
};
|
||||
const mockViewModel: Partial<RaceResultsDetailViewModel> = {
|
||||
raceId,
|
||||
results: [],
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.getResultsDetail).mockResolvedValue(mockDto as RaceResultsDetailDto);
|
||||
vi.mocked(mockResultsDetailPresenter.present).mockReturnValue(mockViewModel as RaceResultsDetailViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.getResultsDetail(raceId);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.getResultsDetail).toHaveBeenCalledWith(raceId);
|
||||
expect(mockResultsDetailPresenter.present).toHaveBeenCalledWith(mockDto, undefined);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
});
|
||||
|
||||
it('should propagate errors from API client', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const error = new Error('API Error');
|
||||
vi.mocked(mockApiClient.getResultsDetail).mockRejectedValue(error);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.getResultsDetail(raceId)).rejects.toThrow('API Error');
|
||||
expect(mockApiClient.getResultsDetail).toHaveBeenCalledWith(raceId);
|
||||
expect(mockResultsDetailPresenter.present).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWithSOF', () => {
|
||||
it('should fetch race with SOF and transform via presenter', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const mockDto: RaceWithSOFDto = {
|
||||
id: raceId,
|
||||
track: 'Spa-Francorchamps',
|
||||
strengthOfField: 2500,
|
||||
};
|
||||
const mockViewModel: RaceWithSOFViewModel = {
|
||||
id: raceId,
|
||||
track: 'Spa-Francorchamps',
|
||||
strengthOfField: 2500,
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.getWithSOF).mockResolvedValue(mockDto);
|
||||
vi.mocked(mockSOFPresenter.present).mockReturnValue(mockViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.getWithSOF(raceId);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.getWithSOF).toHaveBeenCalledWith(raceId);
|
||||
expect(mockSOFPresenter.present).toHaveBeenCalledWith(mockDto);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
});
|
||||
|
||||
it('should handle null strengthOfField', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const mockDto: RaceWithSOFDto = {
|
||||
id: raceId,
|
||||
track: 'Spa-Francorchamps',
|
||||
strengthOfField: null,
|
||||
};
|
||||
const mockViewModel: RaceWithSOFViewModel = {
|
||||
id: raceId,
|
||||
track: 'Spa-Francorchamps',
|
||||
strengthOfField: null,
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.getWithSOF).mockResolvedValue(mockDto);
|
||||
vi.mocked(mockSOFPresenter.present).mockReturnValue(mockViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.getWithSOF(raceId);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.getWithSOF).toHaveBeenCalledWith(raceId);
|
||||
expect(mockSOFPresenter.present).toHaveBeenCalledWith(mockDto);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
});
|
||||
|
||||
it('should propagate errors from API client', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const error = new Error('SOF calculation failed');
|
||||
vi.mocked(mockApiClient.getWithSOF).mockRejectedValue(error);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.getWithSOF(raceId)).rejects.toThrow('SOF calculation failed');
|
||||
expect(mockApiClient.getWithSOF).toHaveBeenCalledWith(raceId);
|
||||
expect(mockSOFPresenter.present).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('importResults', () => {
|
||||
it('should import race results and transform via presenter', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const input = {
|
||||
sessionId: 'session-456',
|
||||
results: [
|
||||
{ position: 1, driverId: 'driver-1', finishTime: 120000 },
|
||||
{ position: 2, driverId: 'driver-2', finishTime: 121000 },
|
||||
],
|
||||
};
|
||||
const mockDto: ImportRaceResultsSummaryDto = {
|
||||
success: true,
|
||||
raceId,
|
||||
driversProcessed: 2,
|
||||
resultsRecorded: 2,
|
||||
};
|
||||
const mockViewModel: ImportRaceResultsSummaryViewModel = {
|
||||
success: true,
|
||||
raceId,
|
||||
driversProcessed: 2,
|
||||
resultsRecorded: 2,
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.importResults).mockResolvedValue(mockDto);
|
||||
vi.mocked(mockImportPresenter.present).mockReturnValue(mockViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.importResults(raceId, input);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.importResults).toHaveBeenCalledWith(raceId, input);
|
||||
expect(mockImportPresenter.present).toHaveBeenCalledWith(mockDto);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
});
|
||||
|
||||
it('should handle import with errors', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const input = { sessionId: 'session-456', results: [] };
|
||||
const mockDto: ImportRaceResultsSummaryDto = {
|
||||
success: false,
|
||||
raceId,
|
||||
driversProcessed: 5,
|
||||
resultsRecorded: 3,
|
||||
errors: ['Driver not found: driver-99', 'Invalid time for driver-88'],
|
||||
};
|
||||
const mockViewModel: ImportRaceResultsSummaryViewModel = {
|
||||
success: false,
|
||||
raceId,
|
||||
driversProcessed: 5,
|
||||
resultsRecorded: 3,
|
||||
errors: ['Driver not found: driver-99', 'Invalid time for driver-88'],
|
||||
};
|
||||
|
||||
vi.mocked(mockApiClient.importResults).mockResolvedValue(mockDto);
|
||||
vi.mocked(mockImportPresenter.present).mockReturnValue(mockViewModel);
|
||||
|
||||
// Act
|
||||
const result = await service.importResults(raceId, input);
|
||||
|
||||
// Assert
|
||||
expect(mockApiClient.importResults).toHaveBeenCalledWith(raceId, input);
|
||||
expect(mockImportPresenter.present).toHaveBeenCalledWith(mockDto);
|
||||
expect(result).toEqual(mockViewModel);
|
||||
expect(result.errors).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should propagate errors from API client', async () => {
|
||||
// Arrange
|
||||
const raceId = 'race-123';
|
||||
const input = { sessionId: 'session-456', results: [] };
|
||||
const error = new Error('Import failed');
|
||||
vi.mocked(mockApiClient.importResults).mockRejectedValue(error);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.importResults(raceId, input)).rejects.toThrow('Import failed');
|
||||
expect(mockApiClient.importResults).toHaveBeenCalledWith(raceId, input);
|
||||
expect(mockImportPresenter.present).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user