integration tests
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 4m46s
Contract Testing / contract-snapshot (pull_request) Has been skipped

This commit is contained in:
2026-01-22 19:16:43 +01:00
parent 597bb48248
commit 2fba80da57
25 changed files with 5143 additions and 7496 deletions

View File

@@ -3,767 +3,143 @@
*
* Tests the orchestration logic of race detail page-related Use Cases:
* - GetRaceDetailUseCase: Retrieves comprehensive race details
* - GetRaceParticipantsUseCase: Retrieves race participants count
* - GetRaceWinnerUseCase: Retrieves race winner and podium
* - GetRaceStatisticsUseCase: Retrieves race statistics
* - GetRaceLapTimesUseCase: Retrieves race lap times
* - GetRaceQualifyingUseCase: Retrieves race qualifying results
* - GetRacePointsUseCase: Retrieves race points distribution
* - GetRaceHighlightsUseCase: Retrieves race highlights
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Adheres to Clean Architecture:
* - Tests Core Use Cases directly
* - Uses In-Memory adapters for repositories
* - Follows Given/When/Then pattern
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/races/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
import { GetRaceDetailUseCase } from '../../../core/races/use-cases/GetRaceDetailUseCase';
import { GetRaceParticipantsUseCase } from '../../../core/races/use-cases/GetRaceParticipantsUseCase';
import { GetRaceWinnerUseCase } from '../../../core/races/use-cases/GetRaceWinnerUseCase';
import { GetRaceStatisticsUseCase } from '../../../core/races/use-cases/GetRaceStatisticsUseCase';
import { GetRaceLapTimesUseCase } from '../../../core/races/use-cases/GetRaceLapTimesUseCase';
import { GetRaceQualifyingUseCase } from '../../../core/races/use-cases/GetRaceQualifyingUseCase';
import { GetRacePointsUseCase } from '../../../core/races/use-cases/GetRacePointsUseCase';
import { GetRaceHighlightsUseCase } from '../../../core/races/use-cases/GetRaceHighlightsUseCase';
import { RaceDetailQuery } from '../../../core/races/ports/RaceDetailQuery';
import { RaceParticipantsQuery } from '../../../core/races/ports/RaceParticipantsQuery';
import { RaceWinnerQuery } from '../../../core/races/ports/RaceWinnerQuery';
import { RaceStatisticsQuery } from '../../../core/races/ports/RaceStatisticsQuery';
import { RaceLapTimesQuery } from '../../../core/races/ports/RaceLapTimesQuery';
import { RaceQualifyingQuery } from '../../../core/races/ports/RaceQualifyingQuery';
import { RacePointsQuery } from '../../../core/races/ports/RacePointsQuery';
import { RaceHighlightsQuery } from '../../../core/races/ports/RaceHighlightsQuery';
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryRaceRegistrationRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRegistrationRepository';
import { InMemoryResultRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryResultRepository';
import { InMemoryLeagueMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository';
import { GetRaceDetailUseCase } from '../../../core/racing/application/use-cases/GetRaceDetailUseCase';
import { Race } from '../../../core/racing/domain/entities/Race';
import { League } from '../../../core/racing/domain/entities/League';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Race Detail Use Case Orchestration', () => {
let raceRepository: InMemoryRaceRepository;
let eventPublisher: InMemoryEventPublisher;
let leagueRepository: InMemoryLeagueRepository;
let driverRepository: InMemoryDriverRepository;
let raceRegistrationRepository: InMemoryRaceRegistrationRepository;
let resultRepository: InMemoryResultRepository;
let leagueMembershipRepository: InMemoryLeagueMembershipRepository;
let getRaceDetailUseCase: GetRaceDetailUseCase;
let getRaceParticipantsUseCase: GetRaceParticipantsUseCase;
let getRaceWinnerUseCase: GetRaceWinnerUseCase;
let getRaceStatisticsUseCase: GetRaceStatisticsUseCase;
let getRaceLapTimesUseCase: GetRaceLapTimesUseCase;
let getRaceQualifyingUseCase: GetRaceQualifyingUseCase;
let getRacePointsUseCase: GetRacePointsUseCase;
let getRaceHighlightsUseCase: GetRaceHighlightsUseCase;
let mockLogger: Logger;
beforeAll(() => {
// TODO: Initialize In-Memory repositories and event publisher
// raceRepository = new InMemoryRaceRepository();
// eventPublisher = new InMemoryEventPublisher();
// getRaceDetailUseCase = new GetRaceDetailUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceParticipantsUseCase = new GetRaceParticipantsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceWinnerUseCase = new GetRaceWinnerUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceStatisticsUseCase = new GetRaceStatisticsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceLapTimesUseCase = new GetRaceLapTimesUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceQualifyingUseCase = new GetRaceQualifyingUseCase({
// raceRepository,
// eventPublisher,
// });
// getRacePointsUseCase = new GetRacePointsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceHighlightsUseCase = new GetRaceHighlightsUseCase({
// raceRepository,
// eventPublisher,
// });
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
raceRepository = new InMemoryRaceRepository(mockLogger);
leagueRepository = new InMemoryLeagueRepository(mockLogger);
driverRepository = new InMemoryDriverRepository(mockLogger);
raceRegistrationRepository = new InMemoryRaceRegistrationRepository(mockLogger);
resultRepository = new InMemoryResultRepository(mockLogger, raceRepository);
leagueMembershipRepository = new InMemoryLeagueMembershipRepository(mockLogger);
getRaceDetailUseCase = new GetRaceDetailUseCase(
raceRepository,
leagueRepository,
driverRepository,
raceRegistrationRepository,
resultRepository,
leagueMembershipRepository
);
});
beforeEach(() => {
// TODO: Clear all In-Memory repositories before each test
// raceRepository.clear();
// eventPublisher.clear();
beforeEach(async () => {
// Clear repositories
(raceRepository as any).races.clear();
leagueRepository.clear();
await driverRepository.clear();
(raceRegistrationRepository as any).registrations.clear();
(resultRepository as any).results.clear();
leagueMembershipRepository.clear();
});
describe('GetRaceDetailUseCase - Success Path', () => {
describe('GetRaceDetailUseCase', () => {
it('should retrieve race detail with complete information', async () => {
// TODO: Implement test
// Scenario: Driver views race detail
// Given: A race exists with complete information
// And: The race has track, car, league, date, time, duration, status
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain complete race information
// And: EventPublisher should emit RaceDetailAccessedEvent
// Given: A race and league exist
const leagueId = 'l1';
const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' });
await leagueRepository.create(league);
const raceId = 'r1';
const race = Race.create({
id: raceId,
leagueId,
scheduledAt: new Date(Date.now() + 86400000),
track: 'Spa',
car: 'GT3',
status: 'scheduled'
});
await raceRepository.create(race);
// When: GetRaceDetailUseCase.execute() is called
const result = await getRaceDetailUseCase.execute({ raceId });
// Then: The result should contain race and league information
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.race.id).toBe(raceId);
expect(data.league?.id).toBe(leagueId);
expect(data.isUserRegistered).toBe(false);
});
it('should retrieve race detail with track layout', async () => {
// TODO: Implement test
// Scenario: Race with track layout
// Given: A race exists with track layout
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show track layout
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with weather information', async () => {
// TODO: Implement test
// Scenario: Race with weather information
// Given: A race exists with weather information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show weather information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with race conditions', async () => {
// TODO: Implement test
// Scenario: Race with conditions
// Given: A race exists with conditions
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show race conditions
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with description', async () => {
// TODO: Implement test
// Scenario: Race with description
// Given: A race exists with description
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show description
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with rules', async () => {
// TODO: Implement test
// Scenario: Race with rules
// Given: A race exists with rules
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show rules
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with requirements', async () => {
// TODO: Implement test
// Scenario: Race with requirements
// Given: A race exists with requirements
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show requirements
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with page title', async () => {
// TODO: Implement test
// Scenario: Race with page title
// Given: A race exists
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should include page title
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with page description', async () => {
// TODO: Implement test
// Scenario: Race with page description
// Given: A race exists
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should include page description
// And: EventPublisher should emit RaceDetailAccessedEvent
});
});
describe('GetRaceDetailUseCase - Edge Cases', () => {
it('should handle race with missing track information', async () => {
// TODO: Implement test
// Scenario: Race with missing track data
// Given: A race exists with missing track information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with missing car information', async () => {
// TODO: Implement test
// Scenario: Race with missing car data
// Given: A race exists with missing car information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with missing league information', async () => {
// TODO: Implement test
// Scenario: Race with missing league data
// Given: A race exists with missing league information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no description', async () => {
// TODO: Implement test
// Scenario: Race with no description
// Given: A race exists with no description
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default description
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no rules', async () => {
// TODO: Implement test
// Scenario: Race with no rules
// Given: A race exists with no rules
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default rules
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no requirements', async () => {
// TODO: Implement test
// Scenario: Race with no requirements
// Given: A race exists with no requirements
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default requirements
// And: EventPublisher should emit RaceDetailAccessedEvent
});
});
describe('GetRaceDetailUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceDetailUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
const result = await getRaceDetailUseCase.execute({ raceId: 'non-existent' });
// Then: Should return RACE_NOT_FOUND error
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
});
it('should throw error when race ID is invalid', async () => {
// TODO: Implement test
// Scenario: Invalid race ID
// Given: An invalid race ID (e.g., empty string, null, undefined)
// When: GetRaceDetailUseCase.execute() is called with invalid race ID
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should identify if a driver is registered', async () => {
// Given: A race and a registered driver
const leagueId = 'l1';
const raceId = 'r1';
const driverId = 'd1';
const race = Race.create({
id: raceId,
leagueId,
scheduledAt: new Date(Date.now() + 86400000),
track: 'Spa',
car: 'GT3',
status: 'scheduled'
});
await raceRepository.create(race);
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: A race exists
// And: RaceRepository throws an error during query
// When: GetRaceDetailUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' });
await driverRepository.create(driver);
describe('GetRaceParticipantsUseCase - Success Path', () => {
it('should retrieve race participants count', async () => {
// TODO: Implement test
// Scenario: Race with participants
// Given: A race exists with participants
// When: GetRaceParticipantsUseCase.execute() is called with race ID
// Then: The result should show participants count
// And: EventPublisher should emit RaceParticipantsAccessedEvent
});
// Mock registration (using any to bypass private access if needed, but InMemoryRaceRegistrationRepository has register method)
await raceRegistrationRepository.register({
raceId: raceId as any,
driverId: driverId as any,
registeredAt: new Date()
} as any);
it('should retrieve race participants count for race with no participants', async () => {
// TODO: Implement test
// Scenario: Race with no participants
// Given: A race exists with no participants
// When: GetRaceParticipantsUseCase.execute() is called with race ID
// Then: The result should show 0 participants
// And: EventPublisher should emit RaceParticipantsAccessedEvent
});
// When: GetRaceDetailUseCase.execute() is called with driverId
const result = await getRaceDetailUseCase.execute({ raceId, driverId });
it('should retrieve race participants count for upcoming race', async () => {
// TODO: Implement test
// Scenario: Upcoming race with participants
// Given: An upcoming race exists with participants
// When: GetRaceParticipantsUseCase.execute() is called with race ID
// Then: The result should show participants count
// And: EventPublisher should emit RaceParticipantsAccessedEvent
});
it('should retrieve race participants count for completed race', async () => {
// TODO: Implement test
// Scenario: Completed race with participants
// Given: A completed race exists with participants
// When: GetRaceParticipantsUseCase.execute() is called with race ID
// Then: The result should show participants count
// And: EventPublisher should emit RaceParticipantsAccessedEvent
});
});
describe('GetRaceParticipantsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceParticipantsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceParticipantsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceWinnerUseCase - Success Path', () => {
it('should retrieve race winner for completed race', async () => {
// TODO: Implement test
// Scenario: Completed race with winner
// Given: A completed race exists with winner
// When: GetRaceWinnerUseCase.execute() is called with race ID
// Then: The result should show race winner
// And: EventPublisher should emit RaceWinnerAccessedEvent
});
it('should retrieve race podium for completed race', async () => {
// TODO: Implement test
// Scenario: Completed race with podium
// Given: A completed race exists with podium
// When: GetRaceWinnerUseCase.execute() is called with race ID
// Then: The result should show top 3 finishers
// And: EventPublisher should emit RaceWinnerAccessedEvent
});
it('should not retrieve winner for upcoming race', async () => {
// TODO: Implement test
// Scenario: Upcoming race without winner
// Given: An upcoming race exists
// When: GetRaceWinnerUseCase.execute() is called with race ID
// Then: The result should not show winner or podium
// And: EventPublisher should emit RaceWinnerAccessedEvent
});
it('should not retrieve winner for in-progress race', async () => {
// TODO: Implement test
// Scenario: In-progress race without winner
// Given: An in-progress race exists
// When: GetRaceWinnerUseCase.execute() is called with race ID
// Then: The result should not show winner or podium
// And: EventPublisher should emit RaceWinnerAccessedEvent
});
});
describe('GetRaceWinnerUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceWinnerUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceWinnerUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceStatisticsUseCase - Success Path', () => {
it('should retrieve race statistics with lap count', async () => {
// TODO: Implement test
// Scenario: Race with lap count
// Given: A race exists with lap count
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show lap count
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with incidents count', async () => {
// TODO: Implement test
// Scenario: Race with incidents count
// Given: A race exists with incidents count
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show incidents count
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with penalties count', async () => {
// TODO: Implement test
// Scenario: Race with penalties count
// Given: A race exists with penalties count
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show penalties count
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with protests count', async () => {
// TODO: Implement test
// Scenario: Race with protests count
// Given: A race exists with protests count
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show protests count
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with stewarding actions count', async () => {
// TODO: Implement test
// Scenario: Race with stewarding actions count
// Given: A race exists with stewarding actions count
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show stewarding actions count
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with all metrics', async () => {
// TODO: Implement test
// Scenario: Race with all statistics
// Given: A race exists with all statistics
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show all statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with empty metrics', async () => {
// TODO: Implement test
// Scenario: Race with no statistics
// Given: A race exists with no statistics
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show empty or default statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
});
describe('GetRaceStatisticsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceStatisticsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceStatisticsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceLapTimesUseCase - Success Path', () => {
it('should retrieve race lap times with average lap time', async () => {
// TODO: Implement test
// Scenario: Race with average lap time
// Given: A race exists with average lap time
// When: GetRaceLapTimesUseCase.execute() is called with race ID
// Then: The result should show average lap time
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
it('should retrieve race lap times with fastest lap', async () => {
// TODO: Implement test
// Scenario: Race with fastest lap
// Given: A race exists with fastest lap
// When: GetRaceLapTimesUseCase.execute() is called with race ID
// Then: The result should show fastest lap
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
it('should retrieve race lap times with best sector times', async () => {
// TODO: Implement test
// Scenario: Race with best sector times
// Given: A race exists with best sector times
// When: GetRaceLapTimesUseCase.execute() is called with race ID
// Then: The result should show best sector times
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
it('should retrieve race lap times with all metrics', async () => {
// TODO: Implement test
// Scenario: Race with all lap time metrics
// Given: A race exists with all lap time metrics
// When: GetRaceLapTimesUseCase.execute() is called with race ID
// Then: The result should show all lap time metrics
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
it('should retrieve race lap times with empty metrics', async () => {
// TODO: Implement test
// Scenario: Race with no lap times
// Given: A race exists with no lap times
// When: GetRaceLapTimesUseCase.execute() is called with race ID
// Then: The result should show empty or default lap times
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
});
describe('GetRaceLapTimesUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceLapTimesUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceLapTimesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceQualifyingUseCase - Success Path', () => {
it('should retrieve race qualifying results', async () => {
// TODO: Implement test
// Scenario: Race with qualifying results
// Given: A race exists with qualifying results
// When: GetRaceQualifyingUseCase.execute() is called with race ID
// Then: The result should show qualifying results
// And: EventPublisher should emit RaceQualifyingAccessedEvent
});
it('should retrieve race starting grid', async () => {
// TODO: Implement test
// Scenario: Race with starting grid
// Given: A race exists with starting grid
// When: GetRaceQualifyingUseCase.execute() is called with race ID
// Then: The result should show starting grid
// And: EventPublisher should emit RaceQualifyingAccessedEvent
});
it('should retrieve race qualifying results with pole position', async () => {
// TODO: Implement test
// Scenario: Race with pole position
// Given: A race exists with pole position
// When: GetRaceQualifyingUseCase.execute() is called with race ID
// Then: The result should show pole position
// And: EventPublisher should emit RaceQualifyingAccessedEvent
});
it('should retrieve race qualifying results with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no qualifying results
// Given: A race exists with no qualifying results
// When: GetRaceQualifyingUseCase.execute() is called with race ID
// Then: The result should show empty or default qualifying results
// And: EventPublisher should emit RaceQualifyingAccessedEvent
});
});
describe('GetRaceQualifyingUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceQualifyingUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceQualifyingUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRacePointsUseCase - Success Path', () => {
it('should retrieve race points distribution', async () => {
// TODO: Implement test
// Scenario: Race with points distribution
// Given: A race exists with points distribution
// When: GetRacePointsUseCase.execute() is called with race ID
// Then: The result should show points distribution
// And: EventPublisher should emit RacePointsAccessedEvent
});
it('should retrieve race championship implications', async () => {
// TODO: Implement test
// Scenario: Race with championship implications
// Given: A race exists with championship implications
// When: GetRacePointsUseCase.execute() is called with race ID
// Then: The result should show championship implications
// And: EventPublisher should emit RacePointsAccessedEvent
});
it('should retrieve race points with empty distribution', async () => {
// TODO: Implement test
// Scenario: Race with no points distribution
// Given: A race exists with no points distribution
// When: GetRacePointsUseCase.execute() is called with race ID
// Then: The result should show empty or default points distribution
// And: EventPublisher should emit RacePointsAccessedEvent
});
});
describe('GetRacePointsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRacePointsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRacePointsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceHighlightsUseCase - Success Path', () => {
it('should retrieve race highlights', async () => {
// TODO: Implement test
// Scenario: Race with highlights
// Given: A race exists with highlights
// When: GetRaceHighlightsUseCase.execute() is called with race ID
// Then: The result should show highlights
// And: EventPublisher should emit RaceHighlightsAccessedEvent
});
it('should retrieve race video link', async () => {
// TODO: Implement test
// Scenario: Race with video link
// Given: A race exists with video link
// When: GetRaceHighlightsUseCase.execute() is called with race ID
// Then: The result should show video link
// And: EventPublisher should emit RaceHighlightsAccessedEvent
});
it('should retrieve race gallery', async () => {
// TODO: Implement test
// Scenario: Race with gallery
// Given: A race exists with gallery
// When: GetRaceHighlightsUseCase.execute() is called with race ID
// Then: The result should show gallery
// And: EventPublisher should emit RaceHighlightsAccessedEvent
});
it('should retrieve race highlights with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no highlights
// Given: A race exists with no highlights
// When: GetRaceHighlightsUseCase.execute() is called with race ID
// Then: The result should show empty or default highlights
// And: EventPublisher should emit RaceHighlightsAccessedEvent
});
});
describe('GetRaceHighlightsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceHighlightsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceHighlightsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('Race Detail Page Data Orchestration', () => {
it('should correctly orchestrate data for race detail page', async () => {
// TODO: Implement test
// Scenario: Race detail page data orchestration
// Given: A race exists with all information
// When: Multiple use cases are executed for the same race
// Then: Each use case should return its respective data
// And: EventPublisher should emit appropriate events for each use case
});
it('should correctly format race information for display', async () => {
// TODO: Implement test
// Scenario: Race information formatting
// Given: A race exists with all information
// When: GetRaceDetailUseCase.execute() is called
// Then: The result should format:
// - Track name: Clearly displayed
// - Car: Clearly displayed
// - League: Clearly displayed
// - Date: Formatted correctly
// - Time: Formatted correctly
// - Duration: Formatted correctly
// - Status: Clearly indicated (Upcoming, In Progress, Completed)
});
it('should correctly handle race status transitions', async () => {
// TODO: Implement test
// Scenario: Race status transitions
// Given: A race exists with status "Upcoming"
// When: Race status changes to "In Progress"
// And: GetRaceDetailUseCase.execute() is called
// Then: The result should show the updated status
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should correctly handle race with no statistics', async () => {
// TODO: Implement test
// Scenario: Race with no statistics
// Given: A race exists with no statistics
// When: GetRaceStatisticsUseCase.execute() is called
// Then: The result should show empty or default statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should correctly handle race with no lap times', async () => {
// TODO: Implement test
// Scenario: Race with no lap times
// Given: A race exists with no lap times
// When: GetRaceLapTimesUseCase.execute() is called
// Then: The result should show empty or default lap times
// And: EventPublisher should emit RaceLapTimesAccessedEvent
});
it('should correctly handle race with no qualifying results', async () => {
// TODO: Implement test
// Scenario: Race with no qualifying results
// Given: A race exists with no qualifying results
// When: GetRaceQualifyingUseCase.execute() is called
// Then: The result should show empty or default qualifying results
// And: EventPublisher should emit RaceQualifyingAccessedEvent
});
it('should correctly handle race with no highlights', async () => {
// TODO: Implement test
// Scenario: Race with no highlights
// Given: A race exists with no highlights
// When: GetRaceHighlightsUseCase.execute() is called
// Then: The result should show empty or default highlights
// And: EventPublisher should emit RaceHighlightsAccessedEvent
// Then: isUserRegistered should be true
expect(result.isOk()).toBe(true);
expect(result.unwrap().isUserRegistered).toBe(true);
});
});
});

View File

@@ -2,722 +2,158 @@
* Integration Test: Race Results Use Case Orchestration
*
* Tests the orchestration logic of race results page-related Use Cases:
* - GetRaceResultsUseCase: Retrieves complete race results (all finishers)
* - GetRaceStatisticsUseCase: Retrieves race statistics (fastest lap, average lap time, etc.)
* - GetRaceResultsDetailUseCase: Retrieves complete race results (all finishers)
* - GetRacePenaltiesUseCase: Retrieves race penalties and incidents
* - GetRaceStewardingActionsUseCase: Retrieves race stewarding actions
* - GetRacePointsDistributionUseCase: Retrieves race points distribution
* - GetRaceChampionshipImplicationsUseCase: Retrieves race championship implications
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Adheres to Clean Architecture:
* - Tests Core Use Cases directly
* - Uses In-Memory adapters for repositories
* - Follows Given/When/Then pattern
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/races/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
import { GetRaceResultsUseCase } from '../../../core/races/use-cases/GetRaceResultsUseCase';
import { GetRaceStatisticsUseCase } from '../../../core/races/use-cases/GetRaceStatisticsUseCase';
import { GetRacePenaltiesUseCase } from '../../../core/races/use-cases/GetRacePenaltiesUseCase';
import { GetRaceStewardingActionsUseCase } from '../../../core/races/use-cases/GetRaceStewardingActionsUseCase';
import { GetRacePointsDistributionUseCase } from '../../../core/races/use-cases/GetRacePointsDistributionUseCase';
import { GetRaceChampionshipImplicationsUseCase } from '../../../core/races/use-cases/GetRaceChampionshipImplicationsUseCase';
import { RaceResultsQuery } from '../../../core/races/ports/RaceResultsQuery';
import { RaceStatisticsQuery } from '../../../core/races/ports/RaceStatisticsQuery';
import { RacePenaltiesQuery } from '../../../core/races/ports/RacePenaltiesQuery';
import { RaceStewardingActionsQuery } from '../../../core/races/ports/RaceStewardingActionsQuery';
import { RacePointsDistributionQuery } from '../../../core/races/ports/RacePointsDistributionQuery';
import { RaceChampionshipImplicationsQuery } from '../../../core/races/ports/RaceChampionshipImplicationsQuery';
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryResultRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryResultRepository';
import { InMemoryPenaltyRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryPenaltyRepository';
import { GetRaceResultsDetailUseCase } from '../../../core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import { GetRacePenaltiesUseCase } from '../../../core/racing/application/use-cases/GetRacePenaltiesUseCase';
import { Race } from '../../../core/racing/domain/entities/Race';
import { League } from '../../../core/racing/domain/entities/League';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { Result as RaceResult } from '../../../core/racing/domain/entities/result/Result';
import { Penalty } from '../../../core/racing/domain/entities/penalty/Penalty';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Race Results Use Case Orchestration', () => {
let raceRepository: InMemoryRaceRepository;
let eventPublisher: InMemoryEventPublisher;
let getRaceResultsUseCase: GetRaceResultsUseCase;
let getRaceStatisticsUseCase: GetRaceStatisticsUseCase;
let leagueRepository: InMemoryLeagueRepository;
let driverRepository: InMemoryDriverRepository;
let resultRepository: InMemoryResultRepository;
let penaltyRepository: InMemoryPenaltyRepository;
let getRaceResultsDetailUseCase: GetRaceResultsDetailUseCase;
let getRacePenaltiesUseCase: GetRacePenaltiesUseCase;
let getRaceStewardingActionsUseCase: GetRaceStewardingActionsUseCase;
let getRacePointsDistributionUseCase: GetRacePointsDistributionUseCase;
let getRaceChampionshipImplicationsUseCase: GetRaceChampionshipImplicationsUseCase;
let mockLogger: Logger;
beforeAll(() => {
// TODO: Initialize In-Memory repositories and event publisher
// raceRepository = new InMemoryRaceRepository();
// eventPublisher = new InMemoryEventPublisher();
// getRaceResultsUseCase = new GetRaceResultsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceStatisticsUseCase = new GetRaceStatisticsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRacePenaltiesUseCase = new GetRacePenaltiesUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceStewardingActionsUseCase = new GetRaceStewardingActionsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRacePointsDistributionUseCase = new GetRacePointsDistributionUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceChampionshipImplicationsUseCase = new GetRaceChampionshipImplicationsUseCase({
// raceRepository,
// eventPublisher,
// });
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
raceRepository = new InMemoryRaceRepository(mockLogger);
leagueRepository = new InMemoryLeagueRepository(mockLogger);
driverRepository = new InMemoryDriverRepository(mockLogger);
resultRepository = new InMemoryResultRepository(mockLogger, raceRepository);
penaltyRepository = new InMemoryPenaltyRepository(mockLogger);
getRaceResultsDetailUseCase = new GetRaceResultsDetailUseCase(
raceRepository,
leagueRepository,
resultRepository,
driverRepository,
penaltyRepository
);
getRacePenaltiesUseCase = new GetRacePenaltiesUseCase(
penaltyRepository,
driverRepository
);
});
beforeEach(() => {
// TODO: Clear all In-Memory repositories before each test
// raceRepository.clear();
// eventPublisher.clear();
beforeEach(async () => {
(raceRepository as any).races.clear();
leagueRepository.clear();
await driverRepository.clear();
(resultRepository as any).results.clear();
(penaltyRepository as any).penalties.clear();
});
describe('GetRaceResultsUseCase - Success Path', () => {
describe('GetRaceResultsDetailUseCase', () => {
it('should retrieve complete race results with all finishers', async () => {
// TODO: Implement test
// Scenario: Driver views complete race results
// Given: A completed race exists with multiple finishers
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain all finishers
// And: The list should be ordered by position
// And: EventPublisher should emit RaceResultsAccessedEvent
});
// Given: A completed race with results
const leagueId = 'l1';
const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' });
await leagueRepository.create(league);
it('should retrieve race results with race winner', async () => {
// TODO: Implement test
// Scenario: Race with winner
// Given: A completed race exists with winner
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show race winner
// And: EventPublisher should emit RaceResultsAccessedEvent
});
const raceId = 'r1';
const race = Race.create({
id: raceId,
leagueId,
scheduledAt: new Date(Date.now() - 86400000),
track: 'Spa',
car: 'GT3',
status: 'completed'
});
await raceRepository.create(race);
it('should retrieve race results with podium', async () => {
// TODO: Implement test
// Scenario: Race with podium
// Given: A completed race exists with podium
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show top 3 finishers
// And: EventPublisher should emit RaceResultsAccessedEvent
});
const driverId = 'd1';
const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' });
await driverRepository.create(driver);
it('should retrieve race results with driver information', async () => {
// TODO: Implement test
// Scenario: Race results with driver information
// Given: A completed race exists with driver information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show driver name, team, car
// And: EventPublisher should emit RaceResultsAccessedEvent
});
const raceResult = RaceResult.create({
id: 'res1',
raceId,
driverId,
position: 1,
lapsCompleted: 20,
totalTime: 3600,
fastestLap: 105,
points: 25
});
await resultRepository.create(raceResult);
it('should retrieve race results with position information', async () => {
// TODO: Implement test
// Scenario: Race results with position information
// Given: A completed race exists with position information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show position, race time, gaps
// And: EventPublisher should emit RaceResultsAccessedEvent
});
// When: GetRaceResultsDetailUseCase.execute() is called
const result = await getRaceResultsDetailUseCase.execute({ raceId });
it('should retrieve race results with lap information', async () => {
// TODO: Implement test
// Scenario: Race results with lap information
// Given: A completed race exists with lap information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show laps completed, fastest lap, average lap time
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with points information', async () => {
// TODO: Implement test
// Scenario: Race results with points information
// Given: A completed race exists with points information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show points earned
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with penalties information', async () => {
// TODO: Implement test
// Scenario: Race results with penalties information
// Given: A completed race exists with penalties information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show penalties
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with incidents information', async () => {
// TODO: Implement test
// Scenario: Race results with incidents information
// Given: A completed race exists with incidents information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show incidents
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with stewarding actions information', async () => {
// TODO: Implement test
// Scenario: Race results with stewarding actions information
// Given: A completed race exists with stewarding actions information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show stewarding actions
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with protests information', async () => {
// TODO: Implement test
// Scenario: Race results with protests information
// Given: A completed race exists with protests information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should show protests
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should retrieve race results with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no results
// Given: A race exists with no results
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should be empty
// And: EventPublisher should emit RaceResultsAccessedEvent
// Then: The result should contain race and results
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.race.id).toBe(raceId);
expect(data.results).toHaveLength(1);
expect(data.results[0].driverId.toString()).toBe(driverId);
});
});
describe('GetRaceResultsUseCase - Edge Cases', () => {
it('should handle race with missing driver information', async () => {
// TODO: Implement test
// Scenario: Race results with missing driver data
// Given: A completed race exists with missing driver information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
describe('GetRacePenaltiesUseCase', () => {
it('should retrieve race penalties with driver information', async () => {
// Given: A race with penalties
const raceId = 'r1';
const driverId = 'd1';
const stewardId = 's1';
it('should handle race with missing team information', async () => {
// TODO: Implement test
// Scenario: Race results with missing team data
// Given: A completed race exists with missing team information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
const driver = Driver.create({ id: driverId, iracingId: '100', name: 'John Doe', country: 'US' });
await driverRepository.create(driver);
const steward = Driver.create({ id: stewardId, iracingId: '200', name: 'Steward', country: 'UK' });
await driverRepository.create(steward);
it('should handle race with missing car information', async () => {
// TODO: Implement test
// Scenario: Race results with missing car data
// Given: A completed race exists with missing car information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
const penalty = Penalty.create({
id: 'p1',
raceId,
driverId,
type: 'time',
value: 5,
reason: 'Track limits',
issuedBy: stewardId,
status: 'applied'
});
await penaltyRepository.create(penalty);
it('should handle race with missing position information', async () => {
// TODO: Implement test
// Scenario: Race results with missing position data
// Given: A completed race exists with missing position information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing lap information', async () => {
// TODO: Implement test
// Scenario: Race results with missing lap data
// Given: A completed race exists with missing lap information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing points information', async () => {
// TODO: Implement test
// Scenario: Race results with missing points data
// Given: A completed race exists with missing points information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing penalties information', async () => {
// TODO: Implement test
// Scenario: Race results with missing penalties data
// Given: A completed race exists with missing penalties information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing incidents information', async () => {
// TODO: Implement test
// Scenario: Race results with missing incidents data
// Given: A completed race exists with missing incidents information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing stewarding actions information', async () => {
// TODO: Implement test
// Scenario: Race results with missing stewarding actions data
// Given: A completed race exists with missing stewarding actions information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should handle race with missing protests information', async () => {
// TODO: Implement test
// Scenario: Race results with missing protests data
// Given: A completed race exists with missing protests information
// When: GetRaceResultsUseCase.execute() is called with race ID
// Then: The result should contain results with available information
// And: EventPublisher should emit RaceResultsAccessedEvent
});
});
describe('GetRaceResultsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceResultsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should throw error when race ID is invalid', async () => {
// TODO: Implement test
// Scenario: Invalid race ID
// Given: An invalid race ID (e.g., empty string, null, undefined)
// When: GetRaceResultsUseCase.execute() is called with invalid race ID
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: A race exists
// And: RaceRepository throws an error during query
// When: GetRaceResultsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceStatisticsUseCase - Success Path', () => {
it('should retrieve race statistics with fastest lap', async () => {
// TODO: Implement test
// Scenario: Race with fastest lap
// Given: A completed race exists with fastest lap
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show fastest lap
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with average lap time', async () => {
// TODO: Implement test
// Scenario: Race with average lap time
// Given: A completed race exists with average lap time
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show average lap time
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with total incidents', async () => {
// TODO: Implement test
// Scenario: Race with total incidents
// Given: A completed race exists with total incidents
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show total incidents
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with total penalties', async () => {
// TODO: Implement test
// Scenario: Race with total penalties
// Given: A completed race exists with total penalties
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show total penalties
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with total protests', async () => {
// TODO: Implement test
// Scenario: Race with total protests
// Given: A completed race exists with total protests
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show total protests
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with total stewarding actions', async () => {
// TODO: Implement test
// Scenario: Race with total stewarding actions
// Given: A completed race exists with total stewarding actions
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show total stewarding actions
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with all metrics', async () => {
// TODO: Implement test
// Scenario: Race with all statistics
// Given: A completed race exists with all statistics
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show all statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should retrieve race statistics with empty metrics', async () => {
// TODO: Implement test
// Scenario: Race with no statistics
// Given: A completed race exists with no statistics
// When: GetRaceStatisticsUseCase.execute() is called with race ID
// Then: The result should show empty or default statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
});
describe('GetRaceStatisticsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceStatisticsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceStatisticsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRacePenaltiesUseCase - Success Path', () => {
it('should retrieve race penalties with penalty information', async () => {
// TODO: Implement test
// Scenario: Race with penalties
// Given: A completed race exists with penalties
// When: GetRacePenaltiesUseCase.execute() is called with race ID
// Then: The result should show penalty information
// And: EventPublisher should emit RacePenaltiesAccessedEvent
});
it('should retrieve race penalties with incident information', async () => {
// TODO: Implement test
// Scenario: Race with incidents
// Given: A completed race exists with incidents
// When: GetRacePenaltiesUseCase.execute() is called with race ID
// Then: The result should show incident information
// And: EventPublisher should emit RacePenaltiesAccessedEvent
});
it('should retrieve race penalties with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no penalties
// Given: A completed race exists with no penalties
// When: GetRacePenaltiesUseCase.execute() is called with race ID
// Then: The result should be empty
// And: EventPublisher should emit RacePenaltiesAccessedEvent
});
});
describe('GetRacePenaltiesUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRacePenaltiesUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRacePenaltiesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
const result = await getRacePenaltiesUseCase.execute({ raceId });
describe('GetRaceStewardingActionsUseCase - Success Path', () => {
it('should retrieve race stewarding actions with action information', async () => {
// TODO: Implement test
// Scenario: Race with stewarding actions
// Given: A completed race exists with stewarding actions
// When: GetRaceStewardingActionsUseCase.execute() is called with race ID
// Then: The result should show stewarding action information
// And: EventPublisher should emit RaceStewardingActionsAccessedEvent
});
it('should retrieve race stewarding actions with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no stewarding actions
// Given: A completed race exists with no stewarding actions
// When: GetRaceStewardingActionsUseCase.execute() is called with race ID
// Then: The result should be empty
// And: EventPublisher should emit RaceStewardingActionsAccessedEvent
});
});
describe('GetRaceStewardingActionsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceStewardingActionsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceStewardingActionsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRacePointsDistributionUseCase - Success Path', () => {
it('should retrieve race points distribution', async () => {
// TODO: Implement test
// Scenario: Race with points distribution
// Given: A completed race exists with points distribution
// When: GetRacePointsDistributionUseCase.execute() is called with race ID
// Then: The result should show points distribution
// And: EventPublisher should emit RacePointsDistributionAccessedEvent
});
it('should retrieve race points distribution with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no points distribution
// Given: A completed race exists with no points distribution
// When: GetRacePointsDistributionUseCase.execute() is called with race ID
// Then: The result should be empty
// And: EventPublisher should emit RacePointsDistributionAccessedEvent
});
});
describe('GetRacePointsDistributionUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRacePointsDistributionUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRacePointsDistributionUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceChampionshipImplicationsUseCase - Success Path', () => {
it('should retrieve race championship implications', async () => {
// TODO: Implement test
// Scenario: Race with championship implications
// Given: A completed race exists with championship implications
// When: GetRaceChampionshipImplicationsUseCase.execute() is called with race ID
// Then: The result should show championship implications
// And: EventPublisher should emit RaceChampionshipImplicationsAccessedEvent
});
it('should retrieve race championship implications with empty results', async () => {
// TODO: Implement test
// Scenario: Race with no championship implications
// Given: A completed race exists with no championship implications
// When: GetRaceChampionshipImplicationsUseCase.execute() is called with race ID
// Then: The result should be empty
// And: EventPublisher should emit RaceChampionshipImplicationsAccessedEvent
});
});
describe('GetRaceChampionshipImplicationsUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceChampionshipImplicationsUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRaceChampionshipImplicationsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('Race Results Page Data Orchestration', () => {
it('should correctly orchestrate data for race results page', async () => {
// TODO: Implement test
// Scenario: Race results page data orchestration
// Given: A completed race exists with all information
// When: Multiple use cases are executed for the same race
// Then: Each use case should return its respective data
// And: EventPublisher should emit appropriate events for each use case
});
it('should correctly format race results for display', async () => {
// TODO: Implement test
// Scenario: Race results formatting
// Given: A completed race exists with all information
// When: GetRaceResultsUseCase.execute() is called
// Then: The result should format:
// - Driver name: Clearly displayed
// - Team: Clearly displayed
// - Car: Clearly displayed
// - Position: Clearly displayed
// - Race time: Formatted correctly
// - Gaps: Formatted correctly
// - Laps completed: Clearly displayed
// - Points earned: Clearly displayed
// - Fastest lap: Formatted correctly
// - Average lap time: Formatted correctly
// - Penalties: Clearly displayed
// - Incidents: Clearly displayed
// - Stewarding actions: Clearly displayed
// - Protests: Clearly displayed
});
it('should correctly format race statistics for display', async () => {
// TODO: Implement test
// Scenario: Race statistics formatting
// Given: A completed race exists with all statistics
// When: GetRaceStatisticsUseCase.execute() is called
// Then: The result should format:
// - Fastest lap: Formatted correctly
// - Average lap time: Formatted correctly
// - Total incidents: Clearly displayed
// - Total penalties: Clearly displayed
// - Total protests: Clearly displayed
// - Total stewarding actions: Clearly displayed
});
it('should correctly format race penalties for display', async () => {
// TODO: Implement test
// Scenario: Race penalties formatting
// Given: A completed race exists with penalties
// When: GetRacePenaltiesUseCase.execute() is called
// Then: The result should format:
// - Penalty ID: Clearly displayed
// - Penalty type: Clearly displayed
// - Penalty severity: Clearly displayed
// - Penalty recipient: Clearly displayed
// - Penalty reason: Clearly displayed
// - Penalty timestamp: Formatted correctly
});
it('should correctly format race stewarding actions for display', async () => {
// TODO: Implement test
// Scenario: Race stewarding actions formatting
// Given: A completed race exists with stewarding actions
// When: GetRaceStewardingActionsUseCase.execute() is called
// Then: The result should format:
// - Stewarding action ID: Clearly displayed
// - Stewarding action type: Clearly displayed
// - Stewarding action recipient: Clearly displayed
// - Stewarding action reason: Clearly displayed
// - Stewarding action timestamp: Formatted correctly
});
it('should correctly format race points distribution for display', async () => {
// TODO: Implement test
// Scenario: Race points distribution formatting
// Given: A completed race exists with points distribution
// When: GetRacePointsDistributionUseCase.execute() is called
// Then: The result should format:
// - Points distribution: Clearly displayed
// - Championship implications: Clearly displayed
});
it('should correctly format race championship implications for display', async () => {
// TODO: Implement test
// Scenario: Race championship implications formatting
// Given: A completed race exists with championship implications
// When: GetRaceChampionshipImplicationsUseCase.execute() is called
// Then: The result should format:
// - Championship implications: Clearly displayed
// - Points changes: Clearly displayed
// - Position changes: Clearly displayed
});
it('should correctly handle race with no results', async () => {
// TODO: Implement test
// Scenario: Race with no results
// Given: A race exists with no results
// When: GetRaceResultsUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RaceResultsAccessedEvent
});
it('should correctly handle race with no statistics', async () => {
// TODO: Implement test
// Scenario: Race with no statistics
// Given: A race exists with no statistics
// When: GetRaceStatisticsUseCase.execute() is called
// Then: The result should show empty or default statistics
// And: EventPublisher should emit RaceStatisticsAccessedEvent
});
it('should correctly handle race with no penalties', async () => {
// TODO: Implement test
// Scenario: Race with no penalties
// Given: A race exists with no penalties
// When: GetRacePenaltiesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RacePenaltiesAccessedEvent
});
it('should correctly handle race with no stewarding actions', async () => {
// TODO: Implement test
// Scenario: Race with no stewarding actions
// Given: A race exists with no stewarding actions
// When: GetRaceStewardingActionsUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RaceStewardingActionsAccessedEvent
});
it('should correctly handle race with no points distribution', async () => {
// TODO: Implement test
// Scenario: Race with no points distribution
// Given: A race exists with no points distribution
// When: GetRacePointsDistributionUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RacePointsDistributionAccessedEvent
});
it('should correctly handle race with no championship implications', async () => {
// TODO: Implement test
// Scenario: Race with no championship implications
// Given: A race exists with no championship implications
// When: GetRaceChampionshipImplicationsUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RaceChampionshipImplicationsAccessedEvent
// Then: It should return penalties and drivers
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.penalties).toHaveLength(1);
expect(data.drivers.some(d => d.id === driverId)).toBe(true);
expect(data.drivers.some(d => d.id === stewardId)).toBe(true);
});
});
});

View File

@@ -3,682 +3,97 @@
*
* Tests the orchestration logic of all races page-related Use Cases:
* - GetAllRacesUseCase: Retrieves comprehensive list of all races
* - FilterRacesUseCase: Filters races by league, car, track, date range
* - SearchRacesUseCase: Searches races by track name and league name
* - SortRacesUseCase: Sorts races by date, league, car
* - PaginateRacesUseCase: Paginates race results
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
* - Uses In-Memory adapters for fast, deterministic testing
*
* Adheres to Clean Architecture:
* - Tests Core Use Cases directly
* - Uses In-Memory adapters for repositories
* - Follows Given/When/Then pattern
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/races/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
import { GetAllRacesUseCase } from '../../../core/races/use-cases/GetAllRacesUseCase';
import { FilterRacesUseCase } from '../../../core/races/use-cases/FilterRacesUseCase';
import { SearchRacesUseCase } from '../../../core/races/use-cases/SearchRacesUseCase';
import { SortRacesUseCase } from '../../../core/races/use-cases/SortRacesUseCase';
import { PaginateRacesUseCase } from '../../../core/races/use-cases/PaginateRacesUseCase';
import { AllRacesQuery } from '../../../core/races/ports/AllRacesQuery';
import { RaceFilterCommand } from '../../../core/races/ports/RaceFilterCommand';
import { RaceSearchCommand } from '../../../core/races/ports/RaceSearchCommand';
import { RaceSortCommand } from '../../../core/races/ports/RaceSortCommand';
import { RacePaginationCommand } from '../../../core/races/ports/RacePaginationCommand';
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { GetAllRacesUseCase } from '../../../core/racing/application/use-cases/GetAllRacesUseCase';
import { Race } from '../../../core/racing/domain/entities/Race';
import { League } from '../../../core/racing/domain/entities/League';
import { Logger } from '../../../core/shared/domain/Logger';
describe('All Races Use Case Orchestration', () => {
let raceRepository: InMemoryRaceRepository;
let eventPublisher: InMemoryEventPublisher;
let leagueRepository: InMemoryLeagueRepository;
let getAllRacesUseCase: GetAllRacesUseCase;
let filterRacesUseCase: FilterRacesUseCase;
let searchRacesUseCase: SearchRacesUseCase;
let sortRacesUseCase: SortRacesUseCase;
let paginateRacesUseCase: PaginateRacesUseCase;
let mockLogger: Logger;
beforeAll(() => {
// TODO: Initialize In-Memory repositories and event publisher
// raceRepository = new InMemoryRaceRepository();
// eventPublisher = new InMemoryEventPublisher();
// getAllRacesUseCase = new GetAllRacesUseCase({
// raceRepository,
// eventPublisher,
// });
// filterRacesUseCase = new FilterRacesUseCase({
// raceRepository,
// eventPublisher,
// });
// searchRacesUseCase = new SearchRacesUseCase({
// raceRepository,
// eventPublisher,
// });
// sortRacesUseCase = new SortRacesUseCase({
// raceRepository,
// eventPublisher,
// });
// paginateRacesUseCase = new PaginateRacesUseCase({
// raceRepository,
// eventPublisher,
// });
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
raceRepository = new InMemoryRaceRepository(mockLogger);
leagueRepository = new InMemoryLeagueRepository(mockLogger);
getAllRacesUseCase = new GetAllRacesUseCase(
raceRepository,
leagueRepository,
mockLogger
);
});
beforeEach(() => {
// TODO: Clear all In-Memory repositories before each test
// raceRepository.clear();
// eventPublisher.clear();
beforeEach(async () => {
(raceRepository as any).races.clear();
leagueRepository.clear();
});
describe('GetAllRacesUseCase - Success Path', () => {
describe('GetAllRacesUseCase', () => {
it('should retrieve comprehensive list of all races', async () => {
// TODO: Implement test
// Scenario: Driver views all races
// Given: Multiple races exist with different tracks, cars, leagues, and dates
// And: Races include upcoming, in-progress, and completed races
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain all races
// And: Each race should display track name, date, car, league, and winner (if completed)
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should retrieve all races with complete information', async () => {
// TODO: Implement test
// Scenario: All races with complete information
// Given: Multiple races exist with complete information
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with all available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should retrieve all races with minimal information', async () => {
// TODO: Implement test
// Scenario: All races with minimal data
// Given: Races exist with basic information only
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should retrieve all races when no races exist', async () => {
// TODO: Implement test
// Scenario: No races exist
// Given: No races exist in the system
// When: GetAllRacesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit AllRacesAccessedEvent
});
});
describe('GetAllRacesUseCase - Edge Cases', () => {
it('should handle races with missing track information', async () => {
// TODO: Implement test
// Scenario: Races with missing track data
// Given: Races exist with missing track information
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should handle races with missing car information', async () => {
// TODO: Implement test
// Scenario: Races with missing car data
// Given: Races exist with missing car information
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should handle races with missing league information', async () => {
// TODO: Implement test
// Scenario: Races with missing league data
// Given: Races exist with missing league information
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should handle races with missing winner information', async () => {
// TODO: Implement test
// Scenario: Races with missing winner data
// Given: Races exist with missing winner information
// When: GetAllRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit AllRacesAccessedEvent
});
});
describe('GetAllRacesUseCase - Error Handling', () => {
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetAllRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('FilterRacesUseCase - Success Path', () => {
it('should filter races by league', async () => {
// TODO: Implement test
// Scenario: Filter races by league
// Given: Multiple races exist across different leagues
// When: FilterRacesUseCase.execute() is called with league filter
// Then: The result should contain only races from the specified league
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races by car', async () => {
// TODO: Implement test
// Scenario: Filter races by car
// Given: Multiple races exist with different cars
// When: FilterRacesUseCase.execute() is called with car filter
// Then: The result should contain only races with the specified car
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races by track', async () => {
// TODO: Implement test
// Scenario: Filter races by track
// Given: Multiple races exist at different tracks
// When: FilterRacesUseCase.execute() is called with track filter
// Then: The result should contain only races at the specified track
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races by date range', async () => {
// TODO: Implement test
// Scenario: Filter races by date range
// Given: Multiple races exist across different dates
// When: FilterRacesUseCase.execute() is called with date range
// Then: The result should contain only races within the date range
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races by multiple criteria', async () => {
// TODO: Implement test
// Scenario: Filter races by multiple criteria
// Given: Multiple races exist with different attributes
// When: FilterRacesUseCase.execute() is called with multiple filters
// Then: The result should contain only races matching all criteria
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races with empty result when no matches', async () => {
// TODO: Implement test
// Scenario: Filter with no matches
// Given: Races exist but none match the filter criteria
// When: FilterRacesUseCase.execute() is called with filter
// Then: The result should be empty
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races with pagination', async () => {
// TODO: Implement test
// Scenario: Filter races with pagination
// Given: Many races exist matching filter criteria
// When: FilterRacesUseCase.execute() is called with filter and pagination
// Then: The result should contain only the specified page of filtered races
// And: EventPublisher should emit RacesFilteredEvent
});
it('should filter races with limit', async () => {
// TODO: Implement test
// Scenario: Filter races with limit
// Given: Many races exist matching filter criteria
// When: FilterRacesUseCase.execute() is called with filter and limit
// Then: The result should contain only the specified number of filtered races
// And: EventPublisher should emit RacesFilteredEvent
});
});
describe('FilterRacesUseCase - Edge Cases', () => {
it('should handle empty filter criteria', async () => {
// TODO: Implement test
// Scenario: Empty filter criteria
// Given: Races exist
// When: FilterRacesUseCase.execute() is called with empty filter
// Then: The result should contain all races (no filtering applied)
// And: EventPublisher should emit RacesFilteredEvent
});
it('should handle case-insensitive filtering', async () => {
// TODO: Implement test
// Scenario: Case-insensitive filtering
// Given: Races exist with mixed case names
// When: FilterRacesUseCase.execute() is called with different case filter
// Then: The result should match regardless of case
// And: EventPublisher should emit RacesFilteredEvent
});
it('should handle partial matches in text filters', async () => {
// TODO: Implement test
// Scenario: Partial matches in text filters
// Given: Races exist with various names
// When: FilterRacesUseCase.execute() is called with partial text
// Then: The result should include races with partial matches
// And: EventPublisher should emit RacesFilteredEvent
});
});
describe('FilterRacesUseCase - Error Handling', () => {
it('should handle invalid filter parameters', async () => {
// TODO: Implement test
// Scenario: Invalid filter parameters
// Given: Invalid filter values (e.g., empty strings, null)
// When: FilterRacesUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during filter
// When: FilterRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('SearchRacesUseCase - Success Path', () => {
it('should search races by track name', async () => {
// TODO: Implement test
// Scenario: Search races by track name
// Given: Multiple races exist at different tracks
// When: SearchRacesUseCase.execute() is called with track name
// Then: The result should contain races matching the track name
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races by league name', async () => {
// TODO: Implement test
// Scenario: Search races by league name
// Given: Multiple races exist in different leagues
// When: SearchRacesUseCase.execute() is called with league name
// Then: The result should contain races matching the league name
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races with partial matches', async () => {
// TODO: Implement test
// Scenario: Search with partial matches
// Given: Races exist with various names
// When: SearchRacesUseCase.execute() is called with partial search term
// Then: The result should include races with partial matches
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races case-insensitively', async () => {
// TODO: Implement test
// Scenario: Case-insensitive search
// Given: Races exist with mixed case names
// When: SearchRacesUseCase.execute() is called with different case search term
// Then: The result should match regardless of case
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races with empty result when no matches', async () => {
// TODO: Implement test
// Scenario: Search with no matches
// Given: Races exist but none match the search term
// When: SearchRacesUseCase.execute() is called with search term
// Then: The result should be empty
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races with pagination', async () => {
// TODO: Implement test
// Scenario: Search races with pagination
// Given: Many races exist matching search term
// When: SearchRacesUseCase.execute() is called with search term and pagination
// Then: The result should contain only the specified page of search results
// And: EventPublisher should emit RacesSearchedEvent
});
it('should search races with limit', async () => {
// TODO: Implement test
// Scenario: Search races with limit
// Given: Many races exist matching search term
// When: SearchRacesUseCase.execute() is called with search term and limit
// Then: The result should contain only the specified number of search results
// And: EventPublisher should emit RacesSearchedEvent
});
});
describe('SearchRacesUseCase - Edge Cases', () => {
it('should handle empty search term', async () => {
// TODO: Implement test
// Scenario: Empty search term
// Given: Races exist
// When: SearchRacesUseCase.execute() is called with empty search term
// Then: The result should contain all races (no search applied)
// And: EventPublisher should emit RacesSearchedEvent
});
it('should handle special characters in search term', async () => {
// TODO: Implement test
// Scenario: Special characters in search term
// Given: Races exist with special characters in names
// When: SearchRacesUseCase.execute() is called with special characters
// Then: The result should handle special characters appropriately
// And: EventPublisher should emit RacesSearchedEvent
});
it('should handle very long search terms', async () => {
// TODO: Implement test
// Scenario: Very long search term
// Given: Races exist
// When: SearchRacesUseCase.execute() is called with very long search term
// Then: The result should handle the long term appropriately
// And: EventPublisher should emit RacesSearchedEvent
});
});
describe('SearchRacesUseCase - Error Handling', () => {
it('should handle invalid search parameters', async () => {
// TODO: Implement test
// Scenario: Invalid search parameters
// Given: Invalid search values (e.g., null, undefined)
// When: SearchRacesUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during search
// When: SearchRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('SortRacesUseCase - Success Path', () => {
it('should sort races by date', async () => {
// TODO: Implement test
// Scenario: Sort races by date
// Given: Multiple races exist with different dates
// When: SortRacesUseCase.execute() is called with date sort
// Then: The result should be sorted by date
// And: EventPublisher should emit RacesSortedEvent
});
it('should sort races by league', async () => {
// TODO: Implement test
// Scenario: Sort races by league
// Given: Multiple races exist with different leagues
// When: SortRacesUseCase.execute() is called with league sort
// Then: The result should be sorted by league name alphabetically
// And: EventPublisher should emit RacesSortedEvent
});
it('should sort races by car', async () => {
// TODO: Implement test
// Scenario: Sort races by car
// Given: Multiple races exist with different cars
// When: SortRacesUseCase.execute() is called with car sort
// Then: The result should be sorted by car name alphabetically
// And: EventPublisher should emit RacesSortedEvent
});
it('should sort races in ascending order', async () => {
// TODO: Implement test
// Scenario: Sort races in ascending order
// Given: Multiple races exist
// When: SortRacesUseCase.execute() is called with ascending sort
// Then: The result should be sorted in ascending order
// And: EventPublisher should emit RacesSortedEvent
const leagueId = 'l1';
const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' });
await leagueRepository.create(league);
const race1 = Race.create({
id: 'r1',
leagueId,
scheduledAt: new Date(Date.now() + 86400000),
track: 'Spa',
car: 'GT3',
status: 'scheduled'
});
const race2 = Race.create({
id: 'r2',
leagueId,
scheduledAt: new Date(Date.now() - 86400000),
track: 'Monza',
car: 'GT3',
status: 'completed'
});
await raceRepository.create(race1);
await raceRepository.create(race2);
// When: GetAllRacesUseCase.execute() is called
const result = await getAllRacesUseCase.execute({});
// Then: The result should contain all races and leagues
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.races).toHaveLength(2);
expect(data.leagues).toHaveLength(1);
expect(data.totalCount).toBe(2);
});
it('should sort races in descending order', async () => {
// TODO: Implement test
// Scenario: Sort races in descending order
// Given: Multiple races exist
// When: SortRacesUseCase.execute() is called with descending sort
// Then: The result should be sorted in descending order
// And: EventPublisher should emit RacesSortedEvent
});
it('should return empty list when no races exist', async () => {
// When: GetAllRacesUseCase.execute() is called
const result = await getAllRacesUseCase.execute({});
it('should sort races with pagination', async () => {
// TODO: Implement test
// Scenario: Sort races with pagination
// Given: Many races exist
// When: SortRacesUseCase.execute() is called with sort and pagination
// Then: The result should contain only the specified page of sorted races
// And: EventPublisher should emit RacesSortedEvent
});
it('should sort races with limit', async () => {
// TODO: Implement test
// Scenario: Sort races with limit
// Given: Many races exist
// When: SortRacesUseCase.execute() is called with sort and limit
// Then: The result should contain only the specified number of sorted races
// And: EventPublisher should emit RacesSortedEvent
});
});
describe('SortRacesUseCase - Edge Cases', () => {
it('should handle races with missing sort field', async () => {
// TODO: Implement test
// Scenario: Races with missing sort field
// Given: Races exist with missing sort field values
// When: SortRacesUseCase.execute() is called
// Then: The result should handle missing values appropriately
// And: EventPublisher should emit RacesSortedEvent
});
it('should handle empty race list', async () => {
// TODO: Implement test
// Scenario: Empty race list
// Given: No races exist
// When: SortRacesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RacesSortedEvent
});
it('should handle single race', async () => {
// TODO: Implement test
// Scenario: Single race
// Given: Only one race exists
// When: SortRacesUseCase.execute() is called
// Then: The result should contain the single race
// And: EventPublisher should emit RacesSortedEvent
});
});
describe('SortRacesUseCase - Error Handling', () => {
it('should handle invalid sort parameters', async () => {
// TODO: Implement test
// Scenario: Invalid sort parameters
// Given: Invalid sort field or direction
// When: SortRacesUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during sort
// When: SortRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('PaginateRacesUseCase - Success Path', () => {
it('should paginate races with page and pageSize', async () => {
// TODO: Implement test
// Scenario: Paginate races
// Given: Many races exist
// When: PaginateRacesUseCase.execute() is called with page and pageSize
// Then: The result should contain only the specified page of races
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with first page', async () => {
// TODO: Implement test
// Scenario: First page of races
// Given: Many races exist
// When: PaginateRacesUseCase.execute() is called with page 1
// Then: The result should contain the first page of races
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with middle page', async () => {
// TODO: Implement test
// Scenario: Middle page of races
// Given: Many races exist
// When: PaginateRacesUseCase.execute() is called with middle page number
// Then: The result should contain the middle page of races
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with last page', async () => {
// TODO: Implement test
// Scenario: Last page of races
// Given: Many races exist
// When: PaginateRacesUseCase.execute() is called with last page number
// Then: The result should contain the last page of races
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with different page sizes', async () => {
// TODO: Implement test
// Scenario: Different page sizes
// Given: Many races exist
// When: PaginateRacesUseCase.execute() is called with different pageSize values
// Then: The result should contain the correct number of races per page
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with empty result when page exceeds total', async () => {
// TODO: Implement test
// Scenario: Page exceeds total
// Given: Races exist
// When: PaginateRacesUseCase.execute() is called with page beyond total
// Then: The result should be empty
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should paginate races with empty result when no races exist', async () => {
// TODO: Implement test
// Scenario: No races exist
// Given: No races exist
// When: PaginateRacesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RacesPaginatedEvent
});
});
describe('PaginateRacesUseCase - Edge Cases', () => {
it('should handle page 0', async () => {
// TODO: Implement test
// Scenario: Page 0
// Given: Races exist
// When: PaginateRacesUseCase.execute() is called with page 0
// Then: Should handle appropriately (either throw error or return first page)
// And: EventPublisher should emit RacesPaginatedEvent or NOT emit
});
it('should handle very large page size', async () => {
// TODO: Implement test
// Scenario: Very large page size
// Given: Races exist
// When: PaginateRacesUseCase.execute() is called with very large pageSize
// Then: The result should contain all races or handle appropriately
// And: EventPublisher should emit RacesPaginatedEvent
});
it('should handle page size larger than total races', async () => {
// TODO: Implement test
// Scenario: Page size larger than total
// Given: Few races exist
// When: PaginateRacesUseCase.execute() is called with pageSize > total
// Then: The result should contain all races
// And: EventPublisher should emit RacesPaginatedEvent
});
});
describe('PaginateRacesUseCase - Error Handling', () => {
it('should handle invalid pagination parameters', async () => {
// TODO: Implement test
// Scenario: Invalid pagination parameters
// Given: Invalid page or pageSize values (negative, null, undefined)
// When: PaginateRacesUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during pagination
// When: PaginateRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('All Races Page Data Orchestration', () => {
it('should correctly orchestrate filtering, searching, sorting, and pagination', async () => {
// TODO: Implement test
// Scenario: Combined operations
// Given: Many races exist with various attributes
// When: Multiple use cases are executed in sequence
// Then: Each use case should work correctly
// And: EventPublisher should emit appropriate events for each operation
});
it('should correctly format race information for all races list', async () => {
// TODO: Implement test
// Scenario: Race information formatting
// Given: Races exist with all information
// When: AllRacesUseCase.execute() is called
// Then: The result should format:
// - Track name: Clearly displayed
// - Date: Formatted correctly
// - Car: Clearly displayed
// - League: Clearly displayed
// - Winner: Clearly displayed (if completed)
});
it('should correctly handle race status in all races list', async () => {
// TODO: Implement test
// Scenario: Race status in all races
// Given: Races exist with different statuses (Upcoming, In Progress, Completed)
// When: AllRacesUseCase.execute() is called
// Then: The result should show appropriate status for each race
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should correctly handle empty states', async () => {
// TODO: Implement test
// Scenario: Empty states
// Given: No races exist
// When: AllRacesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit AllRacesAccessedEvent
});
it('should correctly handle loading states', async () => {
// TODO: Implement test
// Scenario: Loading states
// Given: Races are being loaded
// When: AllRacesUseCase.execute() is called
// Then: The use case should handle loading state appropriately
// And: EventPublisher should emit appropriate events
});
it('should correctly handle error states', async () => {
// TODO: Implement test
// Scenario: Error states
// Given: Repository throws error
// When: AllRacesUseCase.execute() is called
// Then: The use case should handle error appropriately
// And: EventPublisher should NOT emit any events
expect(result.isOk()).toBe(true);
expect(result.unwrap().races).toHaveLength(0);
expect(result.unwrap().totalCount).toBe(0);
});
});
});

View File

@@ -2,699 +2,88 @@
* Integration Test: Races Main Use Case Orchestration
*
* Tests the orchestration logic of races main page-related Use Cases:
* - GetUpcomingRacesUseCase: Retrieves upcoming races for the main page
* - GetRecentRaceResultsUseCase: Retrieves recent race results for the main page
* - GetRaceDetailUseCase: Retrieves race details for navigation
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
* - Uses In-Memory adapters for fast, deterministic testing
* - GetAllRacesUseCase: Used to retrieve upcoming and recent races
*
* Adheres to Clean Architecture:
* - Tests Core Use Cases directly
* - Uses In-Memory adapters for repositories
* - Follows Given/When/Then pattern
*
* Focus: Business logic orchestration, NOT UI rendering
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/races/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryEventPublisher } from '../../../adapters/events/InMemoryEventPublisher';
import { GetUpcomingRacesUseCase } from '../../../core/races/use-cases/GetUpcomingRacesUseCase';
import { GetRecentRaceResultsUseCase } from '../../../core/races/use-cases/GetRecentRaceResultsUseCase';
import { GetRaceDetailUseCase } from '../../../core/races/use-cases/GetRaceDetailUseCase';
import { UpcomingRacesQuery } from '../../../core/races/ports/UpcomingRacesQuery';
import { RecentRaceResultsQuery } from '../../../core/races/ports/RecentRaceResultsQuery';
import { RaceDetailQuery } from '../../../core/races/ports/RaceDetailQuery';
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryRaceRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryRaceRepository';
import { InMemoryLeagueRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryLeagueRepository';
import { GetAllRacesUseCase } from '../../../core/racing/application/use-cases/GetAllRacesUseCase';
import { Race } from '../../../core/racing/domain/entities/Race';
import { League } from '../../../core/racing/domain/entities/League';
import { Logger } from '../../../core/shared/domain/Logger';
describe('Races Main Use Case Orchestration', () => {
let raceRepository: InMemoryRaceRepository;
let eventPublisher: InMemoryEventPublisher;
let getUpcomingRacesUseCase: GetUpcomingRacesUseCase;
let getRecentRaceResultsUseCase: GetRecentRaceResultsUseCase;
let getRaceDetailUseCase: GetRaceDetailUseCase;
let leagueRepository: InMemoryLeagueRepository;
let getAllRacesUseCase: GetAllRacesUseCase;
let mockLogger: Logger;
beforeAll(() => {
// TODO: Initialize In-Memory repositories and event publisher
// raceRepository = new InMemoryRaceRepository();
// eventPublisher = new InMemoryEventPublisher();
// getUpcomingRacesUseCase = new GetUpcomingRacesUseCase({
// raceRepository,
// eventPublisher,
// });
// getRecentRaceResultsUseCase = new GetRecentRaceResultsUseCase({
// raceRepository,
// eventPublisher,
// });
// getRaceDetailUseCase = new GetRaceDetailUseCase({
// raceRepository,
// eventPublisher,
// });
mockLogger = {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {},
} as unknown as Logger;
raceRepository = new InMemoryRaceRepository(mockLogger);
leagueRepository = new InMemoryLeagueRepository(mockLogger);
getAllRacesUseCase = new GetAllRacesUseCase(
raceRepository,
leagueRepository,
mockLogger
);
});
beforeEach(() => {
// TODO: Clear all In-Memory repositories before each test
// raceRepository.clear();
// eventPublisher.clear();
beforeEach(async () => {
(raceRepository as any).races.clear();
leagueRepository.clear();
});
describe('GetUpcomingRacesUseCase - Success Path', () => {
it('should retrieve upcoming races with complete information', async () => {
// TODO: Implement test
// Scenario: Driver views upcoming races
// Given: Multiple upcoming races exist with different tracks, cars, and leagues
// And: Each race has track name, date, time, car, and league
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should contain all upcoming races
// And: Each race should display track name, date, time, car, and league
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races sorted by date', async () => {
// TODO: Implement test
// Scenario: Upcoming races are sorted by date
// Given: Multiple upcoming races exist with different dates
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should be sorted by date (earliest first)
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with minimal information', async () => {
// TODO: Implement test
// Scenario: Upcoming races with minimal data
// Given: Upcoming races exist with basic information only
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with league filtering', async () => {
// TODO: Implement test
// Scenario: Filter upcoming races by league
// Given: Multiple upcoming races exist across different leagues
// When: GetUpcomingRacesUseCase.execute() is called with league filter
// Then: The result should contain only races from the specified league
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with car filtering', async () => {
// TODO: Implement test
// Scenario: Filter upcoming races by car
// Given: Multiple upcoming races exist with different cars
// When: GetUpcomingRacesUseCase.execute() is called with car filter
// Then: The result should contain only races with the specified car
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with track filtering', async () => {
// TODO: Implement test
// Scenario: Filter upcoming races by track
// Given: Multiple upcoming races exist at different tracks
// When: GetUpcomingRacesUseCase.execute() is called with track filter
// Then: The result should contain only races at the specified track
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with date range filtering', async () => {
// TODO: Implement test
// Scenario: Filter upcoming races by date range
// Given: Multiple upcoming races exist across different dates
// When: GetUpcomingRacesUseCase.execute() is called with date range
// Then: The result should contain only races within the date range
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with pagination', async () => {
// TODO: Implement test
// Scenario: Paginate upcoming races
// Given: Many upcoming races exist (more than page size)
// When: GetUpcomingRacesUseCase.execute() is called with pagination
// Then: The result should contain only the specified page of races
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with limit', async () => {
// TODO: Implement test
// Scenario: Limit upcoming races
// Given: Many upcoming races exist
// When: GetUpcomingRacesUseCase.execute() is called with limit
// Then: The result should contain only the specified number of races
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should retrieve upcoming races with empty result when no races exist', async () => {
// TODO: Implement test
// Scenario: No upcoming races exist
// Given: No upcoming races exist in the system
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
});
describe('GetUpcomingRacesUseCase - Edge Cases', () => {
it('should handle races with missing track information', async () => {
// TODO: Implement test
// Scenario: Upcoming races with missing track data
// Given: Upcoming races exist with missing track information
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should handle races with missing car information', async () => {
// TODO: Implement test
// Scenario: Upcoming races with missing car data
// Given: Upcoming races exist with missing car information
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
it('should handle races with missing league information', async () => {
// TODO: Implement test
// Scenario: Upcoming races with missing league data
// Given: Upcoming races exist with missing league information
// When: GetUpcomingRacesUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit UpcomingRacesAccessedEvent
});
});
describe('GetUpcomingRacesUseCase - Error Handling', () => {
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetUpcomingRacesUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
it('should handle invalid pagination parameters', async () => {
// TODO: Implement test
// Scenario: Invalid pagination parameters
// Given: Invalid page or pageSize values
// When: GetUpcomingRacesUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
});
describe('GetRecentRaceResultsUseCase - Success Path', () => {
it('should retrieve recent race results with complete information', async () => {
// TODO: Implement test
// Scenario: Driver views recent race results
// Given: Multiple recent race results exist with different tracks, cars, and leagues
// And: Each race has track name, date, winner, car, and league
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain all recent race results
// And: Each race should display track name, date, winner, car, and league
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results sorted by date (newest first)', async () => {
// TODO: Implement test
// Scenario: Recent race results are sorted by date
// Given: Multiple recent race results exist with different dates
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should be sorted by date (newest first)
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with minimal information', async () => {
// TODO: Implement test
// Scenario: Recent race results with minimal data
// Given: Recent race results exist with basic information only
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with league filtering', async () => {
// TODO: Implement test
// Scenario: Filter recent race results by league
// Given: Multiple recent race results exist across different leagues
// When: GetRecentRaceResultsUseCase.execute() is called with league filter
// Then: The result should contain only races from the specified league
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with car filtering', async () => {
// TODO: Implement test
// Scenario: Filter recent race results by car
// Given: Multiple recent race results exist with different cars
// When: GetRecentRaceResultsUseCase.execute() is called with car filter
// Then: The result should contain only races with the specified car
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with track filtering', async () => {
// TODO: Implement test
// Scenario: Filter recent race results by track
// Given: Multiple recent race results exist at different tracks
// When: GetRecentRaceResultsUseCase.execute() is called with track filter
// Then: The result should contain only races at the specified track
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with date range filtering', async () => {
// TODO: Implement test
// Scenario: Filter recent race results by date range
// Given: Multiple recent race results exist across different dates
// When: GetRecentRaceResultsUseCase.execute() is called with date range
// Then: The result should contain only races within the date range
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with pagination', async () => {
// TODO: Implement test
// Scenario: Paginate recent race results
// Given: Many recent race results exist (more than page size)
// When: GetRecentRaceResultsUseCase.execute() is called with pagination
// Then: The result should contain only the specified page of races
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with limit', async () => {
// TODO: Implement test
// Scenario: Limit recent race results
// Given: Many recent race results exist
// When: GetRecentRaceResultsUseCase.execute() is called with limit
// Then: The result should contain only the specified number of races
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should retrieve recent race results with empty result when no races exist', async () => {
// TODO: Implement test
// Scenario: No recent race results exist
// Given: No recent race results exist in the system
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should be empty
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
});
describe('GetRecentRaceResultsUseCase - Edge Cases', () => {
it('should handle races with missing winner information', async () => {
// TODO: Implement test
// Scenario: Recent race results with missing winner data
// Given: Recent race results exist with missing winner information
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should handle races with missing track information', async () => {
// TODO: Implement test
// Scenario: Recent race results with missing track data
// Given: Recent race results exist with missing track information
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should handle races with missing car information', async () => {
// TODO: Implement test
// Scenario: Recent race results with missing car data
// Given: Recent race results exist with missing car information
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
it('should handle races with missing league information', async () => {
// TODO: Implement test
// Scenario: Recent race results with missing league data
// Given: Recent race results exist with missing league information
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: The result should contain races with available information
// And: EventPublisher should emit RecentRaceResultsAccessedEvent
});
});
describe('GetRecentRaceResultsUseCase - Error Handling', () => {
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: RaceRepository throws an error during query
// When: GetRecentRaceResultsUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
it('should handle invalid pagination parameters', async () => {
// TODO: Implement test
// Scenario: Invalid pagination parameters
// Given: Invalid page or pageSize values
// When: GetRecentRaceResultsUseCase.execute() is called with invalid parameters
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
});
describe('GetRaceDetailUseCase - Success Path', () => {
it('should retrieve race detail with complete information', async () => {
// TODO: Implement test
// Scenario: Driver views race detail
// Given: A race exists with complete information
// And: The race has track, car, league, date, time, duration, status
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain complete race information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with participants count', async () => {
// TODO: Implement test
// Scenario: Race with participants count
// Given: A race exists with participants
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show participants count
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with winner and podium for completed races', async () => {
// TODO: Implement test
// Scenario: Completed race with winner and podium
// Given: A completed race exists with winner and podium
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show winner and podium
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with track layout', async () => {
// TODO: Implement test
// Scenario: Race with track layout
// Given: A race exists with track layout
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show track layout
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with weather information', async () => {
// TODO: Implement test
// Scenario: Race with weather information
// Given: A race exists with weather information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show weather information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with race conditions', async () => {
// TODO: Implement test
// Scenario: Race with conditions
// Given: A race exists with conditions
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show race conditions
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with statistics', async () => {
// TODO: Implement test
// Scenario: Race with statistics
// Given: A race exists with statistics (lap count, incidents, penalties, protests, stewarding actions)
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show race statistics
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with lap times', async () => {
// TODO: Implement test
// Scenario: Race with lap times
// Given: A race exists with lap times (average, fastest, best sectors)
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show lap times
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with qualifying results', async () => {
// TODO: Implement test
// Scenario: Race with qualifying results
// Given: A race exists with qualifying results
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show qualifying results
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with starting grid', async () => {
// TODO: Implement test
// Scenario: Race with starting grid
// Given: A race exists with starting grid
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show starting grid
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with points distribution', async () => {
// TODO: Implement test
// Scenario: Race with points distribution
// Given: A race exists with points distribution
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show points distribution
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with championship implications', async () => {
// TODO: Implement test
// Scenario: Race with championship implications
// Given: A race exists with championship implications
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show championship implications
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with highlights', async () => {
// TODO: Implement test
// Scenario: Race with highlights
// Given: A race exists with highlights
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show highlights
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with video link', async () => {
// TODO: Implement test
// Scenario: Race with video link
// Given: A race exists with video link
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show video link
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with gallery', async () => {
// TODO: Implement test
// Scenario: Race with gallery
// Given: A race exists with gallery
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show gallery
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with description', async () => {
// TODO: Implement test
// Scenario: Race with description
// Given: A race exists with description
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show description
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with rules', async () => {
// TODO: Implement test
// Scenario: Race with rules
// Given: A race exists with rules
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show rules
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should retrieve race detail with requirements', async () => {
// TODO: Implement test
// Scenario: Race with requirements
// Given: A race exists with requirements
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show requirements
// And: EventPublisher should emit RaceDetailAccessedEvent
});
});
describe('GetRaceDetailUseCase - Edge Cases', () => {
it('should handle race with missing track information', async () => {
// TODO: Implement test
// Scenario: Race with missing track data
// Given: A race exists with missing track information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with missing car information', async () => {
// TODO: Implement test
// Scenario: Race with missing car data
// Given: A race exists with missing car information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with missing league information', async () => {
// TODO: Implement test
// Scenario: Race with missing league data
// Given: A race exists with missing league information
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should contain race with available information
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle upcoming race without winner or podium', async () => {
// TODO: Implement test
// Scenario: Upcoming race without winner or podium
// Given: An upcoming race exists (not completed)
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should not show winner or podium
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no statistics', async () => {
// TODO: Implement test
// Scenario: Race with no statistics
// Given: A race exists with no statistics
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default statistics
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no lap times', async () => {
// TODO: Implement test
// Scenario: Race with no lap times
// Given: A race exists with no lap times
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default lap times
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no qualifying results', async () => {
// TODO: Implement test
// Scenario: Race with no qualifying results
// Given: A race exists with no qualifying results
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default qualifying results
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no highlights', async () => {
// TODO: Implement test
// Scenario: Race with no highlights
// Given: A race exists with no highlights
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default highlights
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no video link', async () => {
// TODO: Implement test
// Scenario: Race with no video link
// Given: A race exists with no video link
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default video link
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no gallery', async () => {
// TODO: Implement test
// Scenario: Race with no gallery
// Given: A race exists with no gallery
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default gallery
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no description', async () => {
// TODO: Implement test
// Scenario: Race with no description
// Given: A race exists with no description
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default description
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no rules', async () => {
// TODO: Implement test
// Scenario: Race with no rules
// Given: A race exists with no rules
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default rules
// And: EventPublisher should emit RaceDetailAccessedEvent
});
it('should handle race with no requirements', async () => {
// TODO: Implement test
// Scenario: Race with no requirements
// Given: A race exists with no requirements
// When: GetRaceDetailUseCase.execute() is called with race ID
// Then: The result should show empty or default requirements
// And: EventPublisher should emit RaceDetailAccessedEvent
});
});
describe('GetRaceDetailUseCase - Error Handling', () => {
it('should throw error when race does not exist', async () => {
// TODO: Implement test
// Scenario: Non-existent race
// Given: No race exists with the given ID
// When: GetRaceDetailUseCase.execute() is called with non-existent race ID
// Then: Should throw RaceNotFoundError
// And: EventPublisher should NOT emit any events
});
it('should throw error when race ID is invalid', async () => {
// TODO: Implement test
// Scenario: Invalid race ID
// Given: An invalid race ID (e.g., empty string, null, undefined)
// When: GetRaceDetailUseCase.execute() is called with invalid race ID
// Then: Should throw ValidationError
// And: EventPublisher should NOT emit any events
});
it('should handle repository errors gracefully', async () => {
// TODO: Implement test
// Scenario: Repository throws error
// Given: A race exists
// And: RaceRepository throws an error during query
// When: GetRaceDetailUseCase.execute() is called
// Then: Should propagate the error appropriately
// And: EventPublisher should NOT emit any events
});
});
describe('Races Main Page Data Orchestration', () => {
it('should correctly orchestrate data for main races page', async () => {
// TODO: Implement test
// Scenario: Main races page data orchestration
// Given: Multiple upcoming races exist
// And: Multiple recent race results exist
// When: GetUpcomingRacesUseCase.execute() is called
// And: GetRecentRaceResultsUseCase.execute() is called
// Then: Both use cases should return their respective data
// And: EventPublisher should emit appropriate events for each use case
});
it('should correctly format race information for display', async () => {
// TODO: Implement test
// Scenario: Race information formatting
// Given: A race exists with all information
// When: GetRaceDetailUseCase.execute() is called
// Then: The result should format:
// - Track name: Clearly displayed
// - Date: Formatted correctly
// - Time: Formatted correctly
// - Car: Clearly displayed
// - League: Clearly displayed
// - Status: Clearly indicated (Upcoming, In Progress, Completed)
});
it('should correctly handle race status transitions', async () => {
// TODO: Implement test
// Scenario: Race status transitions
// Given: A race exists with status "Upcoming"
// When: Race status changes to "In Progress"
// And: GetRaceDetailUseCase.execute() is called
// Then: The result should show the updated status
// And: EventPublisher should emit RaceDetailAccessedEvent
describe('Races Main Page Data', () => {
it('should retrieve upcoming and recent races', async () => {
// Given: Upcoming and completed races exist
const leagueId = 'l1';
const league = League.create({ id: leagueId, name: 'Pro League', description: 'Desc', ownerId: 'o1' });
await leagueRepository.create(league);
const upcomingRace = Race.create({
id: 'r1',
leagueId,
scheduledAt: new Date(Date.now() + 86400000),
track: 'Spa',
car: 'GT3',
status: 'scheduled'
});
const completedRace = Race.create({
id: 'r2',
leagueId,
scheduledAt: new Date(Date.now() - 86400000),
track: 'Monza',
car: 'GT3',
status: 'completed'
});
await raceRepository.create(upcomingRace);
await raceRepository.create(completedRace);
// When: GetAllRacesUseCase.execute() is called
const result = await getAllRacesUseCase.execute({});
// Then: The result should contain both races
expect(result.isOk()).toBe(true);
const data = result.unwrap();
expect(data.races).toHaveLength(2);
expect(data.races.some(r => r.status.isScheduled())).toBe(true);
expect(data.races.some(r => r.status.isCompleted())).toBe(true);
});
});
});