import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; import { RatingTestContext } from './RatingTestContext'; import { SaveRatingUseCase } from '../../../../core/rating/application/use-cases/SaveRatingUseCase'; import { GetRatingUseCase } from '../../../../core/rating/application/use-cases/GetRatingUseCase'; import { GetRatingHistoryUseCase } from '../../../../core/rating/application/use-cases/GetRatingHistoryUseCase'; import { GetRatingTrendUseCase } from '../../../../core/rating/application/use-cases/GetRatingTrendUseCase'; import { Driver } from '../../../../core/racing/domain/entities/Driver'; import { Rating } from '../../../../core/rating/domain/entities/Rating'; describe('Rating Persistence Use Cases', () => { let context: RatingTestContext; let saveRatingUseCase: SaveRatingUseCase; let getRatingUseCase: GetRatingUseCase; let getRatingHistoryUseCase: GetRatingHistoryUseCase; let getRatingTrendUseCase: GetRatingTrendUseCase; beforeAll(() => { context = RatingTestContext.create(); saveRatingUseCase = new SaveRatingUseCase( context.ratingRepository, context.eventPublisher ); getRatingUseCase = new GetRatingUseCase( context.ratingRepository ); getRatingHistoryUseCase = new GetRatingHistoryUseCase( context.ratingRepository ); getRatingTrendUseCase = new GetRatingTrendUseCase( context.ratingRepository ); }); beforeEach(async () => { await context.clear(); }); describe('SaveRatingUseCase', () => { describe('UseCase - Success Path', () => { it('should save rating successfully', 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: A rating to save const rating = Rating.create({ driverId, rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); // When: SaveRatingUseCase.execute() is called const result = await saveRatingUseCase.execute({ rating }); // Then: The rating should be saved expect(result.isOk()).toBe(true); // And: EventPublisher should emit RatingSavedEvent expect(context.eventPublisher.events).toContainEqual( expect.objectContaining({ type: 'RatingSavedEvent' }) ); }); it('should update existing rating', 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: An initial rating const initialRating = Rating.create({ driverId, rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date(Date.now() - 86400000) }); await context.ratingRepository.save(initialRating); // Given: An updated rating const updatedRating = Rating.create({ driverId, rating: 1550, components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 }, timestamp: new Date() }); // When: SaveRatingUseCase.execute() is called const result = await saveRatingUseCase.execute({ rating: updatedRating }); // Then: The rating should be updated expect(result.isOk()).toBe(true); // And: EventPublisher should emit RatingUpdatedEvent expect(context.eventPublisher.events).toContainEqual( expect.objectContaining({ type: 'RatingUpdatedEvent' }) ); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A rating with non-existent driver const rating = Rating.create({ driverId: 'd999', rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); // When: SaveRatingUseCase.execute() is called const result = await saveRatingUseCase.execute({ rating }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetRatingUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve rating 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: A saved rating const rating = Rating.create({ driverId, rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); await context.ratingRepository.save(rating); // When: GetRatingUseCase.execute() is called const result = await getRatingUseCase.execute({ driverId }); // Then: The rating should be retrieved expect(result.isOk()).toBe(true); const retrievedRating = result.unwrap(); expect(retrievedRating.driverId.toString()).toBe(driverId); expect(retrievedRating.rating).toBe(1500); expect(retrievedRating.components.resultsStrength).toBe(80); }); it('should return latest rating 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 ratings for the same driver const oldRating = Rating.create({ driverId, rating: 1400, components: { resultsStrength: 70, consistency: 65, cleanDriving: 80, racecraft: 75, reliability: 85, teamContribution: 60 }, timestamp: new Date(Date.now() - 86400000) }); await context.ratingRepository.save(oldRating); const newRating = Rating.create({ driverId, rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); await context.ratingRepository.save(newRating); // When: GetRatingUseCase.execute() is called const result = await getRatingUseCase.execute({ driverId }); // Then: The latest rating should be retrieved expect(result.isOk()).toBe(true); const retrievedRating = result.unwrap(); expect(retrievedRating.driverId.toString()).toBe(driverId); expect(retrievedRating.rating).toBe(1500); expect(retrievedRating.timestamp.getTime()).toBe(newRating.timestamp.getTime()); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetRatingUseCase.execute() is called const result = await getRatingUseCase.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: GetRatingUseCase.execute() is called const result = await getRatingUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetRatingHistoryUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve rating history 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 ratings for the same driver for (let i = 1; i <= 5; i++) { const rating = Rating.create({ driverId, rating: 1400 + (i * 20), components: { resultsStrength: 70 + (i * 2), consistency: 65 + (i * 2), cleanDriving: 80 + (i * 2), racecraft: 75 + (i * 2), reliability: 85 + (i * 2), teamContribution: 60 + (i * 2) }, timestamp: new Date(Date.now() - (i * 86400000)) }); await context.ratingRepository.save(rating); } // When: GetRatingHistoryUseCase.execute() is called const result = await getRatingHistoryUseCase.execute({ driverId }); // Then: The rating history should be retrieved expect(result.isOk()).toBe(true); const history = result.unwrap(); expect(history).toHaveLength(5); expect(history[0].rating).toBe(1500); expect(history[4].rating).toBe(1420); }); it('should retrieve rating history with 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 ratings for the same driver for (let i = 1; i <= 10; i++) { const rating = Rating.create({ driverId, rating: 1400 + (i * 10), components: { resultsStrength: 70 + i, consistency: 65 + i, cleanDriving: 80 + i, racecraft: 75 + i, reliability: 85 + i, teamContribution: 60 + i }, timestamp: new Date(Date.now() - (i * 86400000)) }); await context.ratingRepository.save(rating); } // When: GetRatingHistoryUseCase.execute() is called with limit const result = await getRatingHistoryUseCase.execute({ driverId, limit: 5 }); // Then: The rating history should be retrieved with limit expect(result.isOk()).toBe(true); const history = result.unwrap(); expect(history).toHaveLength(5); }); }); describe('UseCase - Error Handling', () => { it('should handle missing driver', async () => { // Given: A non-existent driver const driverId = 'd999'; // When: GetRatingHistoryUseCase.execute() is called const result = await getRatingHistoryUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with no rating history', 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: GetRatingHistoryUseCase.execute() is called const result = await getRatingHistoryUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); describe('GetRatingTrendUseCase', () => { describe('UseCase - Success Path', () => { it('should retrieve rating 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 ratings for the same driver for (let i = 1; i <= 5; i++) { const rating = Rating.create({ driverId, rating: 1400 + (i * 20), components: { resultsStrength: 70 + (i * 2), consistency: 65 + (i * 2), cleanDriving: 80 + (i * 2), racecraft: 75 + (i * 2), reliability: 85 + (i * 2), teamContribution: 60 + (i * 2) }, timestamp: new Date(Date.now() - (i * 86400000)) }); await context.ratingRepository.save(rating); } // When: GetRatingTrendUseCase.execute() is called const result = await getRatingTrendUseCase.execute({ driverId }); // Then: The rating 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.change).toBeGreaterThan(0); expect(trend.changePercentage).toBeGreaterThan(0); }); it('should calculate 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 ratings for the same driver for (let i = 1; i <= 10; i++) { const rating = Rating.create({ driverId, rating: 1400 + (i * 10), components: { resultsStrength: 70 + i, consistency: 65 + i, cleanDriving: 80 + i, racecraft: 75 + i, reliability: 85 + i, teamContribution: 60 + i }, timestamp: new Date(Date.now() - (i * 86400000)) }); await context.ratingRepository.save(rating); } // When: GetRatingTrendUseCase.execute() is called with period const result = await getRatingTrendUseCase.execute({ driverId, period: 7 }); // Then: The rating 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: GetRatingTrendUseCase.execute() is called const result = await getRatingTrendUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); it('should handle driver with insufficient rating history', async () => { // Given: A driver with only one rating const driverId = 'd1'; const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' }); await context.driverRepository.create(driver); const rating = Rating.create({ driverId, rating: 1500, components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 }, timestamp: new Date() }); await context.ratingRepository.save(rating); // When: GetRatingTrendUseCase.execute() is called const result = await getRatingTrendUseCase.execute({ driverId }); // Then: The result should be an error expect(result.isErr()).toBe(true); }); }); }); });