237 lines
8.4 KiB
TypeScript
237 lines
8.4 KiB
TypeScript
/**
|
|
* Integration Test: GetDriversLeaderboardUseCase Orchestration
|
|
*
|
|
* Tests the orchestration logic of GetDriversLeaderboardUseCase:
|
|
* - GetDriversLeaderboardUseCase: Retrieves list of drivers with rankings and statistics
|
|
* - Validates that Use Cases correctly interact with their Ports (Repositories, other Use Cases)
|
|
* - Uses In-Memory adapters for fast, deterministic testing
|
|
*
|
|
* Focus: Business logic orchestration, NOT UI rendering
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
|
|
import { InMemoryDriverStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverStatsRepository';
|
|
import { GetDriversLeaderboardUseCase } from '../../../core/racing/application/use-cases/GetDriversLeaderboardUseCase';
|
|
import { RankingUseCase } from '../../../core/racing/application/use-cases/RankingUseCase';
|
|
import { DriverStatsUseCase } from '../../../core/racing/application/use-cases/DriverStatsUseCase';
|
|
import { Driver } from '../../../core/racing/domain/entities/Driver';
|
|
import { Logger } from '../../../core/shared/domain/Logger';
|
|
|
|
describe('GetDriversLeaderboardUseCase Orchestration', () => {
|
|
let driverRepository: InMemoryDriverRepository;
|
|
let driverStatsRepository: InMemoryDriverStatsRepository;
|
|
let rankingUseCase: RankingUseCase;
|
|
let driverStatsUseCase: DriverStatsUseCase;
|
|
let getDriversLeaderboardUseCase: GetDriversLeaderboardUseCase;
|
|
let mockLogger: Logger;
|
|
|
|
beforeAll(() => {
|
|
mockLogger = {
|
|
info: () => {},
|
|
debug: () => {},
|
|
warn: () => {},
|
|
error: () => {},
|
|
} as unknown as Logger;
|
|
|
|
driverRepository = new InMemoryDriverRepository(mockLogger);
|
|
driverStatsRepository = new InMemoryDriverStatsRepository(mockLogger);
|
|
|
|
// RankingUseCase and DriverStatsUseCase are dependencies of GetDriversLeaderboardUseCase
|
|
rankingUseCase = new RankingUseCase(
|
|
{} as any, // standingRepository not used in getAllDriverRankings
|
|
{} as any, // driverRepository not used in getAllDriverRankings
|
|
driverStatsRepository,
|
|
mockLogger
|
|
);
|
|
|
|
driverStatsUseCase = new DriverStatsUseCase(
|
|
{} as any, // resultRepository not used in getDriverStats
|
|
{} as any, // standingRepository not used in getDriverStats
|
|
driverStatsRepository,
|
|
mockLogger
|
|
);
|
|
|
|
getDriversLeaderboardUseCase = new GetDriversLeaderboardUseCase(
|
|
driverRepository,
|
|
rankingUseCase,
|
|
driverStatsUseCase,
|
|
mockLogger
|
|
);
|
|
});
|
|
|
|
beforeEach(() => {
|
|
driverRepository.clear();
|
|
driverStatsRepository.clear();
|
|
});
|
|
|
|
describe('GetDriversLeaderboardUseCase - Success Path', () => {
|
|
it('should retrieve complete list of drivers with all data', async () => {
|
|
// Scenario: System has multiple drivers
|
|
// Given: 3 drivers exist with various data
|
|
const drivers = [
|
|
Driver.create({ id: 'd1', iracingId: '1', name: 'Driver 1', country: 'US' }),
|
|
Driver.create({ id: 'd2', iracingId: '2', name: 'Driver 2', country: 'UK' }),
|
|
Driver.create({ id: 'd3', iracingId: '3', name: 'Driver 3', country: 'DE' }),
|
|
];
|
|
|
|
for (const d of drivers) {
|
|
await driverRepository.create(d);
|
|
}
|
|
|
|
// And: Each driver has statistics
|
|
await driverStatsRepository.saveDriverStats('d1', {
|
|
rating: 2000,
|
|
totalRaces: 10,
|
|
wins: 2,
|
|
podiums: 5,
|
|
overallRank: 1,
|
|
safetyRating: 4.5,
|
|
sportsmanshipRating: 95,
|
|
dnfs: 0,
|
|
avgFinish: 3.5,
|
|
bestFinish: 1,
|
|
worstFinish: 10,
|
|
consistency: 85,
|
|
experienceLevel: 'pro'
|
|
});
|
|
await driverStatsRepository.saveDriverStats('d2', {
|
|
rating: 1800,
|
|
totalRaces: 8,
|
|
wins: 1,
|
|
podiums: 3,
|
|
overallRank: 2,
|
|
safetyRating: 4.0,
|
|
sportsmanshipRating: 90,
|
|
dnfs: 1,
|
|
avgFinish: 5.2,
|
|
bestFinish: 1,
|
|
worstFinish: 15,
|
|
consistency: 75,
|
|
experienceLevel: 'intermediate'
|
|
});
|
|
await driverStatsRepository.saveDriverStats('d3', {
|
|
rating: 1500,
|
|
totalRaces: 5,
|
|
wins: 0,
|
|
podiums: 1,
|
|
overallRank: 3,
|
|
safetyRating: 3.5,
|
|
sportsmanshipRating: 80,
|
|
dnfs: 0,
|
|
avgFinish: 8.0,
|
|
bestFinish: 3,
|
|
worstFinish: 12,
|
|
consistency: 65,
|
|
experienceLevel: 'rookie'
|
|
});
|
|
|
|
// When: GetDriversLeaderboardUseCase.execute() is called
|
|
const result = await getDriversLeaderboardUseCase.execute({});
|
|
|
|
// Then: The result should contain all drivers
|
|
expect(result.isOk()).toBe(true);
|
|
const leaderboard = result.unwrap();
|
|
|
|
expect(leaderboard.items).toHaveLength(3);
|
|
expect(leaderboard.totalRaces).toBe(23);
|
|
expect(leaderboard.totalWins).toBe(3);
|
|
expect(leaderboard.activeCount).toBe(3);
|
|
|
|
// And: Drivers should be sorted by rating (high to low)
|
|
expect(leaderboard.items[0].driver.id).toBe('d1');
|
|
expect(leaderboard.items[1].driver.id).toBe('d2');
|
|
expect(leaderboard.items[2].driver.id).toBe('d3');
|
|
|
|
expect(leaderboard.items[0].rating).toBe(2000);
|
|
expect(leaderboard.items[1].rating).toBe(1800);
|
|
expect(leaderboard.items[2].rating).toBe(1500);
|
|
});
|
|
|
|
it('should handle empty drivers list', async () => {
|
|
// Scenario: System has no registered drivers
|
|
// Given: No drivers exist in the system
|
|
// When: GetDriversLeaderboardUseCase.execute() is called
|
|
const result = await getDriversLeaderboardUseCase.execute({});
|
|
|
|
// Then: The result should contain an empty array
|
|
expect(result.isOk()).toBe(true);
|
|
const leaderboard = result.unwrap();
|
|
expect(leaderboard.items).toHaveLength(0);
|
|
expect(leaderboard.totalRaces).toBe(0);
|
|
expect(leaderboard.totalWins).toBe(0);
|
|
expect(leaderboard.activeCount).toBe(0);
|
|
});
|
|
|
|
it('should correctly identify active drivers', async () => {
|
|
// Scenario: Some drivers have no races
|
|
// Given: 2 drivers exist, one with races, one without
|
|
await driverRepository.create(Driver.create({ id: 'active', iracingId: '1', name: 'Active', country: 'US' }));
|
|
await driverRepository.create(Driver.create({ id: 'inactive', iracingId: '2', name: 'Inactive', country: 'UK' }));
|
|
|
|
await driverStatsRepository.saveDriverStats('active', {
|
|
rating: 1500,
|
|
totalRaces: 1,
|
|
wins: 0,
|
|
podiums: 0,
|
|
overallRank: 1,
|
|
safetyRating: 3.0,
|
|
sportsmanshipRating: 70,
|
|
dnfs: 0,
|
|
avgFinish: 10,
|
|
bestFinish: 10,
|
|
worstFinish: 10,
|
|
consistency: 50,
|
|
experienceLevel: 'rookie'
|
|
});
|
|
// No stats for inactive driver or totalRaces = 0
|
|
await driverStatsRepository.saveDriverStats('inactive', {
|
|
rating: 1000,
|
|
totalRaces: 0,
|
|
wins: 0,
|
|
podiums: 0,
|
|
overallRank: null,
|
|
safetyRating: 2.5,
|
|
sportsmanshipRating: 50,
|
|
dnfs: 0,
|
|
avgFinish: 0,
|
|
bestFinish: 0,
|
|
worstFinish: 0,
|
|
consistency: 0,
|
|
experienceLevel: 'rookie'
|
|
});
|
|
|
|
// When: GetDriversLeaderboardUseCase.execute() is called
|
|
const result = await getDriversLeaderboardUseCase.execute({});
|
|
|
|
// Then: Only one driver should be active
|
|
const leaderboard = result.unwrap();
|
|
expect(leaderboard.activeCount).toBe(1);
|
|
expect(leaderboard.items.find(i => i.driver.id === 'active')?.isActive).toBe(true);
|
|
expect(leaderboard.items.find(i => i.driver.id === 'inactive')?.isActive).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('GetDriversLeaderboardUseCase - Error Handling', () => {
|
|
it('should handle repository errors gracefully', async () => {
|
|
// Scenario: Repository throws error
|
|
// Given: DriverRepository throws an error during query
|
|
const originalFindAll = driverRepository.findAll.bind(driverRepository);
|
|
driverRepository.findAll = async () => {
|
|
throw new Error('Repository error');
|
|
};
|
|
|
|
// When: GetDriversLeaderboardUseCase.execute() is called
|
|
const result = await getDriversLeaderboardUseCase.execute({});
|
|
|
|
// Then: Should return a repository error
|
|
expect(result.isErr()).toBe(true);
|
|
const error = result.unwrapErr();
|
|
expect(error.code).toBe('REPOSITORY_ERROR');
|
|
|
|
// Restore original method
|
|
driverRepository.findAll = originalFindAll;
|
|
});
|
|
});
|
|
});
|