Files
gridpilot.gg/core/rating/application/use-cases/CalculateRatingUseCase.test.ts
Marc Mintel bf2c0fdb0c
Some checks failed
CI / lint-typecheck (pull_request) Failing after 1m29s
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
code quality
2026-01-26 01:54:57 +01:00

353 lines
10 KiB
TypeScript

/**
* Unit tests for CalculateRatingUseCase
*
* Tests business logic and orchestration using mocked ports.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CalculateRatingUseCase } from './CalculateRatingUseCase';
import { Driver } from '../../../racing/domain/entities/Driver';
import { Race } from '../../../racing/domain/entities/Race';
import { Result } from '../../../racing/domain/entities/result/Result';
// Mock repositories and publisher
const mockDriverRepository = {
findById: vi.fn(),
};
const mockRaceRepository = {
findById: vi.fn(),
};
const mockResultRepository = {
findByRaceId: vi.fn(),
};
const mockRatingRepository = {
save: vi.fn(),
};
const mockEventPublisher = {
publish: vi.fn(),
};
describe('CalculateRatingUseCase', () => {
let useCase: CalculateRatingUseCase;
beforeEach(() => {
vi.clearAllMocks();
useCase = new CalculateRatingUseCase({
driverRepository: mockDriverRepository as any,
raceRepository: mockRaceRepository as any,
resultRepository: mockResultRepository as any,
ratingRepository: mockRatingRepository as any,
eventPublisher: mockEventPublisher as any,
});
});
describe('Scenario 1: Driver missing', () => {
it('should return error when driver is not found', async () => {
// Given
mockDriverRepository.findById.mockResolvedValue(null);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toBe('Driver not found');
expect(mockRatingRepository.save).not.toHaveBeenCalled();
});
});
describe('Scenario 2: Race missing', () => {
it('should return error when race is not found', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(null);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toBe('Race not found');
});
});
describe('Scenario 3: No results', () => {
it('should return error when no results found for race', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([]);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toBe('No results found for race');
});
});
describe('Scenario 4: Driver not present in results', () => {
it('should return error when driver is not in race results', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
const otherResult = Result.create({
id: 'result-1',
raceId: 'race-456',
driverId: 'driver-456',
position: 1,
fastestLap: 60000,
incidents: 0,
startPosition: 1,
points: 25,
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([otherResult]);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().message).toBe('Driver not found in race results');
});
});
describe('Scenario 5: Publishes event after save', () => {
it('should call ratingRepository.save before eventPublisher.publish', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
const mockResult = Result.create({
id: 'result-1',
raceId: 'race-456',
driverId: 'driver-123',
position: 1,
fastestLap: 60000,
incidents: 0,
startPosition: 1,
points: 25,
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
mockRatingRepository.save.mockResolvedValue(undefined);
mockEventPublisher.publish.mockResolvedValue(undefined);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isOk()).toBe(true);
expect(mockRatingRepository.save).toHaveBeenCalledTimes(1);
expect(mockEventPublisher.publish).toHaveBeenCalledTimes(1);
// Verify call order: save should be called before publish
const saveCallOrder = mockRatingRepository.save.mock.invocationCallOrder[0];
const publishCallOrder = mockEventPublisher.publish.mock.invocationCallOrder[0];
expect(saveCallOrder).toBeLessThan(publishCallOrder);
});
});
describe('Scenario 6: Component boundaries for cleanDriving', () => {
it('should return cleanDriving = 100 when incidents = 0', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
const mockResult = Result.create({
id: 'result-1',
raceId: 'race-456',
driverId: 'driver-123',
position: 1,
fastestLap: 60000,
incidents: 0,
startPosition: 1,
points: 25,
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
mockRatingRepository.save.mockResolvedValue(undefined);
mockEventPublisher.publish.mockResolvedValue(undefined);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isOk()).toBe(true);
const rating = result.unwrap();
expect(rating.components.cleanDriving).toBe(100);
});
it('should return cleanDriving = 20 when incidents >= 5', async () => {
// Given
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
const mockResult = Result.create({
id: 'result-1',
raceId: 'race-456',
driverId: 'driver-123',
position: 1,
fastestLap: 60000,
incidents: 5,
startPosition: 1,
points: 25,
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
mockRatingRepository.save.mockResolvedValue(undefined);
mockEventPublisher.publish.mockResolvedValue(undefined);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isOk()).toBe(true);
const rating = result.unwrap();
expect(rating.components.cleanDriving).toBe(20);
});
});
describe('Scenario 7: Time-dependent output', () => {
it('should produce deterministic timestamp when time is frozen', async () => {
// Given
const frozenTime = new Date('2024-01-01T12:00:00.000Z');
vi.useFakeTimers();
vi.setSystemTime(frozenTime);
const mockDriver = Driver.create({
id: 'driver-123',
iracingId: 'iracing-123',
name: 'Test Driver',
country: 'US',
});
const mockRace = Race.create({
id: 'race-456',
leagueId: 'league-789',
scheduledAt: new Date(),
track: 'Test Track',
car: 'Test Car',
});
const mockResult = Result.create({
id: 'result-1',
raceId: 'race-456',
driverId: 'driver-123',
position: 1,
fastestLap: 60000,
incidents: 0,
startPosition: 1,
points: 25,
});
mockDriverRepository.findById.mockResolvedValue(mockDriver);
mockRaceRepository.findById.mockResolvedValue(mockRace);
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
mockRatingRepository.save.mockResolvedValue(undefined);
mockEventPublisher.publish.mockResolvedValue(undefined);
// When
const result = await useCase.execute({
driverId: 'driver-123',
raceId: 'race-456',
});
// Then
expect(result.isOk()).toBe(true);
const rating = result.unwrap();
expect(rating.timestamp).toEqual(frozenTime);
vi.useRealTimers();
});
});
});