import { RecordTeamRaceRatingEventsUseCase } from './RecordTeamRaceRatingEventsUseCase'; import { ITeamRaceResultsProvider } from '../ports/ITeamRaceResultsProvider'; import { ITeamRatingEventRepository } from '@core/racing/domain/repositories/ITeamRatingEventRepository'; import { ITeamRatingRepository } from '@core/racing/domain/repositories/ITeamRatingRepository'; import { AppendTeamRatingEventsUseCase } from './AppendTeamRatingEventsUseCase'; import { TeamDrivingRaceFactsDto } from '@core/racing/domain/services/TeamDrivingRatingEventFactory'; import { TeamRatingEvent } from '@core/racing/domain/entities/TeamRatingEvent'; import { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId'; import { TeamRatingDimensionKey } from '@core/racing/domain/value-objects/TeamRatingDimensionKey'; import { TeamRatingDelta } from '@core/racing/domain/value-objects/TeamRatingDelta'; // Mock repositories class MockTeamRaceResultsProvider implements ITeamRaceResultsProvider { private results: TeamDrivingRaceFactsDto | null = null; async getTeamRaceResults(raceId: string): Promise { return this.results; } setResults(results: TeamDrivingRaceFactsDto | null) { this.results = results; } } class MockTeamRatingEventRepository implements ITeamRatingEventRepository { private events: TeamRatingEvent[] = []; async save(event: TeamRatingEvent): Promise { this.events.push(event); return event; } async findByTeamId(teamId: string): Promise { return this.events.filter(e => e.teamId === teamId); } async findByIds(ids: TeamRatingEventId[]): Promise { return this.events.filter(e => ids.some(id => id.equals(e.id))); } async getAllByTeamId(teamId: string): Promise { return this.events.filter(e => e.teamId === teamId); } async findEventsPaginated(teamId: string): Promise { const events = await this.getAllByTeamId(teamId); return { items: events, total: events.length, limit: 10, offset: 0, hasMore: false, }; } clear() { this.events = []; } } class MockTeamRatingRepository implements ITeamRatingRepository { private snapshots: Map = new Map(); async findByTeamId(teamId: string): Promise { return this.snapshots.get(teamId) || null; } async save(snapshot: any): Promise { this.snapshots.set(snapshot.teamId, snapshot); return snapshot; } clear() { this.snapshots.clear(); } } describe('RecordTeamRaceRatingEventsUseCase', () => { let useCase: RecordTeamRaceRatingEventsUseCase; let mockResultsProvider: MockTeamRaceResultsProvider; let mockEventRepo: MockTeamRatingEventRepository; let mockRatingRepo: MockTeamRatingRepository; let appendUseCase: AppendTeamRatingEventsUseCase; beforeEach(() => { mockResultsProvider = new MockTeamRaceResultsProvider(); mockEventRepo = new MockTeamRatingEventRepository(); mockRatingRepo = new MockTeamRatingRepository(); appendUseCase = new AppendTeamRatingEventsUseCase(mockEventRepo, mockRatingRepo); useCase = new RecordTeamRaceRatingEventsUseCase( mockResultsProvider, mockEventRepo, mockRatingRepo, appendUseCase ); }); afterEach(() => { mockEventRepo.clear(); mockRatingRepo.clear(); }); 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.eventsCreated).toBe(0); expect(result.teamsUpdated).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.eventsCreated).toBe(0); expect(result.teamsUpdated).toEqual([]); expect(result.errors).toEqual([]); }); it('should create events for single team and update snapshot', 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.eventsCreated).toBeGreaterThan(0); expect(result.teamsUpdated).toContain('team-123'); expect(result.errors).toEqual([]); // Verify events were saved const savedEvents = await mockEventRepo.getAllByTeamId('team-123'); expect(savedEvents.length).toBeGreaterThan(0); // Verify snapshot was updated const snapshot = await mockRatingRepo.findByTeamId('team-123'); expect(snapshot).toBeDefined(); expect(snapshot.teamId).toBe('team-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, }, { teamId: 'team-789', position: 3, incidents: 0, status: 'dnf', fieldSize: 3, strengthOfField: 55, }, ], }; mockResultsProvider.setResults(raceResults); const result = await useCase.execute({ raceId: 'race-123' }); expect(result.success).toBe(true); expect(result.eventsCreated).toBeGreaterThan(0); expect(result.teamsUpdated).toContain('team-123'); expect(result.teamsUpdated).toContain('team-456'); expect(result.teamsUpdated).toContain('team-789'); expect(result.errors).toEqual([]); // Verify all team snapshots were updated const snapshot1 = await mockRatingRepo.findByTeamId('team-123'); const snapshot2 = await mockRatingRepo.findByTeamId('team-456'); const snapshot3 = await mockRatingRepo.findByTeamId('team-789'); expect(snapshot1).toBeDefined(); expect(snapshot2).toBeDefined(); expect(snapshot3).toBeDefined(); }); 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.eventsCreated).toBeGreaterThan(5); // Should have many events expect(result.teamsUpdated).toContain('team-123'); // Verify events include optional rating events const savedEvents = await mockEventRepo.getAllByTeamId('team-123'); const paceEvent = savedEvents.find(e => e.reason.code === 'RACE_PACE'); const consistencyEvent = savedEvents.find(e => e.reason.code === 'RACE_CONSISTENCY'); const teamworkEvent = savedEvents.find(e => e.reason.code === 'RACE_TEAMWORK'); const sportsmanshipEvent = savedEvents.find(e => e.reason.code === 'RACE_SPORTSMANSHIP'); expect(paceEvent).toBeDefined(); expect(consistencyEvent).toBeDefined(); expect(teamworkEvent).toBeDefined(); expect(sportsmanshipEvent).toBeDefined(); }); it('should handle partial failures gracefully', async () => { const raceResults: TeamDrivingRaceFactsDto = { raceId: 'race-123', teamId: 'team-123', results: [ { teamId: 'team-123', position: 1, incidents: 0, status: 'finished', fieldSize: 2, strengthOfField: 55, }, { teamId: 'team-456', position: 2, incidents: 0, status: 'finished', fieldSize: 2, strengthOfField: 55, }, ], }; mockResultsProvider.setResults(raceResults); // Mock the append use case to fail for team-456 const originalExecute = appendUseCase.execute.bind(appendUseCase); appendUseCase.execute = async (events) => { if (events.length > 0 && events[0] && events[0].teamId === 'team-456') { throw new Error('Simulated failure for team-456'); } return originalExecute(events); }; const result = await useCase.execute({ raceId: 'race-123' }); expect(result.success).toBe(false); expect(result.teamsUpdated).toContain('team-123'); expect(result.teamsUpdated).not.toContain('team-456'); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('team-456'); }); 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 repository to throw error const originalSave = mockEventRepo.save.bind(mockEventRepo); mockEventRepo.save = async () => { throw new Error('Repository 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('Repository error'); expect(result.eventsCreated).toBe(0); expect(result.teamsUpdated).toEqual([]); }); it('should handle empty results array', 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.eventsCreated).toBe(0); expect(result.teamsUpdated).toEqual([]); expect(result.errors).toEqual([]); }); it('should handle race with minimal events generated', async () => { // Race where teams have some impact (DNS creates penalty event) 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.eventsCreated).toBeGreaterThan(0); // DNS creates penalty event expect(result.teamsUpdated).toContain('team-123'); expect(result.errors).toEqual([]); }); }); });