core tests

This commit is contained in:
2026-01-22 18:05:30 +01:00
parent 0a37454171
commit 35cc7cf12b
26 changed files with 4701 additions and 21 deletions

View File

@@ -0,0 +1,57 @@
import { describe, it, expect, vi } from 'vitest';
import { DriverStatsUseCase, type DriverStats } from './DriverStatsUseCase';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { DriverStatsRepository } from '../../domain/repositories/DriverStatsRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('DriverStatsUseCase', () => {
const mockResultRepository = {} as ResultRepository;
const mockStandingRepository = {} as StandingRepository;
const mockDriverStatsRepository = {
getDriverStats: vi.fn(),
} as unknown as DriverStatsRepository;
const mockLogger = {
debug: vi.fn(),
} as unknown as Logger;
const useCase = new DriverStatsUseCase(
mockResultRepository,
mockStandingRepository,
mockDriverStatsRepository,
mockLogger
);
it('should return driver stats when found', async () => {
const mockStats: DriverStats = {
rating: 1500,
safetyRating: 4.5,
sportsmanshipRating: 4.8,
totalRaces: 10,
wins: 2,
podiums: 5,
dnfs: 0,
avgFinish: 3.5,
bestFinish: 1,
worstFinish: 8,
consistency: 0.9,
experienceLevel: 'Intermediate',
overallRank: 42,
};
vi.mocked(mockDriverStatsRepository.getDriverStats).mockResolvedValue(mockStats);
const result = await useCase.getDriverStats('driver-1');
expect(result).toEqual(mockStats);
expect(mockLogger.debug).toHaveBeenCalledWith('Getting stats for driver driver-1');
expect(mockDriverStatsRepository.getDriverStats).toHaveBeenCalledWith('driver-1');
});
it('should return null when stats are not found', async () => {
vi.mocked(mockDriverStatsRepository.getDriverStats).mockResolvedValue(null);
const result = await useCase.getDriverStats('non-existent');
expect(result).toBeNull();
});
});

View File

@@ -0,0 +1,43 @@
import { describe, it, expect, vi } from 'vitest';
import { GetDriverUseCase } from './GetDriverUseCase';
import { Result } from '@core/shared/domain/Result';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { Driver } from '../../domain/entities/Driver';
describe('GetDriverUseCase', () => {
const mockDriverRepository = {
findById: vi.fn(),
} as unknown as DriverRepository;
const useCase = new GetDriverUseCase(mockDriverRepository);
it('should return a driver when found', async () => {
const mockDriver = { id: 'driver-1', name: 'John Doe' } as unknown as Driver;
vi.mocked(mockDriverRepository.findById).mockResolvedValue(mockDriver);
const result = await useCase.execute({ driverId: 'driver-1' });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(mockDriver);
expect(mockDriverRepository.findById).toHaveBeenCalledWith('driver-1');
});
it('should return null when driver is not found', async () => {
vi.mocked(mockDriverRepository.findById).mockResolvedValue(null);
const result = await useCase.execute({ driverId: 'non-existent' });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeNull();
});
it('should return an error when repository throws', async () => {
const error = new Error('Repository error');
vi.mocked(mockDriverRepository.findById).mockRejectedValue(error);
const result = await useCase.execute({ driverId: 'driver-1' });
expect(result.isErr()).toBe(true);
expect(result.error).toBe(error);
});
});

View File

@@ -0,0 +1,90 @@
import { describe, it, expect, vi } from 'vitest';
import { GetTeamsLeaderboardUseCase } from './GetTeamsLeaderboardUseCase';
import { Result } from '@core/shared/domain/Result';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { Team } from '../../domain/entities/Team';
describe('GetTeamsLeaderboardUseCase', () => {
const mockTeamRepository = {
findAll: vi.fn(),
} as unknown as TeamRepository;
const mockTeamMembershipRepository = {
getTeamMembers: vi.fn(),
} as unknown as TeamMembershipRepository;
const mockGetDriverStats = vi.fn();
const mockLogger = {
error: vi.fn(),
} as unknown as Logger;
const useCase = new GetTeamsLeaderboardUseCase(
mockTeamRepository,
mockTeamMembershipRepository,
mockGetDriverStats,
mockLogger
);
it('should return teams leaderboard with calculated stats', async () => {
const mockTeam1 = { id: 'team-1', name: 'Team 1' } as unknown as Team;
const mockTeam2 = { id: 'team-2', name: 'Team 2' } as unknown as Team;
vi.mocked(mockTeamRepository.findAll).mockResolvedValue([mockTeam1, mockTeam2]);
vi.mocked(mockTeamMembershipRepository.getTeamMembers).mockImplementation(async (teamId) => {
if (teamId === 'team-1') return [{ driverId: 'driver-1' }, { driverId: 'driver-2' }] as any;
if (teamId === 'team-2') return [{ driverId: 'driver-3' }] as any;
return [];
});
mockGetDriverStats.mockImplementation((driverId) => {
if (driverId === 'driver-1') return { rating: 1000, wins: 1, totalRaces: 5 };
if (driverId === 'driver-2') return { rating: 2000, wins: 2, totalRaces: 10 };
if (driverId === 'driver-3') return { rating: 1500, wins: 0, totalRaces: 2 };
return null;
});
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.items).toHaveLength(2);
const item1 = data.items.find(i => i.team.id === 'team-1');
expect(item1?.rating).toBe(1500); // (1000 + 2000) / 2
expect(item1?.totalWins).toBe(3);
expect(item1?.totalRaces).toBe(15);
const item2 = data.items.find(i => i.team.id === 'team-2');
expect(item2?.rating).toBe(1500);
expect(item2?.totalWins).toBe(0);
expect(item2?.totalRaces).toBe(2);
expect(data.topItems).toHaveLength(2);
});
it('should handle teams with no members', async () => {
const mockTeam = { id: 'team-empty', name: 'Empty Team' } as unknown as Team;
vi.mocked(mockTeamRepository.findAll).mockResolvedValue([mockTeam]);
vi.mocked(mockTeamMembershipRepository.getTeamMembers).mockResolvedValue([]);
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.items[0].rating).toBeNull();
expect(data.items[0].performanceLevel).toBe('beginner');
});
it('should return error when repository fails', async () => {
vi.mocked(mockTeamRepository.findAll).mockRejectedValue(new Error('DB Error'));
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.error.code).toBe('REPOSITORY_ERROR');
expect(mockLogger.error).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,59 @@
import { describe, it, expect, vi } from 'vitest';
import { RankingUseCase, type DriverRanking } from './RankingUseCase';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { DriverStatsRepository } from '../../domain/repositories/DriverStatsRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('RankingUseCase', () => {
const mockStandingRepository = {} as StandingRepository;
const mockDriverRepository = {} as DriverRepository;
const mockDriverStatsRepository = {
getAllStats: vi.fn(),
} as unknown as DriverStatsRepository;
const mockLogger = {
debug: vi.fn(),
} as unknown as Logger;
const useCase = new RankingUseCase(
mockStandingRepository,
mockDriverRepository,
mockDriverStatsRepository,
mockLogger
);
it('should return all driver rankings', async () => {
const mockStatsMap = new Map([
['driver-1', { rating: 1500, wins: 2, totalRaces: 10, overallRank: 1 }],
['driver-2', { rating: 1200, wins: 0, totalRaces: 5, overallRank: 2 }],
]);
vi.mocked(mockDriverStatsRepository.getAllStats).mockResolvedValue(mockStatsMap as any);
const result = await useCase.getAllDriverRankings();
expect(result).toHaveLength(2);
expect(result).toContainEqual({
driverId: 'driver-1',
rating: 1500,
wins: 2,
totalRaces: 10,
overallRank: 1,
});
expect(result).toContainEqual({
driverId: 'driver-2',
rating: 1200,
wins: 0,
totalRaces: 5,
overallRank: 2,
});
expect(mockLogger.debug).toHaveBeenCalledWith('Getting all driver rankings');
});
it('should return empty array when no stats exist', async () => {
vi.mocked(mockDriverStatsRepository.getAllStats).mockResolvedValue(new Map());
const result = await useCase.getAllDriverRankings();
expect(result).toEqual([]);
});
});

View File

@@ -0,0 +1,44 @@
import { describe, it, expect, vi } from 'vitest';
import { RaceResultGenerator } from './RaceResultGenerator';
describe('RaceResultGenerator', () => {
it('should generate results for all drivers', () => {
const raceId = 'race-1';
const driverIds = ['d1', 'd2', 'd3'];
const driverRatings = new Map([
['d1', 2000],
['d2', 1500],
['d3', 1000],
]);
const results = RaceResultGenerator.generateRaceResults(raceId, driverIds, driverRatings);
expect(results).toHaveLength(3);
const resultDriverIds = results.map(r => r.driverId.toString());
expect(resultDriverIds).toContain('d1');
expect(resultDriverIds).toContain('d2');
expect(resultDriverIds).toContain('d3');
results.forEach(r => {
expect(r.raceId.toString()).toBe(raceId);
expect(r.position.toNumber()).toBeGreaterThan(0);
expect(r.position.toNumber()).toBeLessThanOrEqual(3);
});
});
it('should provide incident descriptions', () => {
expect(RaceResultGenerator.getIncidentDescription(0)).toBe('Clean race');
expect(RaceResultGenerator.getIncidentDescription(1)).toBe('Track limits violation');
expect(RaceResultGenerator.getIncidentDescription(2)).toBe('Contact with another car');
expect(RaceResultGenerator.getIncidentDescription(3)).toBe('Off-track incident');
expect(RaceResultGenerator.getIncidentDescription(4)).toBe('Collision requiring safety car');
expect(RaceResultGenerator.getIncidentDescription(5)).toBe('5 incidents');
});
it('should calculate incident penalty points', () => {
expect(RaceResultGenerator.getIncidentPenaltyPoints(0)).toBe(0);
expect(RaceResultGenerator.getIncidentPenaltyPoints(1)).toBe(0);
expect(RaceResultGenerator.getIncidentPenaltyPoints(2)).toBe(2);
expect(RaceResultGenerator.getIncidentPenaltyPoints(3)).toBe(4);
});
});

View File

@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest';
import { RaceResultGeneratorWithIncidents } from './RaceResultGeneratorWithIncidents';
import { RaceIncidents } from '../../domain/value-objects/RaceIncidents';
describe('RaceResultGeneratorWithIncidents', () => {
it('should generate results for all drivers', () => {
const raceId = 'race-1';
const driverIds = ['d1', 'd2'];
const driverRatings = new Map([
['d1', 2000],
['d2', 1500],
]);
const results = RaceResultGeneratorWithIncidents.generateRaceResults(raceId, driverIds, driverRatings);
expect(results).toHaveLength(2);
results.forEach(r => {
expect(r.raceId.toString()).toBe(raceId);
expect(r.incidents).toBeInstanceOf(RaceIncidents);
});
});
it('should calculate incident penalty points', () => {
const incidents = new RaceIncidents([
{ type: 'contact', lap: 1, description: 'desc', penaltyPoints: 2 },
{ type: 'unsafe_rejoin', lap: 5, description: 'desc', penaltyPoints: 3 },
]);
expect(RaceResultGeneratorWithIncidents.getIncidentPenaltyPoints(incidents)).toBe(5);
});
it('should get incident description', () => {
const incidents = new RaceIncidents([
{ type: 'contact', lap: 1, description: 'desc', penaltyPoints: 2 },
]);
const description = RaceResultGeneratorWithIncidents.getIncidentDescription(incidents);
expect(description).toContain('1 incidents');
});
});