261 lines
7.7 KiB
TypeScript
261 lines
7.7 KiB
TypeScript
import { TeamDrivingRaceFactsDto } from '@core/racing/domain/services/TeamDrivingRatingEventFactory';
|
|
import type { Logger } from '@core/shared/domain/Logger';
|
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
import type { TeamRaceResultsProvider } from '../ports/TeamRaceResultsProvider';
|
|
import { TeamRatingFactoryUseCase } from './TeamRatingFactoryUseCase';
|
|
|
|
// Mock provider
|
|
class MockTeamRaceResultsProvider implements TeamRaceResultsProvider {
|
|
private results: TeamDrivingRaceFactsDto | null = null;
|
|
|
|
async getTeamRaceResults(): Promise<TeamDrivingRaceFactsDto | null> {
|
|
return this.results;
|
|
}
|
|
|
|
setResults(results: TeamDrivingRaceFactsDto | null) {
|
|
this.results = results;
|
|
}
|
|
}
|
|
|
|
// Mock logger
|
|
const mockLogger: Logger = {
|
|
info: () => {},
|
|
error: () => {},
|
|
warn: () => {},
|
|
debug: () => {},
|
|
};
|
|
|
|
describe('TeamRatingFactoryUseCase', () => {
|
|
let useCase: TeamRatingFactoryUseCase;
|
|
let mockResultsProvider: MockTeamRaceResultsProvider;
|
|
|
|
beforeEach(() => {
|
|
mockResultsProvider = new MockTeamRaceResultsProvider();
|
|
useCase = new TeamRatingFactoryUseCase(mockResultsProvider, mockLogger);
|
|
});
|
|
|
|
describe('execute', () => {
|
|
it('should return error when race results not found', async () => {
|
|
mockResultsProvider.setResults(null);
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('Team race results not found');
|
|
expect(result.events).toEqual([]);
|
|
});
|
|
|
|
it('should return success with no events when results are empty', async () => {
|
|
mockResultsProvider.setResults({
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [],
|
|
});
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.events).toEqual([]);
|
|
expect(result.errors).toEqual([]);
|
|
});
|
|
|
|
it('should create events for single team', async () => {
|
|
const raceResults: TeamDrivingRaceFactsDto = {
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [
|
|
{
|
|
teamId: 'team-123',
|
|
position: 1,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
fieldSize: 3,
|
|
strengthOfField: 55,
|
|
},
|
|
],
|
|
};
|
|
|
|
mockResultsProvider.setResults(raceResults);
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.events.length).toBeGreaterThan(0);
|
|
expect(result.errors).toEqual([]);
|
|
|
|
// Verify events have correct structure
|
|
const event = result.events[0];
|
|
expect(event.teamId).toBe('team-123');
|
|
expect(event.source.type).toBe('race');
|
|
expect(event.source.id).toBe('race-123');
|
|
});
|
|
|
|
it('should create events for multiple teams', async () => {
|
|
const raceResults: TeamDrivingRaceFactsDto = {
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [
|
|
{
|
|
teamId: 'team-123',
|
|
position: 1,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
fieldSize: 3,
|
|
strengthOfField: 55,
|
|
},
|
|
{
|
|
teamId: 'team-456',
|
|
position: 2,
|
|
incidents: 1,
|
|
status: 'finished',
|
|
fieldSize: 3,
|
|
strengthOfField: 55,
|
|
},
|
|
],
|
|
};
|
|
|
|
mockResultsProvider.setResults(raceResults);
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.events.length).toBeGreaterThan(0);
|
|
|
|
// Should have events for both teams
|
|
const team123Events = result.events.filter(e => e.teamId === 'team-123');
|
|
const team456Events = result.events.filter(e => e.teamId === 'team-456');
|
|
|
|
expect(team123Events.length).toBeGreaterThan(0);
|
|
expect(team456Events.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle optional ratings in results', async () => {
|
|
const raceResults: TeamDrivingRaceFactsDto = {
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [
|
|
{
|
|
teamId: 'team-123',
|
|
position: 1,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
fieldSize: 3,
|
|
strengthOfField: 65,
|
|
pace: 85,
|
|
consistency: 80,
|
|
teamwork: 90,
|
|
sportsmanship: 95,
|
|
},
|
|
],
|
|
};
|
|
|
|
mockResultsProvider.setResults(raceResults);
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.events.length).toBeGreaterThan(5); // Should have many events
|
|
|
|
// Verify events include optional rating events
|
|
const reasonCodes = result.events.map(e => e.reason.code);
|
|
expect(reasonCodes).toContain('RACE_PACE');
|
|
expect(reasonCodes).toContain('RACE_CONSISTENCY');
|
|
expect(reasonCodes).toContain('RACE_TEAMWORK');
|
|
expect(reasonCodes).toContain('RACE_SPORTSMANSHIP');
|
|
});
|
|
|
|
it('should handle repository errors', async () => {
|
|
const raceResults: TeamDrivingRaceFactsDto = {
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [
|
|
{
|
|
teamId: 'team-123',
|
|
position: 1,
|
|
incidents: 0,
|
|
status: 'finished',
|
|
fieldSize: 1,
|
|
strengthOfField: 55,
|
|
},
|
|
],
|
|
};
|
|
|
|
mockResultsProvider.setResults(raceResults);
|
|
|
|
// Mock provider to throw error
|
|
mockResultsProvider.getTeamRaceResults = async () => {
|
|
throw new Error('Provider error');
|
|
};
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
expect(result.errors[0]).toContain('Provider error');
|
|
expect(result.events).toEqual([]);
|
|
});
|
|
|
|
it('should handle race with minimal events generated', async () => {
|
|
const raceResults: TeamDrivingRaceFactsDto = {
|
|
raceId: 'race-123',
|
|
teamId: 'team-123',
|
|
results: [
|
|
{
|
|
teamId: 'team-123',
|
|
position: 1,
|
|
incidents: 0,
|
|
status: 'dns',
|
|
fieldSize: 1,
|
|
strengthOfField: 50,
|
|
},
|
|
],
|
|
};
|
|
|
|
mockResultsProvider.setResults(raceResults);
|
|
|
|
const result = await useCase.execute({ raceId: 'race-123' });
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.events.length).toBeGreaterThan(0); // DNS creates penalty event
|
|
});
|
|
});
|
|
|
|
describe('createManualEvents', () => {
|
|
it('should create manual events with source ID', () => {
|
|
const events = useCase.createManualEvents(
|
|
'team-123',
|
|
'driving',
|
|
5,
|
|
'MANUAL_ADJUSTMENT',
|
|
'manualAdjustment',
|
|
'adjustment-123'
|
|
);
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0].teamId).toBe('team-123');
|
|
expect(events[0].dimension.value).toBe('driving');
|
|
expect(events[0].delta.value).toBe(5);
|
|
expect(events[0].reason.code).toBe('MANUAL_ADJUSTMENT');
|
|
expect(events[0].source.type).toBe('manualAdjustment');
|
|
expect(events[0].source.id).toBe('adjustment-123');
|
|
});
|
|
|
|
it('should create manual events without source ID', () => {
|
|
const events = useCase.createManualEvents(
|
|
'team-456',
|
|
'adminTrust',
|
|
-3,
|
|
'PENALTY',
|
|
'penalty'
|
|
);
|
|
|
|
expect(events).toHaveLength(1);
|
|
expect(events[0].teamId).toBe('team-456');
|
|
expect(events[0].dimension.value).toBe('adminTrust');
|
|
expect(events[0].delta.value).toBe(-3);
|
|
expect(events[0].reason.code).toBe('PENALTY');
|
|
expect(events[0].source.type).toBe('penalty');
|
|
expect(events[0].source.id).toBeUndefined();
|
|
});
|
|
});
|
|
}); |