import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { RatingTestContext } from './RatingTestContext'; import { GetRatingLeaderboardUseCase } from '../../../../core/rating/application/use-cases/GetRatingLeaderboardUseCase'; import { GetRatingPercentileUseCase } from '../../../../core/rating/application/use-cases/GetRatingPercentileUseCase'; import { GetRatingComparisonUseCase } from '../../../../core/rating/application/use-cases/GetRatingComparisonUseCase'; import { Driver } from '../../../../core/racing/domain/entities/Driver'; import { Rating } from '../../../../core/rating/domain/entities/Rating'; describe('Rating Leaderboard Use Cases', () => { let context: RatingTestContext; let getRatingLeaderboardUseCase: GetRatingLeaderboardUseCase; let getRatingPercentileUseCase: GetRatingPercentileUseCase; let getRatingComparisonUseCase: GetRatingComparisonUseCase; beforeAll(() => { context = RatingTestContext.create(); getRatingLeaderboardUseCase = new GetRatingLeaderboardUseCase( context.driverRepository, context.ratingRepository ); getRatingPercentileUseCase = new GetRatingPercentileUseCase( context.driverRepository, context.ratingRepository ); getRatingComparisonUseCase = new GetRatingComparisonUseCase( context.driverRepository, context.ratingRepository ); }); beforeEach(async () => { await context.clear(); }); describe('GetRatingLeaderboardUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve driver leaderboard sorted by rating', async () => { // Given: Multiple drivers with different ratings const drivers = [ Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }), Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }), Driver.create({ id: 'd3', iracingId: '102', name: 'Bob Johnson', country: 'CA' }) ]; for (const driver of drivers) { await context.driverRepository.create(driver); } // Given: Ratings for each driver const ratings = [ Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }), Rating.create({ driverId: 'd2', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }), Rating.create({ driverId: 'd3', rating: 1400, components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 }, timestamp: new Date() }) ]; for (const rating of ratings) { await context.ratingRepository.save(rating); } // When: GetRatingLeaderboardUseCase.execute() is called const result = await getRatingLeaderboardUseCase.execute({}); // Then: The leaderboard should be retrieved sorted by rating expect(result.isOk()).toBe(true); const leaderboard = result.unwrap(); expect(leaderboard).toHaveLength(3); expect(leaderboard[0].driverId.toString()).toBe('d2'); // Highest rating expect(leaderboard[0].rating).toBe(1600); expect(leaderboard[1].driverId.toString()).toBe('d1'); expect(leaderboard[2].driverId.toString()).toBe('d3'); }); it('should retrieve leaderboard with limit', async () => { // Given: Multiple drivers with different ratings const drivers = []; for (let i = 1; i <= 10; i++) { const driver = Driver.create({ id: `d${i}`, iracingId: `${100 + i}`, name: `Driver ${i}`, country: 'US' }); drivers.push(driver); await context.driverRepository.create(driver); } // Given: Ratings for each driver for (let i = 1; i <= 10; i++) { const rating = Rating.create({ driverId: `d${i}`, rating: 1400 + (i * 20), components: { resultsStrength: 70 + i, consistency: 65 + i, cleanDriving: 80 + i, racecraft: 75 + i, reliability: 85 + i, teamContribution: 60 + i }, timestamp: new Date() }); await context.ratingRepository.save(rating); } // When: GetRatingLeaderboardUseCase.execute() is called with limit const result = await getRatingLeaderboardUseCase.execute({ limit: 5 }); // Then: The leaderboard should be retrieved with limit expect(result.isOk()).toBe(true); const leaderboard = result.unwrap(); expect(leaderboard).toHaveLength(5); expect(leaderboard[0].rating).toBe(1600); // d10 expect(leaderboard[4].rating).toBe(1520); // d6 }); it('should retrieve leaderboard with offset', async () => { // Given: Multiple drivers with different ratings const drivers = []; for (let i = 1; i <= 5; i++) { const driver = Driver.create({ id: `d${i}`, iracingId: `${100 + i}`, name: `Driver ${i}`, country: 'US' }); drivers.push(driver); await context.driverRepository.create(driver); } // Given: Ratings for each driver for (let i = 1; i <= 5; i++) { const rating = Rating.create({ driverId: `d${i}`, rating: 1400 + (i * 20), components: { resultsStrength: 70 + i, consistency: 65 + i, cleanDriving: 80 + i, racecraft: 75 + i, reliability: 85 + i, teamContribution: 60 + i }, timestamp: new Date() }); await context.ratingRepository.save(rating); } // When: GetRatingLeaderboardUseCase.execute() is called with offset const result = await getRatingLeaderboardUseCase.execute({ offset: 2 }); // Then: The leaderboard should be retrieved with offset expect(result.isOk()).toBe(true); const leaderboard = result.unwrap(); expect(leaderboard).toHaveLength(3); // 5 total - 2 offset = 3 expect(leaderboard[0].driverId.toString()).toBe('d3'); // Third highest }); }); describe('UseCase - Edge Cases', () => { it('should handle empty leaderboard', async () => { // Given: No drivers or ratings // When: GetRatingLeaderboardUseCase.execute() is called const result = await getRatingLeaderboardUseCase.execute({}); // Then: The leaderboard should be empty expect(result.isOk()).toBe(true); const leaderboard = result.unwrap(); expect(leaderboard).toHaveLength(0); }); it('should handle drivers without ratings', async () => { // Given: Drivers without ratings const driver = Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // When: GetRatingLeaderboardUseCase.execute() is called const result = await getRatingLeaderboardUseCase.execute({}); // Then: The leaderboard should be empty expect(result.isOk()).toBe(true); const leaderboard = result.unwrap(); expect(leaderboard).toHaveLength(0); }); }); describe('UseCase - Error Handling', () => { it('should handle invalid limit', async () => { // Given: Drivers with ratings const driver = Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); const rating = Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); await context.ratingRepository.save(rating); // When: GetRatingLeaderboardUseCase.execute() is called with invalid limit const result = await getRatingLeaderboardUseCase.execute({ limit: -1 }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetRatingPercentileUseCase', () => { describe('UseCase - Success Path', () => { it('should calculate percentile for driver', async () => { // Given: Multiple drivers with different ratings const drivers = [ Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }), Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }), Driver.create({ id: 'd3', iracingId: '102', name: 'Bob Johnson', country: 'CA' }), Driver.create({ id: 'd4', iracingId: '103', name: 'Alice Brown', country: 'AU' }), Driver.create({ id: 'd5', iracingId: '104', name: 'Charlie Wilson', country: 'DE' }) ]; for (const driver of drivers) { await context.driverRepository.create(driver); } // Given: Ratings for each driver const ratings = [ Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }), Rating.create({ driverId: 'd2', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }), Rating.create({ driverId: 'd3', rating: 1400, components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 }, timestamp: new Date() }), Rating.create({ driverId: 'd4', rating: 1550, components: { resultsStrength: 82, consistency: 77, cleanDriving: 91, racecraft: 86, reliability: 94, teamContribution: 72 }, timestamp: new Date() }), Rating.create({ driverId: 'd5', rating: 1450, components: { resultsStrength: 78, consistency: 73, cleanDriving: 89, racecraft: 83, reliability: 92, teamContribution: 68 }, timestamp: new Date() }) ]; for (const rating of ratings) { await context.ratingRepository.save(rating); } // When: GetRatingPercentileUseCase.execute() is called for driver d2 (highest rating) const result = await getRatingPercentileUseCase.execute({ driverId: 'd2' }); // Then: The percentile should be calculated expect(result.isOk()).toBe(true); const percentile = result.unwrap(); expect(percentile.driverId.toString()).toBe('d2'); expect(percentile.percentile).toBeGreaterThan(0); expect(percentile.percentile).toBeLessThanOrEqual(100); expect(percentile.totalDrivers).toBe(5); expect(percentile.driversAbove).toBe(0); expect(percentile.driversBelow).toBe(4); }); it('should calculate percentile for middle-ranked driver', async () => { // Given: Multiple drivers with different ratings const drivers = [ Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }), Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }), Driver.create({ id: 'd3', iracingId: '102', name: 'Bob Johnson', country: 'CA' }) ]; for (const driver of drivers) { await context.driverRepository.create(driver); } // Given: Ratings for each driver const ratings = [ Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }), Rating.create({ driverId: 'd2', rating: 1400, components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 }, timestamp: new Date() }), Rating.create({ driverId: 'd3', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }) ]; for (const rating of ratings) { await context.ratingRepository.save(rating); } // When: GetRatingPercentileUseCase.execute() is called for driver d1 (middle rating) const result = await getRatingPercentileUseCase.execute({ driverId: 'd1' }); // Then: The percentile should be calculated expect(result.isOk()).toBe(true); const percentile = result.unwrap(); expect(percentile.driverId.toString()).toBe('d1'); expect(percentile.percentile).toBeGreaterThan(0); expect(percentile.percentile).toBeLessThan(100); expect(percentile.totalDrivers).toBe(3); expect(percentile.driversAbove).toBe(1); // d3 expect(percentile.driversBelow).toBe(1); // d2 }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetRatingPercentileUseCase.execute() is called const result = await getRatingPercentileUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with no rating', async () => { // Given: A driver with no rating const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // When: GetRatingPercentileUseCase.execute() is called const result = await getRatingPercentileUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle empty leaderboard', async () => { // Given: No drivers or ratings // When: GetRatingPercentileUseCase.execute() is called const result = await getRatingPercentileUseCase.execute({ driverId: 'd1' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetRatingComparisonUseCase', () => { describe('UseCase - Success Path', () => { it('should compare driver rating with another driver', async () => { // Given: Two drivers with different ratings const driver1 = Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }); const driver2 = Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }); await context.driverRepository.create(driver1); await context.driverRepository.create(driver2); // Given: Ratings for each driver const rating1 = Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); const rating2 = Rating.create({ driverId: 'd2', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }); await context.ratingRepository.save(rating1); await context.ratingRepository.save(rating2); // When: GetRatingComparisonUseCase.execute() is called const result = await getRatingComparisonUseCase.execute({ driverId: 'd1', compareWithDriverId: 'd2' }); // Then: The comparison should be retrieved expect(result.isOk()).toBe(true); const comparison = result.unwrap(); expect(comparison.driverId.toString()).toBe('d1'); expect(comparison.compareWithDriverId.toString()).toBe('d2'); expect(comparison.driverRating).toBe(1500); expect(comparison.compareWithRating).toBe(1600); expect(comparison.difference).toBe(-100); expect(comparison.differencePercentage).toBeLessThan(0); }); it('should compare driver rating with league average', async () => { // Given: Multiple drivers with different ratings const drivers = [ Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }), Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }), Driver.create({ id: 'd3', iracingId: '102', name: 'Bob Johnson', country: 'CA' }) ]; for (const driver of drivers) { await context.driverRepository.create(driver); } // Given: Ratings for each driver const ratings = [ Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }), Rating.create({ driverId: 'd2', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }), Rating.create({ driverId: 'd3', rating: 1400, components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 }, timestamp: new Date() }) ]; for (const rating of ratings) { await context.ratingRepository.save(rating); } // When: GetRatingComparisonUseCase.execute() is called with league comparison const result = await getRatingComparisonUseCase.execute({ driverId: 'd1', compareWithLeague: true }); // Then: The comparison should be retrieved expect(result.isOk()).toBe(true); const comparison = result.unwrap(); expect(comparison.driverId.toString()).toBe('d1'); expect(comparison.driverRating).toBe(1500); expect(comparison.leagueAverage).toBe(1500); // (1500 + 1600 + 1400) / 3 expect(comparison.difference).toBe(0); expect(comparison.differencePercentage).toBe(0); }); it('should compare driver rating with league median', async () => { // Given: Multiple drivers with different ratings const drivers = [ Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }), Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }), Driver.create({ id: 'd3', iracingId: '102', name: 'Bob Johnson', country: 'CA' }), Driver.create({ id: 'd4', iracingId: '103', name: 'Alice Brown', country: 'AU' }) ]; for (const driver of drivers) { await context.driverRepository.create(driver); } // Given: Ratings for each driver const ratings = [ Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }), Rating.create({ driverId: 'd2', rating: 1600, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }), Rating.create({ driverId: 'd3', rating: 1400, components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 }, timestamp: new Date() }), Rating.create({ driverId: 'd4', rating: 1550, components: { resultsStrength: 82, consistency: 77, cleanDriving: 91, racecraft: 86, reliability: 94, teamContribution: 72 }, timestamp: new Date() }) ]; for (const rating of ratings) { await context.ratingRepository.save(rating); } // When: GetRatingComparisonUseCase.execute() is called with league median comparison const result = await getRatingComparisonUseCase.execute({ driverId: 'd1', compareWithLeague: true, useMedian: true }); // Then: The comparison should be retrieved expect(result.isOk()).toBe(true); const comparison = result.unwrap(); expect(comparison.driverId.toString()).toBe('d1'); expect(comparison.driverRating).toBe(1500); expect(comparison.leagueMedian).toBe(1525); // Median of [1400, 1500, 1550, 1600] expect(comparison.difference).toBe(-25); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; const compareWithDriverId = 'd1'; // When: GetRatingComparisonUseCase.execute() is called const result = await getRatingComparisonUseCase.execute({ driverId, compareWithDriverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with no rating', async () => { // Given: Drivers with no ratings const driver1 = Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }); const driver2 = Driver.create({ id: 'd2', iracingId: '101', name: 'Jane Smith', country: 'UK' }); await context.driverRepository.create(driver1); await context.driverRepository.create(driver2); // When: GetRatingComparisonUseCase.execute() is called const result = await getRatingComparisonUseCase.execute({ driverId: 'd1', compareWithDriverId: 'd2' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle empty league for comparison', async () => { // Given: A driver with rating const driver = Driver.create({ id: 'd1', iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); const rating = Rating.create({ driverId: 'd1', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); await context.ratingRepository.save(rating); // When: GetRatingComparisonUseCase.execute() is called with league comparison const result = await getRatingComparisonUseCase.execute({ driverId: 'd1', compareWithLeague: true }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); });