import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { RatingTestContext } from './RatingTestContext'; import { CalculateReliabilityUseCase } from '../../../../core/rating/application/use-cases/CalculateReliabilityUseCase'; import { GetReliabilityScoreUseCase } from '../../../../core/rating/application/use-cases/GetReliabilityScoreUseCase'; import { GetReliabilityTrendUseCase } from '../../../../core/rating/application/use-cases/GetReliabilityTrendUseCase'; import { Driver } from '../../../../core/racing/domain/entities/Driver'; import { Race } from '../../../../core/racing/domain/entities/Race'; import { League } from '../../../../core/racing/domain/entities/League'; import { Result as RaceResult } from '../../../../core/racing/domain/entities/result/Result'; describe('Rating Reliability Use Cases', () => { let context: RatingTestContext; let calculateReliabilityUseCase: CalculateReliabilityUseCase; let getReliabilityScoreUseCase: GetReliabilityScoreUseCase; let getReliabilityTrendUseCase: GetReliabilityTrendUseCase; beforeAll(() => { context = RatingTestContext.create(); calculateReliabilityUseCase = new CalculateReliabilityUseCase( context.driverRepository, context.raceRepository, context.resultRepository, context.eventPublisher ); getReliabilityScoreUseCase = new GetReliabilityScoreUseCase( context.driverRepository, context.resultRepository ); getReliabilityTrendUseCase = new GetReliabilityTrendUseCase( context.driverRepository, context.resultRepository ); }); beforeEach(async () => { await context.clear(); }); describe('CalculateReliabilityUseCase', () => { describe('UseCase - Success Path', () => { it('should calculate reliability for driver with perfect attendance', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple completed races with perfect attendance const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r5' }); // Then: The reliability should be calculated expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.reliabilityScore).toBeGreaterThan(90); expect(reliability.raceCount).toBe(5); expect(reliability.dnfCount).toBe(0); expect(reliability.dnsCount).toBe(0); expect(reliability.attendanceRate).toBe(100); }); it('should calculate reliability with DNFs', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple races with some DNFs const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); // First 2 races are DNFs const isDNF = i <= 2; const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: isDNF ? 0 : 5, lapsCompleted: isDNF ? 10 : 20, totalTime: isDNF ? 0 : 3600, fastestLap: isDNF ? 0 : 105, points: isDNF ? 0 : 10, incidents: isDNF ? 3 : 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r5' }); // Then: The reliability should be calculated with DNF impact expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.reliabilityScore).toBeLessThan(90); expect(reliability.raceCount).toBe(5); expect(reliability.dnfCount).toBe(2); expect(reliability.dnsCount).toBe(0); expect(reliability.attendanceRate).toBe(100); }); it('should calculate reliability with DNSs', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple races with some DNSs const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); // First 2 races are DNSs const isDNS = i <= 2; const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: isDNS ? 0 : 5, lapsCompleted: isDNS ? 0 : 20, totalTime: isDNS ? 0 : 3600, fastestLap: isDNS ? 0 : 105, points: isDNS ? 0 : 10, incidents: 0, startPosition: isDNS ? 0 : 5 }); await context.resultRepository.create(result); } // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r5' }); // Then: The reliability should be calculated with DNS impact expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.reliabilityScore).toBeLessThan(90); expect(reliability.raceCount).toBe(5); expect(reliability.dnfCount).toBe(0); expect(reliability.dnsCount).toBe(2); expect(reliability.attendanceRate).toBe(60); }); it('should calculate reliability with mixed DNFs and DNSs', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple races with mixed issues const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); let position, lapsCompleted, totalTime, fastestLap, points, incidents, startPosition; if (i === 1) { // DNS position = 0; lapsCompleted = 0; totalTime = 0; fastestLap = 0; points = 0; incidents = 0; startPosition = 0; } else if (i === 2) { // DNF position = 0; lapsCompleted = 10; totalTime = 0; fastestLap = 0; points = 0; incidents = 3; startPosition = 5; } else { // Completed position = 5; lapsCompleted = 20; totalTime = 3600; fastestLap = 105; points = 10; incidents = 1; startPosition = 5; } const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position, lapsCompleted, totalTime, fastestLap, points, incidents, startPosition }); await context.resultRepository.create(result); } // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r5' }); // Then: The reliability should be calculated with mixed issues expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.reliabilityScore).toBeLessThan(80); expect(reliability.raceCount).toBe(5); expect(reliability.dnfCount).toBe(1); expect(reliability.dnsCount).toBe(1); expect(reliability.attendanceRate).toBe(60); }); it('should calculate reliability with minimum race count', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Minimum races for reliability calculation const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 3; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r3' }); // Then: The reliability should be calculated expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.raceCount).toBe(3); }); }); describe('UseCase - Edge Cases', () => { it('should handle driver with insufficient races', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Only one race const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); const raceId = 'r1'; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - 86400000), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: 'res1', raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r1' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with no races', async () => { // Given: A driver with no races const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r1' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r1' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle missing race', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // When: CalculateReliabilityUseCase.execute() is called const result = await calculateReliabilityUseCase.execute({ driverId, raceId: 'r999' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetReliabilityScoreUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve reliability score for driver', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple completed races const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: GetReliabilityScoreUseCase.execute() is called const result = await getReliabilityScoreUseCase.execute({ driverId }); // Then: The reliability score should be retrieved expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.reliabilityScore).toBeGreaterThan(0); expect(reliability.raceCount).toBe(5); expect(reliability.dnfCount).toBe(0); expect(reliability.dnsCount).toBe(0); expect(reliability.attendanceRate).toBe(100); }); it('should retrieve reliability score with race limit', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple completed races const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 10; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: GetReliabilityScoreUseCase.execute() is called with limit const result = await getReliabilityScoreUseCase.execute({ driverId, limit: 5 }); // Then: The reliability score should be retrieved with limit expect(result.isOk()).toBe(true); const reliability = result.unwrap(); expect(reliability.driverId.toString()).toBe(driverId); expect(reliability.raceCount).toBe(5); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetReliabilityScoreUseCase.execute() is called const result = await getReliabilityScoreUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with insufficient races', async () => { // Given: A driver with only one race const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); const raceId = 'r1'; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - 86400000), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: 'res1', raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); // When: GetReliabilityScoreUseCase.execute() is called const result = await getReliabilityScoreUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetReliabilityTrendUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve reliability trend for driver', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple completed races const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: GetReliabilityTrendUseCase.execute() is called const result = await getReliabilityTrendUseCase.execute({ driverId }); // Then: The reliability trend should be retrieved expect(result.isOk()).toBe(true); const trend = result.unwrap(); expect(trend.driverId.toString()).toBe(driverId); expect(trend.trend).toBeDefined(); expect(trend.trend.length).toBeGreaterThan(0); expect(trend.averageReliability).toBeGreaterThan(0); expect(trend.attendanceRate).toBe(100); }); it('should retrieve reliability trend over specific period', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple completed races const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 10; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); } // When: GetReliabilityTrendUseCase.execute() is called with period const result = await getReliabilityTrendUseCase.execute({ driverId, period: 7 }); // Then: The reliability trend should be retrieved for the period expect(result.isOk()).toBe(true); const trend = result.unwrap(); expect(trend.driverId.toString()).toBe(driverId); expect(trend.trend.length).toBeLessThanOrEqual(7); }); it('should retrieve reliability trend with DNFs and DNSs', async () => { // Given: A driver const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); // Given: Multiple races with mixed results const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); for (let i = 1; i <= 5; i++) { const raceId = `r${i}`; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - (i * 86400000)), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); let position, lapsCompleted, totalTime, fastestLap, points, incidents, startPosition; if (i === 1) { // DNS position = 0; lapsCompleted = 0; totalTime = 0; fastestLap = 0; points = 0; incidents = 0; startPosition = 0; } else if (i === 2) { // DNF position = 0; lapsCompleted = 10; totalTime = 0; fastestLap = 0; points = 0; incidents = 3; startPosition = 5; } else { // Completed position = 5; lapsCompleted = 20; totalTime = 3600; fastestLap = 105; points = 10; incidents = 1; startPosition = 5; } const result = RaceResult.create({ id: `res${i}`, raceId, driverId, position, lapsCompleted, totalTime, fastestLap, points, incidents, startPosition }); await context.resultRepository.create(result); } // When: GetReliabilityTrendUseCase.execute() is called const result = await getReliabilityTrendUseCase.execute({ driverId }); // Then: The reliability trend should be retrieved expect(result.isOk()).toBe(true); const trend = result.unwrap(); expect(trend.driverId.toString()).toBe(driverId); expect(trend.trend).toBeDefined(); expect(trend.trend.length).toBeGreaterThan(0); expect(trend.averageReliability).toBeGreaterThan(0); expect(trend.attendanceRate).toBe(60); expect(trend.dnfCount).toBe(1); expect(trend.dnsCount).toBe(1); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetReliabilityTrendUseCase.execute() is called const result = await getReliabilityTrendUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with insufficient races', async () => { // Given: A driver with only one race const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); const raceId = 'r1'; const race = Race.create({ id: raceId, leagueId, scheduledAt: new Date(Date.now() - 86400000), track: 'Spa', car: 'GT3', status: 'completed' }); await context.raceRepository.create(race); const result = RaceResult.create({ id: 'res1', raceId, driverId, position: 5, lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 10, incidents: 1, startPosition: 5 }); await context.resultRepository.create(result); // When: GetReliabilityTrendUseCase.execute() is called const result = await getReliabilityTrendUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); });