196 lines
6.9 KiB
TypeScript
196 lines
6.9 KiB
TypeScript
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<TeamRatingEvent> {
|
|
this.events.push(event);
|
|
return event;
|
|
}
|
|
|
|
async findByTeamId(teamId: string): Promise<TeamRatingEvent[]> {
|
|
return this.events.filter(e => e.teamId === teamId);
|
|
}
|
|
|
|
async findByIds(ids: TeamRatingEventId[]): Promise<TeamRatingEvent[]> {
|
|
return this.events.filter(e => ids.some(id => id.equals(e.id)));
|
|
}
|
|
|
|
async getAllByTeamId(teamId: string): Promise<TeamRatingEvent[]> {
|
|
return this.events.filter(e => e.teamId === teamId);
|
|
}
|
|
|
|
async findEventsPaginated(teamId: string): Promise<any> {
|
|
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<string, any> = new Map();
|
|
|
|
async findByTeamId(teamId: string): Promise<any | null> {
|
|
return this.snapshots.get(teamId) || null;
|
|
}
|
|
|
|
async save(snapshot: any): Promise<any> {
|
|
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);
|
|
});
|
|
});
|
|
}); |