import { AppendTeamRatingEventsUseCase } from './AppendTeamRatingEventsUseCase'; import { ITeamRatingEventRepository } from '@core/racing/domain/repositories/ITeamRatingEventRepository'; import { ITeamRatingRepository } from '@core/racing/domain/repositories/ITeamRatingRepository'; 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 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('AppendTeamRatingEventsUseCase', () => { let useCase: AppendTeamRatingEventsUseCase; let mockEventRepo: MockTeamRatingEventRepository; let mockRatingRepo: MockTeamRatingRepository; beforeEach(() => { mockEventRepo = new MockTeamRatingEventRepository(); mockRatingRepo = new MockTeamRatingRepository(); useCase = new AppendTeamRatingEventsUseCase(mockEventRepo, mockRatingRepo); }); afterEach(() => { mockEventRepo.clear(); mockRatingRepo.clear(); }); describe('execute', () => { it('should do nothing when no events provided', async () => { await useCase.execute([]); const events = await mockEventRepo.getAllByTeamId('team-123'); expect(events.length).toBe(0); }); it('should save single event and update snapshot', async () => { const event = TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-456' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }); await useCase.execute([event]); // Check event was saved const savedEvents = await mockEventRepo.getAllByTeamId('team-123'); expect(savedEvents.length).toBe(1); expect(savedEvents[0]!.id.equals(event.id)).toBe(true); // Check snapshot was updated const snapshot = await mockRatingRepo.findByTeamId('team-123'); expect(snapshot).toBeDefined(); expect(snapshot.teamId).toBe('team-123'); expect(snapshot.driving.value).toBe(60); // 50 + 10 }); it('should save multiple events for same team and update snapshot', async () => { const event1 = TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-456' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }); const event2 = TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(5), occurredAt: new Date('2024-01-01T11:00:00Z'), createdAt: new Date('2024-01-01T11:00:00Z'), source: { type: 'race', id: 'race-457' }, reason: { code: 'RACE_FINISH', description: 'Finished 2nd' }, visibility: { public: true }, version: 1, }); await useCase.execute([event1, event2]); // Check both events were saved const savedEvents = await mockEventRepo.getAllByTeamId('team-123'); expect(savedEvents.length).toBe(2); // Check snapshot was updated with weighted average const snapshot = await mockRatingRepo.findByTeamId('team-123'); expect(snapshot).toBeDefined(); expect(snapshot.teamId).toBe('team-123'); // Should be 50 + weighted average of (10, 5) = 50 + 7.5 = 57.5 expect(snapshot.driving.value).toBe(57.5); }); it('should handle multiple teams in one batch', async () => { const event1 = TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-456' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }); const event2 = TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-456', dimension: TeamRatingDimensionKey.create('adminTrust'), delta: TeamRatingDelta.create(5), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'adminAction', id: 'action-789' }, reason: { code: 'POSITIVE_ADMIN_ACTION', description: 'Helped organize event' }, visibility: { public: true }, version: 1, }); await useCase.execute([event1, event2]); // Check both team snapshots were updated const snapshot1 = await mockRatingRepo.findByTeamId('team-123'); const snapshot2 = await mockRatingRepo.findByTeamId('team-456'); expect(snapshot1).toBeDefined(); expect(snapshot1.driving.value).toBe(60); expect(snapshot2).toBeDefined(); expect(snapshot2.adminTrust.value).toBe(55); }); }); });