integration tests
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m51s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped

This commit is contained in:
2026-01-24 01:13:49 +01:00
parent 9bb6b228f1
commit 9ccecbf3bb
25 changed files with 895 additions and 2688 deletions

View File

@@ -1,31 +1,21 @@
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';
import { DriverId } from '../../../../core/racing/domain/entities/DriverId';
import { RaceId } from '../../../../core/racing/domain/entities/RaceId';
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
);
getRatingLeaderboardUseCase = new GetRatingLeaderboardUseCase({
driverRepository: context.driverRepository,
ratingRepository: context.ratingRepository
});
});
beforeEach(async () => {
@@ -48,19 +38,22 @@ describe('Rating Leaderboard Use Cases', () => {
// Given: Ratings for each driver
const ratings = [
Rating.create({
driverId: 'd1',
driverId: DriverId.create('d1'),
raceId: RaceId.create('r1'),
rating: 1500,
components: { resultsStrength: 80, consistency: 75, cleanDriving: 90, racecraft: 85, reliability: 95, teamContribution: 70 },
timestamp: new Date()
}),
Rating.create({
driverId: 'd2',
driverId: DriverId.create('d2'),
raceId: RaceId.create('r1'),
rating: 1600,
components: { resultsStrength: 85, consistency: 80, cleanDriving: 92, racecraft: 88, reliability: 96, teamContribution: 75 },
timestamp: new Date()
}),
Rating.create({
driverId: 'd3',
driverId: DriverId.create('d3'),
raceId: RaceId.create('r1'),
rating: 1400,
components: { resultsStrength: 75, consistency: 70, cleanDriving: 88, racecraft: 82, reliability: 93, teamContribution: 65 },
timestamp: new Date()
@@ -74,413 +67,11 @@ describe('Rating Leaderboard Use Cases', () => {
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);
expect(result).toHaveLength(3);
expect(result[0].driverId.toString()).toBe('d2'); // Highest rating
expect(result[0].rating).toBe(1600);
expect(result[1].driverId.toString()).toBe('d1');
expect(result[2].driverId.toString()).toBe('d3');
});
});
});