This commit is contained in:
2026-01-08 17:36:08 +01:00
parent 66ec6fe727
commit 05cf3bafd2
23 changed files with 3004 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
import { AllRacesPageDataPresenter } from './AllRacesPageDataPresenter';
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
describe('AllRacesPageDataPresenter', () => {
let presenter: AllRacesPageDataPresenter;
beforeEach(() => {
presenter = new AllRacesPageDataPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result: GetAllRacesPageDataResult = {
races: [
{
id: 'race-1',
track: 'Track A',
car: 'Car A',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
leagueId: 'league-1',
leagueName: 'League A',
strengthOfField: 1500,
},
{
id: 'race-2',
track: 'Track B',
car: 'Car B',
scheduledAt: '2024-01-02T10:00:00Z',
status: 'completed',
leagueId: 'league-2',
leagueName: 'League B',
strengthOfField: null,
},
],
filters: {
statuses: [
{ value: 'all', label: 'All Statuses' },
{ value: 'scheduled', label: 'Scheduled' },
],
leagues: [
{ id: 'league-1', name: 'League A' },
{ id: 'league-2', name: 'League B' },
],
},
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
races: [
{
id: 'race-1',
track: 'Track A',
car: 'Car A',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
leagueId: 'league-1',
leagueName: 'League A',
strengthOfField: 1500,
},
{
id: 'race-2',
track: 'Track B',
car: 'Car B',
scheduledAt: '2024-01-02T10:00:00Z',
status: 'completed',
leagueId: 'league-2',
leagueName: 'League B',
strengthOfField: null,
},
],
filters: {
statuses: [
{ value: 'all', label: 'All Statuses' },
{ value: 'scheduled', label: 'Scheduled' },
],
leagues: [
{ id: 'league-1', name: 'League A' },
{ id: 'league-2', name: 'League B' },
],
},
});
});
it('should handle empty races', () => {
const result: GetAllRacesPageDataResult = {
races: [],
filters: {
statuses: [{ value: 'all', label: 'All Statuses' }],
leagues: [],
},
};
presenter.present(result);
expect(presenter.responseModel.races).toEqual([]);
expect(presenter.responseModel.filters.leagues).toEqual([]);
});
});
describe('reset', () => {
it('should clear the model', () => {
const result: GetAllRacesPageDataResult = {
races: [{ id: 'race-1', track: 'Track A', car: 'Car A', scheduledAt: '2024-01-01', status: 'scheduled', leagueId: 'league-1', leagueName: 'League A', strengthOfField: null }],
filters: { statuses: [], leagues: [] },
};
presenter.present(result);
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetAllRacesPageDataResult = {
races: [{ id: 'race-1', track: 'Track A', car: 'Car A', scheduledAt: '2024-01-01', status: 'scheduled', leagueId: 'league-1', leagueName: 'League A', strengthOfField: null }],
filters: { statuses: [], leagues: [] },
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,62 @@
import { GetTotalRacesPresenter } from './GetTotalRacesPresenter';
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
describe('GetTotalRacesPresenter', () => {
let presenter: GetTotalRacesPresenter;
beforeEach(() => {
presenter = new GetTotalRacesPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result: GetTotalRacesResult = { totalRaces: 42 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 42 });
});
it('should handle zero races', () => {
const result: GetTotalRacesResult = { totalRaces: 0 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 0 });
});
it('should handle large numbers', () => {
const result: GetTotalRacesResult = { totalRaces: 999999 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 999999 });
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({ totalRaces: 42 });
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetTotalRacesResult = { totalRaces: 42 };
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,115 @@
import { ImportRaceResultsApiPresenter } from './ImportRaceResultsApiPresenter';
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
describe('ImportRaceResultsApiPresenter', () => {
let presenter: ImportRaceResultsApiPresenter;
beforeEach(() => {
presenter = new ImportRaceResultsApiPresenter();
});
describe('present', () => {
it('should map successful result to response model', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
});
});
it('should handle result with errors', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 8,
errors: ['Driver not found: 12345', 'Driver not found: 67890'],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 10,
resultsRecorded: 8,
errors: ['Driver not found: 12345', 'Driver not found: 67890'],
});
});
it('should handle zero drivers processed', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 0,
resultsRecorded: 0,
errors: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 0,
resultsRecorded: 0,
errors: [],
});
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,159 @@
import { RacePenaltiesPresenter } from './RacePenaltiesPresenter';
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
describe('RacePenaltiesPresenter', () => {
let presenter: RacePenaltiesPresenter;
beforeEach(() => {
presenter = new RacePenaltiesPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const mockPenalty = {
id: 'penalty-123',
driverId: 'driver-456',
type: 'time_penalty',
value: 10,
reason: 'Track limits violation',
issuedBy: 'steward-789',
issuedAt: new Date('2024-01-01T10:00:00Z'),
notes: 'Multiple violations',
};
const mockDriver = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
};
const result: GetRacePenaltiesResult = {
penalties: [mockPenalty as any],
drivers: [mockDriver as any],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
penalties: [
{
id: 'penalty-123',
driverId: 'driver-456',
type: 'time_penalty',
value: 10,
reason: 'Track limits violation',
issuedBy: 'steward-789',
issuedAt: '2024-01-01T10:00:00.000Z',
notes: 'Multiple violations',
},
],
driverMap: {
'driver-456': 'John Doe',
},
});
});
it('should handle multiple penalties and drivers', () => {
const result: GetRacePenaltiesResult = {
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'time_penalty',
value: 5,
reason: 'Reason 1',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
{
id: 'penalty-2',
driverId: 'driver-2',
type: 'drive_through',
value: 0,
reason: 'Reason 2',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:05:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.penalties).toHaveLength(2);
expect(presenter.responseModel.driverMap).toEqual({
'driver-1': 'Driver One',
'driver-2': 'Driver Two',
});
});
it('should handle empty penalties', () => {
const result: GetRacePenaltiesResult = {
penalties: [],
drivers: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
penalties: [],
driverMap: {},
});
});
it('should handle penalties with undefined value', () => {
const result: GetRacePenaltiesResult = {
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'disqualification',
value: undefined,
reason: 'Reason',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
],
drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any],
};
presenter.present(result);
expect(presenter.responseModel.penalties[0]?.value).toBe(0);
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetRacePenaltiesResult = {
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,198 @@
import { RaceProtestsPresenter } from './RaceProtestsPresenter';
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
describe('RaceProtestsPresenter', () => {
let presenter: RaceProtestsPresenter;
beforeEach(() => {
presenter = new RaceProtestsPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const mockProtest = {
id: 'protest-123',
protestingDriverId: 'driver-456',
accusedDriverId: 'driver-789',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Contact at turn 3' } as any,
},
status: { toString: () => 'pending' } as any,
filedAt: new Date('2024-01-01T10:30:00Z'),
};
const mockDriver1 = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
};
const mockDriver2 = {
id: 'driver-789',
name: { toString: () => 'Jane Smith' } as any,
};
const result: GetRaceProtestsResult = {
protests: [mockProtest as any],
drivers: [mockDriver1 as any, mockDriver2 as any],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
protests: [
{
id: 'protest-123',
protestingDriverId: 'driver-456',
accusedDriverId: 'driver-789',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
status: 'pending',
filedAt: '2024-01-01T10:30:00.000Z',
},
],
driverMap: {
'driver-456': 'John Doe',
'driver-789': 'Jane Smith',
},
});
});
it('should handle multiple protests and drivers', () => {
const result: GetRaceProtestsResult = {
protests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 3 } as any,
description: { toString: () => 'Incident 1' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-1',
incident: {
lap: { toNumber: () => 7 } as any,
description: { toString: () => 'Incident 2' } as any
},
status: { toString: () => 'reviewed' } as any,
filedAt: new Date('2024-01-01T10:10:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
{ id: 'driver-3', name: { toString: () => 'Driver Three' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.protests).toHaveLength(2);
expect(presenter.responseModel.driverMap).toEqual({
'driver-1': 'Driver One',
'driver-2': 'Driver Two',
'driver-3': 'Driver Three',
});
});
it('should handle empty protests', () => {
const result: GetRaceProtestsResult = {
protests: [],
drivers: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
protests: [],
driverMap: {},
});
});
it('should handle protests with reviewed status', () => {
const result: GetRaceProtestsResult = {
protests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Test' } as any
},
status: { toString: () => 'approved' } as any,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.protests[0]?.status).toBe('approved');
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
protests: [{
id: 'p1',
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetRaceProtestsResult = {
protests: [{
id: 'p1',
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});