team rating
This commit is contained in:
418
core/racing/application/use-cases/TeamRankingUseCase.test.ts
Normal file
418
core/racing/application/use-cases/TeamRankingUseCase.test.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
import { TeamRankingUseCase } from './TeamRankingUseCase';
|
||||
import type { ITeamRatingRepository } from '@core/racing/domain/repositories/ITeamRatingRepository';
|
||||
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { TeamRatingSnapshot } from '@core/racing/domain/services/TeamRatingSnapshotCalculator';
|
||||
import { TeamRatingValue } from '@core/racing/domain/value-objects/TeamRatingValue';
|
||||
import { Team } from '@core/racing/domain/entities/Team';
|
||||
|
||||
// Mock repositories
|
||||
class MockTeamRatingRepository implements ITeamRatingRepository {
|
||||
private snapshots: Map<string, TeamRatingSnapshot> = new Map();
|
||||
|
||||
async findByTeamId(teamId: string): Promise<TeamRatingSnapshot | null> {
|
||||
return this.snapshots.get(teamId) || null;
|
||||
}
|
||||
|
||||
async save(snapshot: TeamRatingSnapshot): Promise<TeamRatingSnapshot> {
|
||||
this.snapshots.set(snapshot.teamId, snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.snapshots.clear();
|
||||
}
|
||||
|
||||
setSnapshot(teamId: string, snapshot: TeamRatingSnapshot) {
|
||||
this.snapshots.set(teamId, snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
class MockTeamRepository implements ITeamRepository {
|
||||
private teams: Map<string, Team> = new Map();
|
||||
|
||||
async findById(id: string): Promise<Team | null> {
|
||||
return this.teams.get(id) || null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Team[]> {
|
||||
return Array.from(this.teams.values());
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Team[]> {
|
||||
return Array.from(this.teams.values()).filter(t =>
|
||||
t.leagues.some(l => l.toString() === leagueId)
|
||||
);
|
||||
}
|
||||
|
||||
async create(team: Team): Promise<Team> {
|
||||
this.teams.set(team.id, team);
|
||||
return team;
|
||||
}
|
||||
|
||||
async update(team: Team): Promise<Team> {
|
||||
this.teams.set(team.id, team);
|
||||
return team;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
this.teams.delete(id);
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
return this.teams.has(id);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.teams.clear();
|
||||
}
|
||||
|
||||
setTeam(team: Team) {
|
||||
this.teams.set(team.id, team);
|
||||
}
|
||||
}
|
||||
|
||||
// Mock logger
|
||||
const mockLogger: Logger = {
|
||||
info: () => {},
|
||||
error: () => {},
|
||||
warn: () => {},
|
||||
debug: () => {},
|
||||
};
|
||||
|
||||
describe('TeamRankingUseCase', () => {
|
||||
let useCase: TeamRankingUseCase;
|
||||
let mockRatingRepo: MockTeamRatingRepository;
|
||||
let mockTeamRepo: MockTeamRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRatingRepo = new MockTeamRatingRepository();
|
||||
mockTeamRepo = new MockTeamRepository();
|
||||
useCase = new TeamRankingUseCase(mockRatingRepo, mockTeamRepo, mockLogger);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockRatingRepo.clear();
|
||||
mockTeamRepo.clear();
|
||||
});
|
||||
|
||||
describe('getAllTeamRankings', () => {
|
||||
it('should return empty array when no teams exist', async () => {
|
||||
const result = await useCase.getAllTeamRankings();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when no rating snapshots exist', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team-123',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'driver-123',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
|
||||
const result = await useCase.getAllTeamRankings();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return single team ranking', async () => {
|
||||
const teamId = 'team-123';
|
||||
const team = Team.create({
|
||||
id: teamId,
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'driver-123',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
|
||||
const snapshot: TeamRatingSnapshot = {
|
||||
teamId,
|
||||
driving: TeamRatingValue.create(75),
|
||||
adminTrust: TeamRatingValue.create(80),
|
||||
overall: 76.5,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
eventCount: 5,
|
||||
};
|
||||
mockRatingRepo.setSnapshot(teamId, snapshot);
|
||||
|
||||
const result = await useCase.getAllTeamRankings();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({
|
||||
teamId,
|
||||
teamName: 'Test Team',
|
||||
drivingRating: 75,
|
||||
adminTrustRating: 80,
|
||||
overallRating: 76.5,
|
||||
eventCount: 5,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
overallRank: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return multiple teams sorted by overall rating', async () => {
|
||||
// Team 1
|
||||
const team1 = Team.create({
|
||||
id: 'team-1',
|
||||
name: 'Team Alpha',
|
||||
tag: 'TA',
|
||||
description: 'Alpha team',
|
||||
ownerId: 'driver-1',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team1);
|
||||
mockRatingRepo.setSnapshot('team-1', {
|
||||
teamId: 'team-1',
|
||||
driving: TeamRatingValue.create(80),
|
||||
adminTrust: TeamRatingValue.create(70),
|
||||
overall: 77,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
eventCount: 10,
|
||||
});
|
||||
|
||||
// Team 2
|
||||
const team2 = Team.create({
|
||||
id: 'team-2',
|
||||
name: 'Team Beta',
|
||||
tag: 'TB',
|
||||
description: 'Beta team',
|
||||
ownerId: 'driver-2',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team2);
|
||||
mockRatingRepo.setSnapshot('team-2', {
|
||||
teamId: 'team-2',
|
||||
driving: TeamRatingValue.create(90),
|
||||
adminTrust: TeamRatingValue.create(85),
|
||||
overall: 88,
|
||||
lastUpdated: new Date('2024-01-02'),
|
||||
eventCount: 15,
|
||||
});
|
||||
|
||||
// Team 3
|
||||
const team3 = Team.create({
|
||||
id: 'team-3',
|
||||
name: 'Team Gamma',
|
||||
tag: 'TG',
|
||||
description: 'Gamma team',
|
||||
ownerId: 'driver-3',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team3);
|
||||
mockRatingRepo.setSnapshot('team-3', {
|
||||
teamId: 'team-3',
|
||||
driving: TeamRatingValue.create(60),
|
||||
adminTrust: TeamRatingValue.create(65),
|
||||
overall: 61.5,
|
||||
lastUpdated: new Date('2024-01-03'),
|
||||
eventCount: 3,
|
||||
});
|
||||
|
||||
const result = await useCase.getAllTeamRankings();
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
|
||||
// Should be sorted by overall rating descending
|
||||
expect(result[0]).toBeDefined();
|
||||
expect(result[0]!.teamId).toBe('team-2');
|
||||
expect(result[0]!.overallRank).toBe(1);
|
||||
expect(result[0]!.overallRating).toBe(88);
|
||||
|
||||
expect(result[1]).toBeDefined();
|
||||
expect(result[1]!.teamId).toBe('team-1');
|
||||
expect(result[1]!.overallRank).toBe(2);
|
||||
expect(result[1]!.overallRating).toBe(77);
|
||||
|
||||
expect(result[2]).toBeDefined();
|
||||
expect(result[2]!.teamId).toBe('team-3');
|
||||
expect(result[2]!.overallRank).toBe(3);
|
||||
expect(result[2]!.overallRating).toBe(61.5);
|
||||
});
|
||||
|
||||
it('should handle teams without snapshots gracefully', async () => {
|
||||
// Team with snapshot
|
||||
const team1 = Team.create({
|
||||
id: 'team-1',
|
||||
name: 'Team With Rating',
|
||||
tag: 'TWR',
|
||||
description: 'Has rating',
|
||||
ownerId: 'driver-1',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team1);
|
||||
mockRatingRepo.setSnapshot('team-1', {
|
||||
teamId: 'team-1',
|
||||
driving: TeamRatingValue.create(70),
|
||||
adminTrust: TeamRatingValue.create(70),
|
||||
overall: 70,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
eventCount: 5,
|
||||
});
|
||||
|
||||
// Team without snapshot
|
||||
const team2 = Team.create({
|
||||
id: 'team-2',
|
||||
name: 'Team Without Rating',
|
||||
tag: 'TWR',
|
||||
description: 'No rating',
|
||||
ownerId: 'driver-2',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team2);
|
||||
|
||||
const result = await useCase.getAllTeamRankings();
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBeDefined();
|
||||
expect(result[0]!.teamId).toBe('team-1');
|
||||
expect(result[0]!.overallRank).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTeamRanking', () => {
|
||||
it('should return null when team does not exist', async () => {
|
||||
const result = await useCase.getTeamRanking('non-existent-team');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when team exists but has no snapshot', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team-123',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'driver-123',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
|
||||
const result = await useCase.getTeamRanking('team-123');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return team ranking with correct rank', async () => {
|
||||
// Setup multiple teams
|
||||
const teams = [
|
||||
{ id: 'team-1', name: 'Team A', rating: 85 },
|
||||
{ id: 'team-2', name: 'Team B', rating: 90 },
|
||||
{ id: 'team-3', name: 'Team C', rating: 75 },
|
||||
];
|
||||
|
||||
for (const t of teams) {
|
||||
const team = Team.create({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
tag: t.name.substring(0, 2).toUpperCase(),
|
||||
description: `${t.name} description`,
|
||||
ownerId: `driver-${t.id}`,
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
mockRatingRepo.setSnapshot(t.id, {
|
||||
teamId: t.id,
|
||||
driving: TeamRatingValue.create(t.rating),
|
||||
adminTrust: TeamRatingValue.create(t.rating),
|
||||
overall: t.rating,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
eventCount: 5,
|
||||
});
|
||||
}
|
||||
|
||||
// Get ranking for team-2 (should be rank 1 with rating 90)
|
||||
const result = await useCase.getTeamRanking('team-2');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.teamId).toBe('team-2');
|
||||
expect(result?.teamName).toBe('Team B');
|
||||
expect(result?.overallRating).toBe(90);
|
||||
expect(result?.overallRank).toBe(1);
|
||||
});
|
||||
|
||||
it('should calculate correct rank for middle team', async () => {
|
||||
// Setup teams
|
||||
const teams = [
|
||||
{ id: 'team-1', name: 'Team A', rating: 90 },
|
||||
{ id: 'team-2', name: 'Team B', rating: 80 },
|
||||
{ id: 'team-3', name: 'Team C', rating: 70 },
|
||||
];
|
||||
|
||||
for (const t of teams) {
|
||||
const team = Team.create({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
tag: t.name.substring(0, 2).toUpperCase(),
|
||||
description: `${t.name} description`,
|
||||
ownerId: `driver-${t.id}`,
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
mockRatingRepo.setSnapshot(t.id, {
|
||||
teamId: t.id,
|
||||
driving: TeamRatingValue.create(t.rating),
|
||||
adminTrust: TeamRatingValue.create(t.rating),
|
||||
overall: t.rating,
|
||||
lastUpdated: new Date('2024-01-01'),
|
||||
eventCount: 5,
|
||||
});
|
||||
}
|
||||
|
||||
// Get ranking for team-2 (should be rank 2)
|
||||
const result = await useCase.getTeamRanking('team-2');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.overallRank).toBe(2);
|
||||
});
|
||||
|
||||
it('should return complete team ranking data', async () => {
|
||||
const teamId = 'team-123';
|
||||
const team = Team.create({
|
||||
id: teamId,
|
||||
name: 'Complete Team',
|
||||
tag: 'CT',
|
||||
description: 'Complete team description',
|
||||
ownerId: 'driver-123',
|
||||
leagues: [],
|
||||
});
|
||||
mockTeamRepo.setTeam(team);
|
||||
|
||||
const snapshot: TeamRatingSnapshot = {
|
||||
teamId,
|
||||
driving: TeamRatingValue.create(82),
|
||||
adminTrust: TeamRatingValue.create(78),
|
||||
overall: 80.8,
|
||||
lastUpdated: new Date('2024-01-15T10:30:00Z'),
|
||||
eventCount: 25,
|
||||
};
|
||||
mockRatingRepo.setSnapshot(teamId, snapshot);
|
||||
|
||||
const result = await useCase.getTeamRanking(teamId);
|
||||
|
||||
expect(result).toEqual({
|
||||
teamId,
|
||||
teamName: 'Complete Team',
|
||||
drivingRating: 82,
|
||||
adminTrustRating: 78,
|
||||
overallRating: 80.8,
|
||||
eventCount: 25,
|
||||
lastUpdated: new Date('2024-01-15T10:30:00Z'),
|
||||
overallRank: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// Mock repository to throw error
|
||||
const originalFindAll = mockTeamRepo.findAll.bind(mockTeamRepo);
|
||||
mockTeamRepo.findAll = async () => {
|
||||
throw new Error('Repository connection failed');
|
||||
};
|
||||
|
||||
await expect(useCase.getAllTeamRankings()).rejects.toThrow('Repository connection failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user