Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m50s
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
474 lines
16 KiB
TypeScript
474 lines
16 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|
|
});
|