Files
gridpilot.gg/core/racing/application/use-cases/TeamRankingUseCase.test.ts
2025-12-30 12:25:45 +01:00

418 lines
12 KiB
TypeScript

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');
});
});
});