import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { RatingTestContext } from './RatingTestContext'; import { CalculateConsistencyUseCase } from '../../../../core/rating/application/use-cases/CalculateConsistencyUseCase'; import { GetConsistencyScoreUseCase } from '../../../../core/rating/application/use-cases/GetConsistencyScoreUseCase'; import { GetConsistencyTrendUseCase } from '../../../../core/rating/application/use-cases/GetConsistencyTrendUseCase'; 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 Consistency Use Cases', () => { let context: RatingTestContext; let calculateConsistencyUseCase: CalculateConsistencyUseCase; let getConsistencyScoreUseCase: GetConsistencyScoreUseCase; let getConsistencyTrendUseCase: GetConsistencyTrendUseCase; beforeAll(() => { context = RatingTestContext.create(); calculateConsistencyUseCase = new CalculateConsistencyUseCase( context.driverRepository, context.raceRepository, context.resultRepository, context.eventPublisher ); getConsistencyScoreUseCase = new GetConsistencyScoreUseCase( context.driverRepository, context.resultRepository ); getConsistencyTrendUseCase = new GetConsistencyTrendUseCase( context.driverRepository, context.resultRepository ); }); beforeEach(async () => { await context.clear(); }); describe('CalculateConsistencyUseCase', () => { describe('UseCase - Success Path', () => { it('should calculate consistency for driver with consistent results', 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 consistent 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); 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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.execute({ driverId, raceId: 'r5' }); // Then: The consistency should be calculated expect(result.isOk()).toBe(true); const consistency = result.unwrap(); expect(consistency.driverId.toString()).toBe(driverId); expect(consistency.consistencyScore).toBeGreaterThan(0); expect(consistency.consistencyScore).toBeGreaterThan(50); expect(consistency.raceCount).toBe(5); expect(consistency.positionVariance).toBeGreaterThan(0); expect(consistency.positionVariance).toBeLessThan(10); }); it('should calculate consistency for driver with varying results', 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 varying results const leagueId = 'l1'; const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' }); await context.leagueRepository.create(league); const positions = [1, 10, 3, 15, 5]; 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: positions[i - 1], lapsCompleted: 20, totalTime: 3600, fastestLap: 105, points: 25 - (positions[i - 1] * 2), incidents: 1, startPosition: positions[i - 1] }); await context.resultRepository.create(result); } // When: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.execute({ driverId, raceId: 'r5' }); // Then: The consistency should be calculated expect(result.isOk()).toBe(true); const consistency = result.unwrap(); expect(consistency.driverId.toString()).toBe(driverId); expect(consistency.consistencyScore).toBeGreaterThan(0); expect(consistency.consistencyScore).toBeLessThan(50); expect(consistency.raceCount).toBe(5); expect(consistency.positionVariance).toBeGreaterThan(10); }); it('should calculate consistency 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 consistency 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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.execute({ driverId, raceId: 'r3' }); // Then: The consistency should be calculated expect(result.isOk()).toBe(true); const consistency = result.unwrap(); expect(consistency.driverId.toString()).toBe(driverId); expect(consistency.consistencyScore).toBeGreaterThan(0); expect(consistency.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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.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: CalculateConsistencyUseCase.execute() is called const result = await calculateConsistencyUseCase.execute({ driverId, raceId: 'r999' }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetConsistencyScoreUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve consistency 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: GetConsistencyScoreUseCase.execute() is called const result = await getConsistencyScoreUseCase.execute({ driverId }); // Then: The consistency score should be retrieved expect(result.isOk()).toBe(true); const consistency = result.unwrap(); expect(consistency.driverId.toString()).toBe(driverId); expect(consistency.consistencyScore).toBeGreaterThan(0); expect(consistency.raceCount).toBe(5); expect(consistency.positionVariance).toBeGreaterThan(0); }); it('should retrieve consistency 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: GetConsistencyScoreUseCase.execute() is called with limit const result = await getConsistencyScoreUseCase.execute({ driverId, limit: 5 }); // Then: The consistency score should be retrieved with limit expect(result.isOk()).toBe(true); const consistency = result.unwrap(); expect(consistency.driverId.toString()).toBe(driverId); expect(consistency.raceCount).toBe(5); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetConsistencyScoreUseCase.execute() is called const result = await getConsistencyScoreUseCase.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: GetConsistencyScoreUseCase.execute() is called const result = await getConsistencyScoreUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetConsistencyTrendUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve consistency 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: GetConsistencyTrendUseCase.execute() is called const result = await getConsistencyTrendUseCase.execute({ driverId }); // Then: The consistency 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.averageConsistency).toBeGreaterThan(0); expect(trend.improvement).toBeDefined(); }); it('should retrieve consistency 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: GetConsistencyTrendUseCase.execute() is called with period const result = await getConsistencyTrendUseCase.execute({ driverId, period: 7 }); // Then: The consistency 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); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetConsistencyTrendUseCase.execute() is called const result = await getConsistencyTrendUseCase.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: GetConsistencyTrendUseCase.execute() is called const result = await getConsistencyTrendUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); });