integration tests
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m51s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m51s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
This commit is contained in:
36
tests/integration/leaderboards/LeaderboardsTestContext.ts
Normal file
36
tests/integration/leaderboards/LeaderboardsTestContext.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { InMemoryLeaderboardsRepository } from '../../../adapters/leaderboards/persistence/inmemory/InMemoryLeaderboardsRepository';
|
||||
import { InMemoryLeaderboardsEventPublisher } from '../../../adapters/leaderboards/events/InMemoryLeaderboardsEventPublisher';
|
||||
import { GetDriverRankingsUseCase } from '../../../core/leaderboards/application/use-cases/GetDriverRankingsUseCase';
|
||||
import { GetTeamRankingsUseCase } from '../../../core/leaderboards/application/use-cases/GetTeamRankingsUseCase';
|
||||
import { GetGlobalLeaderboardsUseCase } from '../../../core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase';
|
||||
|
||||
export class LeaderboardsTestContext {
|
||||
public readonly repository: InMemoryLeaderboardsRepository;
|
||||
public readonly eventPublisher: InMemoryLeaderboardsEventPublisher;
|
||||
public readonly getDriverRankingsUseCase: GetDriverRankingsUseCase;
|
||||
public readonly getTeamRankingsUseCase: GetTeamRankingsUseCase;
|
||||
public readonly getGlobalLeaderboardsUseCase: GetGlobalLeaderboardsUseCase;
|
||||
|
||||
constructor() {
|
||||
this.repository = new InMemoryLeaderboardsRepository();
|
||||
this.eventPublisher = new InMemoryLeaderboardsEventPublisher();
|
||||
|
||||
const dependencies = {
|
||||
leaderboardsRepository: this.repository,
|
||||
eventPublisher: this.eventPublisher,
|
||||
};
|
||||
|
||||
this.getDriverRankingsUseCase = new GetDriverRankingsUseCase(dependencies);
|
||||
this.getTeamRankingsUseCase = new GetTeamRankingsUseCase(dependencies);
|
||||
this.getGlobalLeaderboardsUseCase = new GetGlobalLeaderboardsUseCase(dependencies);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.repository.clear();
|
||||
this.eventPublisher.clear();
|
||||
}
|
||||
|
||||
static create(): LeaderboardsTestContext {
|
||||
return new LeaderboardsTestContext();
|
||||
}
|
||||
}
|
||||
@@ -1,951 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Driver Rankings Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of driver rankings-related Use Cases:
|
||||
* - GetDriverRankingsUseCase: Retrieves comprehensive list of all drivers with search, filter, and sort capabilities
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - Uses In-Memory adapters for fast, deterministic testing
|
||||
*
|
||||
* Focus: Business logic orchestration, NOT UI rendering
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemoryLeaderboardsRepository } from '../../../adapters/leaderboards/persistence/inmemory/InMemoryLeaderboardsRepository';
|
||||
import { InMemoryLeaderboardsEventPublisher } from '../../../adapters/leaderboards/events/InMemoryLeaderboardsEventPublisher';
|
||||
import { GetDriverRankingsUseCase } from '../../../core/leaderboards/application/use-cases/GetDriverRankingsUseCase';
|
||||
import { ValidationError } from '../../../core/shared/errors/ValidationError';
|
||||
|
||||
describe('Driver Rankings Use Case Orchestration', () => {
|
||||
let leaderboardsRepository: InMemoryLeaderboardsRepository;
|
||||
let eventPublisher: InMemoryLeaderboardsEventPublisher;
|
||||
let getDriverRankingsUseCase: GetDriverRankingsUseCase;
|
||||
|
||||
beforeAll(() => {
|
||||
leaderboardsRepository = new InMemoryLeaderboardsRepository();
|
||||
eventPublisher = new InMemoryLeaderboardsEventPublisher();
|
||||
getDriverRankingsUseCase = new GetDriverRankingsUseCase({
|
||||
leaderboardsRepository,
|
||||
eventPublisher,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
leaderboardsRepository.clear();
|
||||
eventPublisher.clear();
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Success Path', () => {
|
||||
it('should retrieve all drivers with complete data', async () => {
|
||||
// Scenario: System has multiple drivers with complete data
|
||||
// Given: Multiple drivers exist with various ratings, names, and team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Jane Doe',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Speed Squad',
|
||||
raceCount: 45,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob Johnson',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with default query
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: The result should contain all drivers
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
|
||||
// And: Each driver entry should include rank, name, rating, team affiliation, and race count
|
||||
expect(result.drivers[0]).toMatchObject({
|
||||
rank: 1,
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// And: Drivers should be sorted by rating (highest first)
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(4.8);
|
||||
expect(result.drivers[2].rating).toBe(4.5);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with pagination', async () => {
|
||||
// Scenario: System has many drivers requiring pagination
|
||||
// Given: More than 20 drivers exist
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with page=1, limit=20
|
||||
const result = await getDriverRankingsUseCase.execute({ page: 1, limit: 20 });
|
||||
|
||||
// Then: The result should contain 20 drivers
|
||||
expect(result.drivers).toHaveLength(20);
|
||||
|
||||
// And: The result should include pagination metadata (total, page, limit)
|
||||
expect(result.pagination.total).toBe(25);
|
||||
expect(result.pagination.page).toBe(1);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.totalPages).toBe(2);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with different page sizes', async () => {
|
||||
// Scenario: User requests different page sizes
|
||||
// Given: More than 50 drivers exist
|
||||
for (let i = 1; i <= 60; i++) {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with limit=50
|
||||
const result = await getDriverRankingsUseCase.execute({ limit: 50 });
|
||||
|
||||
// Then: The result should contain 50 drivers
|
||||
expect(result.drivers).toHaveLength(50);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with consistent ranking order', async () => {
|
||||
// Scenario: Verify ranking consistency
|
||||
// Given: Multiple drivers exist with various ratings
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: Driver ranks should be sequential (1, 2, 3...)
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
|
||||
// And: No duplicate ranks should appear
|
||||
const ranks = result.drivers.map((d) => d.rank);
|
||||
expect(new Set(ranks).size).toBe(ranks.length);
|
||||
|
||||
// And: All ranks should be sequential
|
||||
for (let i = 0; i < ranks.length; i++) {
|
||||
expect(ranks[i]).toBe(i + 1);
|
||||
}
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with accurate data', async () => {
|
||||
// Scenario: Verify data accuracy
|
||||
// Given: Drivers exist with valid ratings, names, and team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: All driver ratings should be valid numbers
|
||||
expect(result.drivers[0].rating).toBeGreaterThan(0);
|
||||
expect(typeof result.drivers[0].rating).toBe('number');
|
||||
|
||||
// And: All driver ranks should be sequential
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
|
||||
// And: All driver names should be non-empty strings
|
||||
expect(result.drivers[0].name).toBeTruthy();
|
||||
expect(typeof result.drivers[0].name).toBe('string');
|
||||
|
||||
// And: All team affiliations should be valid
|
||||
expect(result.drivers[0].teamId).toBe('team-1');
|
||||
expect(result.drivers[0].teamName).toBe('Racing Team A');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Search Functionality', () => {
|
||||
it('should search for drivers by name', async () => {
|
||||
// Scenario: User searches for a specific driver
|
||||
// Given: Drivers exist with names: "John Smith", "Jane Doe", "Bob Johnson"
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Jane Doe',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob Johnson',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with search="John"
|
||||
const result = await getDriverRankingsUseCase.execute({ search: 'John' });
|
||||
|
||||
// Then: The result should contain drivers whose names contain "John"
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('John Smith');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Bob Johnson');
|
||||
|
||||
// And: The result should not contain drivers whose names do not contain "John"
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Jane Doe');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should search for drivers by partial name', async () => {
|
||||
// Scenario: User searches with partial name
|
||||
// Given: Drivers exist with names: "Alexander", "Alex", "Alexandra"
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Alexander',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Alex',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Alexandra',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with search="Alex"
|
||||
const result = await getDriverRankingsUseCase.execute({ search: 'Alex' });
|
||||
|
||||
// Then: The result should contain all drivers whose names start with "Alex"
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alexander');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alex');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alexandra');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive search', async () => {
|
||||
// Scenario: Search is case-insensitive
|
||||
// Given: Drivers exist with names: "John Smith", "JOHN DOE", "johnson"
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'JOHN DOE',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'johnson',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with search="john"
|
||||
const result = await getDriverRankingsUseCase.execute({ search: 'john' });
|
||||
|
||||
// Then: The result should contain all drivers whose names contain "john" (case-insensitive)
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('John Smith');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('JOHN DOE');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('johnson');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should return empty result when no drivers match search', async () => {
|
||||
// Scenario: Search returns no results
|
||||
// Given: Drivers exist
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with search="NonExistentDriver"
|
||||
const result = await getDriverRankingsUseCase.execute({ search: 'NonExistentDriver' });
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Filter Functionality', () => {
|
||||
it('should filter drivers by rating range', async () => {
|
||||
// Scenario: User filters drivers by rating
|
||||
// Given: Drivers exist with ratings: 3.5, 4.0, 4.5, 5.0
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 3.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-4',
|
||||
name: 'Driver D',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with minRating=4.0
|
||||
const result = await getDriverRankingsUseCase.execute({ minRating: 4.0 });
|
||||
|
||||
// Then: The result should only contain drivers with rating >= 4.0
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.every((d) => d.rating >= 4.0)).toBe(true);
|
||||
|
||||
// And: Drivers with rating < 4.0 should not be visible
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Driver A');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter drivers by team', async () => {
|
||||
// Scenario: User filters drivers by team
|
||||
// Given: Drivers exist with various team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Team 2',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with teamId="team-1"
|
||||
const result = await getDriverRankingsUseCase.execute({ teamId: 'team-1' });
|
||||
|
||||
// Then: The result should only contain drivers from that team
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.every((d) => d.teamId === 'team-1')).toBe(true);
|
||||
|
||||
// And: Drivers from other teams should not be visible
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Driver B');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter drivers by multiple criteria', async () => {
|
||||
// Scenario: User applies multiple filters
|
||||
// Given: Drivers exist with various ratings and team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Team 2',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-4',
|
||||
name: 'Driver D',
|
||||
rating: 3.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with minRating=4.0 and teamId="team-1"
|
||||
const result = await getDriverRankingsUseCase.execute({ minRating: 4.0, teamId: 'team-1' });
|
||||
|
||||
// Then: The result should only contain drivers from that team with rating >= 4.0
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.every((d) => d.teamId === 'team-1' && d.rating >= 4.0)).toBe(true);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle empty filter results', async () => {
|
||||
// Scenario: Filters return no results
|
||||
// Given: Drivers exist
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 3.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with minRating=10.0 (impossible)
|
||||
const result = await getDriverRankingsUseCase.execute({ minRating: 10.0 });
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Sort Functionality', () => {
|
||||
it('should sort drivers by rating (high to low)', async () => {
|
||||
// Scenario: User sorts drivers by rating
|
||||
// Given: Drivers exist with ratings: 3.5, 4.0, 4.5, 5.0
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 3.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-4',
|
||||
name: 'Driver D',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with sortBy="rating", sortOrder="desc"
|
||||
const result = await getDriverRankingsUseCase.execute({ sortBy: 'rating', sortOrder: 'desc' });
|
||||
|
||||
// Then: The result should be sorted by rating in descending order
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(4.5);
|
||||
expect(result.drivers[2].rating).toBe(4.0);
|
||||
expect(result.drivers[3].rating).toBe(3.5);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by name (A-Z)', async () => {
|
||||
// Scenario: User sorts drivers by name
|
||||
// Given: Drivers exist with names: "Zoe", "Alice", "Bob"
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Zoe',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Alice',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with sortBy="name", sortOrder="asc"
|
||||
const result = await getDriverRankingsUseCase.execute({ sortBy: 'name', sortOrder: 'asc' });
|
||||
|
||||
// Then: The result should be sorted alphabetically by name
|
||||
expect(result.drivers[0].name).toBe('Alice');
|
||||
expect(result.drivers[1].name).toBe('Bob');
|
||||
expect(result.drivers[2].name).toBe('Zoe');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by rank (low to high)', async () => {
|
||||
// Scenario: User sorts drivers by rank
|
||||
// Given: Drivers exist with various ranks
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with sortBy="rank", sortOrder="asc"
|
||||
const result = await getDriverRankingsUseCase.execute({ sortBy: 'rank', sortOrder: 'asc' });
|
||||
|
||||
// Then: The result should be sorted by rank in ascending order
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by race count (high to low)', async () => {
|
||||
// Scenario: User sorts drivers by race count
|
||||
// Given: Drivers exist with various race counts
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
raceCount: 30,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with sortBy="raceCount", sortOrder="desc"
|
||||
const result = await getDriverRankingsUseCase.execute({ sortBy: 'raceCount', sortOrder: 'desc' });
|
||||
|
||||
// Then: The result should be sorted by race count in descending order
|
||||
expect(result.drivers[0].raceCount).toBe(50);
|
||||
expect(result.drivers[1].raceCount).toBe(40);
|
||||
expect(result.drivers[2].raceCount).toBe(30);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Edge Cases', () => {
|
||||
it('should handle system with no drivers', async () => {
|
||||
// Scenario: System has no drivers
|
||||
// Given: No drivers exist in the system
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle drivers with same rating', async () => {
|
||||
// Scenario: Multiple drivers with identical ratings
|
||||
// Given: Multiple drivers exist with the same rating
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Zoe',
|
||||
rating: 5.0,
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Alice',
|
||||
rating: 5.0,
|
||||
raceCount: 45,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob',
|
||||
rating: 5.0,
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: Drivers should be sorted by rating
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(5.0);
|
||||
expect(result.drivers[2].rating).toBe(5.0);
|
||||
|
||||
// And: Drivers with same rating should have consistent ordering (e.g., by name)
|
||||
expect(result.drivers[0].name).toBe('Alice');
|
||||
expect(result.drivers[1].name).toBe('Bob');
|
||||
expect(result.drivers[2].name).toBe('Zoe');
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle drivers with no team affiliation', async () => {
|
||||
// Scenario: Drivers without team affiliation
|
||||
// Given: Drivers exist with and without team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: All drivers should be returned
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
|
||||
// And: Drivers without team should show empty or default team value
|
||||
expect(result.drivers[0].teamId).toBe('team-1');
|
||||
expect(result.drivers[0].teamName).toBe('Team 1');
|
||||
expect(result.drivers[1].teamId).toBeUndefined();
|
||||
expect(result.drivers[1].teamName).toBeUndefined();
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle pagination with empty results', async () => {
|
||||
// Scenario: Pagination with no results
|
||||
// Given: No drivers exist
|
||||
// When: GetDriverRankingsUseCase.execute() is called with page=1, limit=20
|
||||
const result = await getDriverRankingsUseCase.execute({ page: 1, limit: 20 });
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: Pagination metadata should show total=0
|
||||
expect(result.pagination.total).toBe(0);
|
||||
expect(result.pagination.page).toBe(1);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.totalPages).toBe(0);
|
||||
|
||||
// And: EventPublisher should emit DriverRankingsAccessedEvent
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetDriverRankingsUseCase - Error Handling', () => {
|
||||
it('should handle driver repository errors gracefully', async () => {
|
||||
// Scenario: Driver repository throws error
|
||||
// Given: LeaderboardsRepository throws an error during query
|
||||
const originalFindAllDrivers = leaderboardsRepository.findAllDrivers.bind(leaderboardsRepository);
|
||||
leaderboardsRepository.findAllDrivers = async () => {
|
||||
throw new Error('Repository error');
|
||||
};
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
try {
|
||||
await getDriverRankingsUseCase.execute({});
|
||||
// Should not reach here
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
// Then: Should propagate the error appropriately
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe('Repository error');
|
||||
}
|
||||
|
||||
// And: EventPublisher should NOT emit any events
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(0);
|
||||
|
||||
// Restore original method
|
||||
leaderboardsRepository.findAllDrivers = originalFindAllDrivers;
|
||||
});
|
||||
|
||||
it('should handle invalid query parameters', async () => {
|
||||
// Scenario: Invalid query parameters
|
||||
// Given: Invalid parameters (e.g., negative page, invalid sort field)
|
||||
// When: GetDriverRankingsUseCase.execute() is called with invalid parameters
|
||||
try {
|
||||
await getDriverRankingsUseCase.execute({ page: -1 });
|
||||
// Should not reach here
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
// Then: Should throw ValidationError
|
||||
expect(error).toBeInstanceOf(ValidationError);
|
||||
}
|
||||
|
||||
// And: EventPublisher should NOT emit any events
|
||||
expect(eventPublisher.getDriverRankingsAccessedEventCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Driver Rankings Data Orchestration', () => {
|
||||
it('should correctly calculate driver rankings based on rating', async () => {
|
||||
// Scenario: Driver ranking calculation
|
||||
// Given: Drivers exist with ratings: 5.0, 4.8, 4.5, 4.2, 4.0
|
||||
const ratings = [5.0, 4.8, 4.5, 4.2, 4.0];
|
||||
ratings.forEach((rating, index) => {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${index}`,
|
||||
name: `Driver ${index}`,
|
||||
rating,
|
||||
raceCount: 10 + index,
|
||||
});
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: Driver rankings should be:
|
||||
// - Rank 1: Driver with rating 5.0
|
||||
// - Rank 2: Driver with rating 4.8
|
||||
// - Rank 3: Driver with rating 4.5
|
||||
// - Rank 4: Driver with rating 4.2
|
||||
// - Rank 5: Driver with rating 4.0
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[1].rating).toBe(4.8);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
expect(result.drivers[2].rating).toBe(4.5);
|
||||
expect(result.drivers[3].rank).toBe(4);
|
||||
expect(result.drivers[3].rating).toBe(4.2);
|
||||
expect(result.drivers[4].rank).toBe(5);
|
||||
expect(result.drivers[4].rating).toBe(4.0);
|
||||
});
|
||||
|
||||
it('should correctly format driver entries with team affiliation', async () => {
|
||||
// Scenario: Driver entry formatting
|
||||
// Given: A driver exists with team affiliation
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called
|
||||
const result = await getDriverRankingsUseCase.execute({});
|
||||
|
||||
// Then: Driver entry should include:
|
||||
// - Rank: Sequential number
|
||||
// - Name: Driver's full name
|
||||
// - Rating: Driver's rating (formatted)
|
||||
// - Team: Team name and logo (if available)
|
||||
// - Race Count: Number of races completed
|
||||
const driver = result.drivers[0];
|
||||
expect(driver.rank).toBe(1);
|
||||
expect(driver.name).toBe('John Smith');
|
||||
expect(driver.rating).toBe(5.0);
|
||||
expect(driver.teamId).toBe('team-1');
|
||||
expect(driver.teamName).toBe('Racing Team A');
|
||||
expect(driver.raceCount).toBe(50);
|
||||
});
|
||||
|
||||
it('should correctly handle pagination metadata', async () => {
|
||||
// Scenario: Pagination metadata calculation
|
||||
// Given: 50 drivers exist
|
||||
for (let i = 1; i <= 50; i++) {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with page=2, limit=20
|
||||
const result = await getDriverRankingsUseCase.execute({ page: 2, limit: 20 });
|
||||
|
||||
// Then: Pagination metadata should include:
|
||||
// - Total: 50
|
||||
// - Page: 2
|
||||
// - Limit: 20
|
||||
// - Total Pages: 3
|
||||
expect(result.pagination.total).toBe(50);
|
||||
expect(result.pagination.page).toBe(2);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.totalPages).toBe(3);
|
||||
});
|
||||
|
||||
it('should correctly apply search, filter, and sort together', async () => {
|
||||
// Scenario: Combined query operations
|
||||
// Given: Drivers exist with various names, ratings, and team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'John Doe',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Team 2',
|
||||
raceCount: 45,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Jane Jenkins',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 40,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-4',
|
||||
name: 'Bob Smith',
|
||||
rating: 3.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 30,
|
||||
});
|
||||
|
||||
// When: GetDriverRankingsUseCase.execute() is called with:
|
||||
// - search: "John"
|
||||
// - minRating: 4.0
|
||||
// - teamId: "team-1"
|
||||
// - sortBy: "rating"
|
||||
// - sortOrder: "desc"
|
||||
const result = await getDriverRankingsUseCase.execute({
|
||||
search: 'John',
|
||||
minRating: 4.0,
|
||||
teamId: 'team-1',
|
||||
sortBy: 'rating',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
||||
// Then: The result should:
|
||||
// - Only contain drivers from team-1
|
||||
// - Only contain drivers with rating >= 4.0
|
||||
// - Only contain drivers whose names contain "John"
|
||||
// - Be sorted by rating in descending order
|
||||
expect(result.drivers).toHaveLength(1);
|
||||
expect(result.drivers[0].name).toBe('John Smith');
|
||||
expect(result.drivers[0].teamId).toBe('team-1');
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
import { ValidationError } from '../../../../core/shared/errors/ValidationError';
|
||||
|
||||
describe('GetDriverRankingsUseCase - Edge Cases & Errors', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle system with no drivers', async () => {
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle drivers with same rating', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Zoe', rating: 5.0, raceCount: 50 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Alice', rating: 5.0, raceCount: 45 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Bob', rating: 5.0, raceCount: 40 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(5.0);
|
||||
expect(result.drivers[2].rating).toBe(5.0);
|
||||
expect(result.drivers[0].name).toBe('Alice');
|
||||
expect(result.drivers[1].name).toBe('Bob');
|
||||
expect(result.drivers[2].name).toBe('Zoe');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle drivers with no team affiliation', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers[0].teamId).toBe('team-1');
|
||||
expect(result.drivers[1].teamId).toBeUndefined();
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle pagination with empty results', async () => {
|
||||
const result = await context.getDriverRankingsUseCase.execute({ page: 1, limit: 20 });
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
expect(result.pagination.total).toBe(0);
|
||||
expect(result.pagination.totalPages).toBe(0);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle driver repository errors gracefully', async () => {
|
||||
const originalFindAllDrivers = context.repository.findAllDrivers.bind(context.repository);
|
||||
context.repository.findAllDrivers = async () => {
|
||||
throw new Error('Repository error');
|
||||
};
|
||||
|
||||
try {
|
||||
await context.getDriverRankingsUseCase.execute({});
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe('Repository error');
|
||||
}
|
||||
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(0);
|
||||
context.repository.findAllDrivers = originalFindAllDrivers;
|
||||
});
|
||||
|
||||
it('should handle invalid query parameters', async () => {
|
||||
try {
|
||||
await context.getDriverRankingsUseCase.execute({ page: -1 });
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ValidationError);
|
||||
}
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetDriverRankingsUseCase - Filter Functionality', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should filter drivers by rating range', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 3.5, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-4', name: 'Driver D', rating: 5.0, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ minRating: 4.0 });
|
||||
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.every((d) => d.rating >= 4.0)).toBe(true);
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Driver A');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter drivers by team', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, teamId: 'team-2', teamName: 'Team 2', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ teamId: 'team-1' });
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.every((d) => d.teamId === 'team-1')).toBe(true);
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Driver B');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter drivers by multiple criteria', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, teamId: 'team-2', teamName: 'Team 2', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-4', name: 'Driver D', rating: 3.5, teamId: 'team-1', teamName: 'Team 1', raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ minRating: 4.0, teamId: 'team-1' });
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.every((d) => d.teamId === 'team-1' && d.rating >= 4.0)).toBe(true);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle empty filter results', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 3.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ minRating: 10.0 });
|
||||
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetDriverRankingsUseCase - Search Functionality', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should search for drivers by name', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'John Smith', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Jane Doe', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Bob Johnson', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ search: 'John' });
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('John Smith');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Bob Johnson');
|
||||
expect(result.drivers.map((d) => d.name)).not.toContain('Jane Doe');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should search for drivers by partial name', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Alexander', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Alex', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Alexandra', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ search: 'Alex' });
|
||||
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alexander');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alex');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('Alexandra');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle case-insensitive search', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'John Smith', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'JOHN DOE', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'johnson', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ search: 'john' });
|
||||
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers.map((d) => d.name)).toContain('John Smith');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('JOHN DOE');
|
||||
expect(result.drivers.map((d) => d.name)).toContain('johnson');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should return empty result when no drivers match search', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'John Smith', rating: 5.0, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ search: 'NonExistentDriver' });
|
||||
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetDriverRankingsUseCase - Sort Functionality', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should sort drivers by rating (high to low)', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 3.5, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-4', name: 'Driver D', rating: 5.0, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ sortBy: 'rating', sortOrder: 'desc' });
|
||||
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(4.5);
|
||||
expect(result.drivers[2].rating).toBe(4.0);
|
||||
expect(result.drivers[3].rating).toBe(3.5);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by name (A-Z)', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Zoe', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Alice', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Bob', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ sortBy: 'name', sortOrder: 'asc' });
|
||||
|
||||
expect(result.drivers[0].name).toBe('Alice');
|
||||
expect(result.drivers[1].name).toBe('Bob');
|
||||
expect(result.drivers[2].name).toBe('Zoe');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by rank (low to high)', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ sortBy: 'rank', sortOrder: 'asc' });
|
||||
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should sort drivers by race count (high to low)', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, raceCount: 50 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, raceCount: 30 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, raceCount: 40 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ sortBy: 'raceCount', sortOrder: 'desc' });
|
||||
|
||||
expect(result.drivers[0].raceCount).toBe(50);
|
||||
expect(result.drivers[1].raceCount).toBe(40);
|
||||
expect(result.drivers[2].raceCount).toBe(30);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetDriverRankingsUseCase - Success Path', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should retrieve all drivers with complete data', async () => {
|
||||
context.repository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
context.repository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Jane Doe',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Speed Squad',
|
||||
raceCount: 45,
|
||||
});
|
||||
context.repository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob Johnson',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
expect(result.drivers[0]).toMatchObject({
|
||||
rank: 1,
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(4.8);
|
||||
expect(result.drivers[2].rating).toBe(4.5);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with pagination', async () => {
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
context.repository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ page: 1, limit: 20 });
|
||||
|
||||
expect(result.drivers).toHaveLength(20);
|
||||
expect(result.pagination.total).toBe(25);
|
||||
expect(result.pagination.page).toBe(1);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.totalPages).toBe(2);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with different page sizes', async () => {
|
||||
for (let i = 1; i <= 60; i++) {
|
||||
context.repository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({ limit: 50 });
|
||||
|
||||
expect(result.drivers).toHaveLength(50);
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with consistent ranking order', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'Driver A', rating: 5.0, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Driver B', rating: 4.8, raceCount: 10 });
|
||||
context.repository.addDriver({ id: 'driver-3', name: 'Driver C', rating: 4.5, raceCount: 10 });
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
|
||||
const ranks = result.drivers.map((d) => d.rank);
|
||||
expect(new Set(ranks).size).toBe(ranks.length);
|
||||
for (let i = 0; i < ranks.length; i++) {
|
||||
expect(ranks[i]).toBe(i + 1);
|
||||
}
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve drivers with accurate data', async () => {
|
||||
context.repository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
const result = await context.getDriverRankingsUseCase.execute({});
|
||||
|
||||
expect(result.drivers[0].rating).toBeGreaterThan(0);
|
||||
expect(typeof result.drivers[0].rating).toBe('number');
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[0].name).toBeTruthy();
|
||||
expect(typeof result.drivers[0].name).toBe('string');
|
||||
expect(result.drivers[0].teamId).toBe('team-1');
|
||||
expect(result.drivers[0].teamName).toBe('Racing Team A');
|
||||
expect(context.eventPublisher.getDriverRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,667 +0,0 @@
|
||||
/**
|
||||
* Integration Test: Global Leaderboards Use Case Orchestration
|
||||
*
|
||||
* Tests the orchestration logic of global leaderboards-related Use Cases:
|
||||
* - GetGlobalLeaderboardsUseCase: Retrieves top drivers and teams for the main leaderboards page
|
||||
* - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
|
||||
* - Uses In-Memory adapters for fast, deterministic testing
|
||||
*
|
||||
* Focus: Business logic orchestration, NOT UI rendering
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { InMemoryLeaderboardsRepository } from '../../../adapters/leaderboards/persistence/inmemory/InMemoryLeaderboardsRepository';
|
||||
import { InMemoryLeaderboardsEventPublisher } from '../../../adapters/leaderboards/events/InMemoryLeaderboardsEventPublisher';
|
||||
import { GetGlobalLeaderboardsUseCase } from '../../../core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase';
|
||||
|
||||
describe('Global Leaderboards Use Case Orchestration', () => {
|
||||
let leaderboardsRepository: InMemoryLeaderboardsRepository;
|
||||
let eventPublisher: InMemoryLeaderboardsEventPublisher;
|
||||
let getGlobalLeaderboardsUseCase: GetGlobalLeaderboardsUseCase;
|
||||
|
||||
beforeAll(() => {
|
||||
leaderboardsRepository = new InMemoryLeaderboardsRepository();
|
||||
eventPublisher = new InMemoryLeaderboardsEventPublisher();
|
||||
getGlobalLeaderboardsUseCase = new GetGlobalLeaderboardsUseCase({
|
||||
leaderboardsRepository,
|
||||
eventPublisher,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
leaderboardsRepository.clear();
|
||||
eventPublisher.clear();
|
||||
});
|
||||
|
||||
describe('GetGlobalLeaderboardsUseCase - Success Path', () => {
|
||||
it('should retrieve top drivers and teams with complete data', async () => {
|
||||
// Scenario: System has multiple drivers and teams with complete data
|
||||
// Given: Multiple drivers exist with various ratings and team affiliations
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Jane Doe',
|
||||
rating: 4.8,
|
||||
teamId: 'team-2',
|
||||
teamName: 'Speed Squad',
|
||||
raceCount: 45,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob Johnson',
|
||||
rating: 4.5,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
// And: Multiple teams exist with various ratings and member counts
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-2',
|
||||
name: 'Speed Squad',
|
||||
rating: 4.7,
|
||||
memberCount: 3,
|
||||
raceCount: 80,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-3',
|
||||
name: 'Champions League',
|
||||
rating: 4.3,
|
||||
memberCount: 4,
|
||||
raceCount: 60,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain top 10 drivers (but we only have 3)
|
||||
expect(result.drivers).toHaveLength(3);
|
||||
|
||||
// And: The result should contain top 10 teams (but we only have 3)
|
||||
expect(result.teams).toHaveLength(3);
|
||||
|
||||
// And: Driver entries should include rank, name, rating, and team affiliation
|
||||
expect(result.drivers[0]).toMatchObject({
|
||||
rank: 1,
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// And: Team entries should include rank, name, rating, and member count
|
||||
expect(result.teams[0]).toMatchObject({
|
||||
rank: 1,
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve top drivers and teams with minimal data', async () => {
|
||||
// Scenario: System has minimal data
|
||||
// Given: Only a few drivers exist
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// And: Only a few teams exist
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 2,
|
||||
raceCount: 20,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain all available drivers
|
||||
expect(result.drivers).toHaveLength(1);
|
||||
expect(result.drivers[0].name).toBe('John Smith');
|
||||
|
||||
// And: The result should contain all available teams
|
||||
expect(result.teams).toHaveLength(1);
|
||||
expect(result.teams[0].name).toBe('Racing Team A');
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve top drivers and teams when there are many', async () => {
|
||||
// Scenario: System has many drivers and teams
|
||||
// Given: More than 10 drivers exist
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// And: More than 10 teams exist
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
leaderboardsRepository.addTeam({
|
||||
id: `team-${i}`,
|
||||
name: `Team ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
memberCount: 2 + i,
|
||||
raceCount: 20 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain only top 10 drivers
|
||||
expect(result.drivers).toHaveLength(10);
|
||||
|
||||
// And: The result should contain only top 10 teams
|
||||
expect(result.teams).toHaveLength(10);
|
||||
|
||||
// And: Drivers should be sorted by rating (highest first)
|
||||
expect(result.drivers[0].rating).toBe(4.9); // Driver 1
|
||||
expect(result.drivers[9].rating).toBe(4.0); // Driver 10
|
||||
|
||||
// And: Teams should be sorted by rating (highest first)
|
||||
expect(result.teams[0].rating).toBe(4.9); // Team 1
|
||||
expect(result.teams[9].rating).toBe(4.0); // Team 10
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve top drivers and teams with consistent ranking order', async () => {
|
||||
// Scenario: Verify ranking consistency
|
||||
// Given: Multiple drivers exist with various ratings
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Driver A',
|
||||
rating: 5.0,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Driver B',
|
||||
rating: 4.8,
|
||||
raceCount: 10,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Driver C',
|
||||
rating: 4.5,
|
||||
raceCount: 10,
|
||||
});
|
||||
|
||||
// And: Multiple teams exist with various ratings
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 2,
|
||||
raceCount: 20,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-2',
|
||||
name: 'Team B',
|
||||
rating: 4.7,
|
||||
memberCount: 2,
|
||||
raceCount: 20,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-3',
|
||||
name: 'Team C',
|
||||
rating: 4.3,
|
||||
memberCount: 2,
|
||||
raceCount: 20,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Driver ranks should be sequential (1, 2, 3...)
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
|
||||
// And: Team ranks should be sequential (1, 2, 3...)
|
||||
expect(result.teams[0].rank).toBe(1);
|
||||
expect(result.teams[1].rank).toBe(2);
|
||||
expect(result.teams[2].rank).toBe(3);
|
||||
|
||||
// And: No duplicate ranks should appear
|
||||
const driverRanks = result.drivers.map((d) => d.rank);
|
||||
const teamRanks = result.teams.map((t) => t.rank);
|
||||
expect(new Set(driverRanks).size).toBe(driverRanks.length);
|
||||
expect(new Set(teamRanks).size).toBe(teamRanks.length);
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve top drivers and teams with accurate data', async () => {
|
||||
// Scenario: Verify data accuracy
|
||||
// Given: Drivers exist with valid ratings and names
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// And: Teams exist with valid ratings and member counts
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: All driver ratings should be valid numbers
|
||||
expect(result.drivers[0].rating).toBeGreaterThan(0);
|
||||
expect(typeof result.drivers[0].rating).toBe('number');
|
||||
|
||||
// And: All team ratings should be valid numbers
|
||||
expect(result.teams[0].rating).toBeGreaterThan(0);
|
||||
expect(typeof result.teams[0].rating).toBe('number');
|
||||
|
||||
// And: All team member counts should be valid numbers
|
||||
expect(result.teams[0].memberCount).toBeGreaterThan(0);
|
||||
expect(typeof result.teams[0].memberCount).toBe('number');
|
||||
|
||||
// And: All names should be non-empty strings
|
||||
expect(result.drivers[0].name).toBeTruthy();
|
||||
expect(typeof result.drivers[0].name).toBe('string');
|
||||
expect(result.teams[0].name).toBeTruthy();
|
||||
expect(typeof result.teams[0].name).toBe('string');
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetGlobalLeaderboardsUseCase - Edge Cases', () => {
|
||||
it('should handle system with no drivers', async () => {
|
||||
// Scenario: System has no drivers
|
||||
// Given: No drivers exist in the system
|
||||
// And: Teams exist
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: The result should contain top teams
|
||||
expect(result.teams).toHaveLength(1);
|
||||
expect(result.teams[0].name).toBe('Racing Team A');
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle system with no teams', async () => {
|
||||
// Scenario: System has no teams
|
||||
// Given: Drivers exist
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// And: No teams exist in the system
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain top drivers
|
||||
expect(result.drivers).toHaveLength(1);
|
||||
expect(result.drivers[0].name).toBe('John Smith');
|
||||
|
||||
// And: The result should contain empty teams list
|
||||
expect(result.teams).toHaveLength(0);
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle system with no data at all', async () => {
|
||||
// Scenario: System has absolutely no data
|
||||
// Given: No drivers exist
|
||||
// And: No teams exist
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: The result should contain empty drivers list
|
||||
expect(result.drivers).toHaveLength(0);
|
||||
|
||||
// And: The result should contain empty teams list
|
||||
expect(result.teams).toHaveLength(0);
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle drivers with same rating', async () => {
|
||||
// Scenario: Multiple drivers with identical ratings
|
||||
// Given: Multiple drivers exist with the same rating
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'Zoe',
|
||||
rating: 5.0,
|
||||
raceCount: 50,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-2',
|
||||
name: 'Alice',
|
||||
rating: 5.0,
|
||||
raceCount: 45,
|
||||
});
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-3',
|
||||
name: 'Bob',
|
||||
rating: 5.0,
|
||||
raceCount: 40,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Drivers should be sorted by rating
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rating).toBe(5.0);
|
||||
expect(result.drivers[2].rating).toBe(5.0);
|
||||
|
||||
// And: Drivers with same rating should have consistent ordering (by name)
|
||||
expect(result.drivers[0].name).toBe('Alice');
|
||||
expect(result.drivers[1].name).toBe('Bob');
|
||||
expect(result.drivers[2].name).toBe('Zoe');
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle teams with same rating', async () => {
|
||||
// Scenario: Multiple teams with identical ratings
|
||||
// Given: Multiple teams exist with the same rating
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Zeta Team',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-2',
|
||||
name: 'Alpha Team',
|
||||
rating: 4.9,
|
||||
memberCount: 3,
|
||||
raceCount: 80,
|
||||
});
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-3',
|
||||
name: 'Beta Team',
|
||||
rating: 4.9,
|
||||
memberCount: 4,
|
||||
raceCount: 60,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Teams should be sorted by rating
|
||||
expect(result.teams[0].rating).toBe(4.9);
|
||||
expect(result.teams[1].rating).toBe(4.9);
|
||||
expect(result.teams[2].rating).toBe(4.9);
|
||||
|
||||
// And: Teams with same rating should have consistent ordering (by name)
|
||||
expect(result.teams[0].name).toBe('Alpha Team');
|
||||
expect(result.teams[1].name).toBe('Beta Team');
|
||||
expect(result.teams[2].name).toBe('Zeta Team');
|
||||
|
||||
// And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetGlobalLeaderboardsUseCase - Error Handling', () => {
|
||||
it('should handle repository errors gracefully', async () => {
|
||||
// Scenario: Repository throws error
|
||||
// Given: LeaderboardsRepository throws an error during query
|
||||
const originalFindAllDrivers = leaderboardsRepository.findAllDrivers.bind(leaderboardsRepository);
|
||||
leaderboardsRepository.findAllDrivers = async () => {
|
||||
throw new Error('Repository error');
|
||||
};
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
try {
|
||||
await getGlobalLeaderboardsUseCase.execute();
|
||||
// Should not reach here
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
// Then: Should propagate the error appropriately
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe('Repository error');
|
||||
}
|
||||
|
||||
// And: EventPublisher should NOT emit any events
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(0);
|
||||
|
||||
// Restore original method
|
||||
leaderboardsRepository.findAllDrivers = originalFindAllDrivers;
|
||||
});
|
||||
|
||||
it('should handle team repository errors gracefully', async () => {
|
||||
// Scenario: Team repository throws error
|
||||
// Given: LeaderboardsRepository throws an error during query
|
||||
const originalFindAllTeams = leaderboardsRepository.findAllTeams.bind(leaderboardsRepository);
|
||||
leaderboardsRepository.findAllTeams = async () => {
|
||||
throw new Error('Team repository error');
|
||||
};
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
try {
|
||||
await getGlobalLeaderboardsUseCase.execute();
|
||||
// Should not reach here
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
// Then: Should propagate the error appropriately
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe('Team repository error');
|
||||
}
|
||||
|
||||
// And: EventPublisher should NOT emit any events
|
||||
expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(0);
|
||||
|
||||
// Restore original method
|
||||
leaderboardsRepository.findAllTeams = originalFindAllTeams;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global Leaderboards Data Orchestration', () => {
|
||||
it('should correctly calculate driver rankings based on rating', async () => {
|
||||
// Scenario: Driver ranking calculation
|
||||
// Given: Drivers exist with ratings: 5.0, 4.8, 4.5, 4.2, 4.0
|
||||
const ratings = [5.0, 4.8, 4.5, 4.2, 4.0];
|
||||
ratings.forEach((rating, index) => {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${index}`,
|
||||
name: `Driver ${index}`,
|
||||
rating,
|
||||
raceCount: 10 + index,
|
||||
});
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Driver rankings should be correct
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.drivers[0].rating).toBe(5.0);
|
||||
expect(result.drivers[1].rank).toBe(2);
|
||||
expect(result.drivers[1].rating).toBe(4.8);
|
||||
expect(result.drivers[2].rank).toBe(3);
|
||||
expect(result.drivers[2].rating).toBe(4.5);
|
||||
expect(result.drivers[3].rank).toBe(4);
|
||||
expect(result.drivers[3].rating).toBe(4.2);
|
||||
expect(result.drivers[4].rank).toBe(5);
|
||||
expect(result.drivers[4].rating).toBe(4.0);
|
||||
});
|
||||
|
||||
it('should correctly calculate team rankings based on rating', async () => {
|
||||
// Scenario: Team ranking calculation
|
||||
// Given: Teams exist with ratings: 4.9, 4.7, 4.6, 4.3, 4.1
|
||||
const ratings = [4.9, 4.7, 4.6, 4.3, 4.1];
|
||||
ratings.forEach((rating, index) => {
|
||||
leaderboardsRepository.addTeam({
|
||||
id: `team-${index}`,
|
||||
name: `Team ${index}`,
|
||||
rating,
|
||||
memberCount: 2 + index,
|
||||
raceCount: 20 + index,
|
||||
});
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Team rankings should be correct
|
||||
expect(result.teams[0].rank).toBe(1);
|
||||
expect(result.teams[0].rating).toBe(4.9);
|
||||
expect(result.teams[1].rank).toBe(2);
|
||||
expect(result.teams[1].rating).toBe(4.7);
|
||||
expect(result.teams[2].rank).toBe(3);
|
||||
expect(result.teams[2].rating).toBe(4.6);
|
||||
expect(result.teams[3].rank).toBe(4);
|
||||
expect(result.teams[3].rating).toBe(4.3);
|
||||
expect(result.teams[4].rank).toBe(5);
|
||||
expect(result.teams[4].rating).toBe(4.1);
|
||||
});
|
||||
|
||||
it('should correctly format driver entries with team affiliation', async () => {
|
||||
// Scenario: Driver entry formatting
|
||||
// Given: A driver exists with team affiliation
|
||||
leaderboardsRepository.addDriver({
|
||||
id: 'driver-1',
|
||||
name: 'John Smith',
|
||||
rating: 5.0,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Racing Team A',
|
||||
raceCount: 50,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Driver entry should include all required fields
|
||||
const driver = result.drivers[0];
|
||||
expect(driver.rank).toBe(1);
|
||||
expect(driver.id).toBe('driver-1');
|
||||
expect(driver.name).toBe('John Smith');
|
||||
expect(driver.rating).toBe(5.0);
|
||||
expect(driver.teamId).toBe('team-1');
|
||||
expect(driver.teamName).toBe('Racing Team A');
|
||||
expect(driver.raceCount).toBe(50);
|
||||
});
|
||||
|
||||
it('should correctly format team entries with member count', async () => {
|
||||
// Scenario: Team entry formatting
|
||||
// Given: A team exists with members
|
||||
leaderboardsRepository.addTeam({
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Team entry should include all required fields
|
||||
const team = result.teams[0];
|
||||
expect(team.rank).toBe(1);
|
||||
expect(team.id).toBe('team-1');
|
||||
expect(team.name).toBe('Racing Team A');
|
||||
expect(team.rating).toBe(4.9);
|
||||
expect(team.memberCount).toBe(5);
|
||||
expect(team.raceCount).toBe(100);
|
||||
});
|
||||
|
||||
it('should limit results to top 10 drivers and teams', async () => {
|
||||
// Scenario: Result limiting
|
||||
// Given: More than 10 drivers exist
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
leaderboardsRepository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
raceCount: 10 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// And: More than 10 teams exist
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
leaderboardsRepository.addTeam({
|
||||
id: `team-${i}`,
|
||||
name: `Team ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
memberCount: 2 + i,
|
||||
raceCount: 20 + i,
|
||||
});
|
||||
}
|
||||
|
||||
// When: GetGlobalLeaderboardsUseCase.execute() is called
|
||||
const result = await getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
// Then: Only top 10 drivers should be returned
|
||||
expect(result.drivers).toHaveLength(10);
|
||||
|
||||
// And: Only top 10 teams should be returned
|
||||
expect(result.teams).toHaveLength(10);
|
||||
|
||||
// And: Results should be sorted by rating (highest first)
|
||||
expect(result.drivers[0].rating).toBe(4.9); // Driver 1
|
||||
expect(result.drivers[9].rating).toBe(4.0); // Driver 10
|
||||
expect(result.teams[0].rating).toBe(4.9); // Team 1
|
||||
expect(result.teams[9].rating).toBe(4.0); // Team 10
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetGlobalLeaderboardsUseCase - Success Path', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should retrieve top drivers and teams with complete data', async () => {
|
||||
context.repository.addDriver({ id: 'driver-1', name: 'John Smith', rating: 5.0, teamId: 'team-1', teamName: 'Racing Team A', raceCount: 50 });
|
||||
context.repository.addDriver({ id: 'driver-2', name: 'Jane Doe', rating: 4.8, teamId: 'team-2', teamName: 'Speed Squad', raceCount: 45 });
|
||||
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Racing Team A', rating: 4.9, memberCount: 5, raceCount: 100 });
|
||||
context.repository.addTeam({ id: 'team-2', name: 'Speed Squad', rating: 4.7, memberCount: 3, raceCount: 80 });
|
||||
|
||||
const result = await context.getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.teams).toHaveLength(2);
|
||||
expect(result.drivers[0].rank).toBe(1);
|
||||
expect(result.teams[0].rank).toBe(1);
|
||||
expect(context.eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should limit results to top 10 drivers and teams', async () => {
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
context.repository.addDriver({ id: `driver-${i}`, name: `Driver ${i}`, rating: 5.0 - i * 0.1, raceCount: 10 + i });
|
||||
context.repository.addTeam({ id: `team-${i}`, name: `Team ${i}`, rating: 5.0 - i * 0.1, memberCount: 2 + i, raceCount: 20 + i });
|
||||
}
|
||||
|
||||
const result = await context.getGlobalLeaderboardsUseCase.execute();
|
||||
|
||||
expect(result.drivers).toHaveLength(10);
|
||||
expect(result.teams).toHaveLength(10);
|
||||
expect(result.drivers[0].rating).toBe(4.9);
|
||||
expect(result.teams[0].rating).toBe(4.9);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetTeamRankingsUseCase - Data Orchestration', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should correctly calculate team rankings based on rating', async () => {
|
||||
const ratings = [4.9, 4.7, 4.6, 4.3, 4.1];
|
||||
ratings.forEach((rating, index) => {
|
||||
context.repository.addTeam({
|
||||
id: `team-${index}`,
|
||||
name: `Team ${index}`,
|
||||
rating,
|
||||
memberCount: 2 + index,
|
||||
raceCount: 20 + index,
|
||||
});
|
||||
});
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({});
|
||||
|
||||
expect(result.teams[0].rank).toBe(1);
|
||||
expect(result.teams[0].rating).toBe(4.9);
|
||||
expect(result.teams[4].rank).toBe(5);
|
||||
expect(result.teams[4].rating).toBe(4.1);
|
||||
});
|
||||
|
||||
it('should correctly aggregate member counts from drivers', async () => {
|
||||
// Scenario: Member count aggregation
|
||||
// Given: A team exists with 5 drivers
|
||||
// And: Each driver is affiliated with the team
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
context.repository.addDriver({
|
||||
id: `driver-${i}`,
|
||||
name: `Driver ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
teamId: 'team-1',
|
||||
teamName: 'Team 1',
|
||||
raceCount: 10,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({});
|
||||
|
||||
expect(result.teams[0].memberCount).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetTeamRankingsUseCase - Search & Filter', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
describe('Search', () => {
|
||||
it('should search for teams by name', async () => {
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Racing Team', rating: 4.9, memberCount: 5, raceCount: 100 });
|
||||
context.repository.addTeam({ id: 'team-2', name: 'Speed Squad', rating: 4.7, memberCount: 3, raceCount: 80 });
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({ search: 'Racing' });
|
||||
|
||||
expect(result.teams).toHaveLength(1);
|
||||
expect(result.teams[0].name).toBe('Racing Team');
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter', () => {
|
||||
it('should filter teams by rating range', async () => {
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Team A', rating: 3.5, memberCount: 2, raceCount: 20 });
|
||||
context.repository.addTeam({ id: 'team-2', name: 'Team B', rating: 4.0, memberCount: 2, raceCount: 20 });
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({ minRating: 4.0 });
|
||||
|
||||
expect(result.teams).toHaveLength(1);
|
||||
expect(result.teams[0].rating).toBe(4.0);
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter teams by member count', async () => {
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Team A', rating: 4.9, memberCount: 2, raceCount: 20 });
|
||||
context.repository.addTeam({ id: 'team-2', name: 'Team B', rating: 4.7, memberCount: 5, raceCount: 20 });
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({ minMemberCount: 5 });
|
||||
|
||||
expect(result.teams).toHaveLength(1);
|
||||
expect(result.teams[0].memberCount).toBe(5);
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { LeaderboardsTestContext } from '../LeaderboardsTestContext';
|
||||
|
||||
describe('GetTeamRankingsUseCase - Success Path', () => {
|
||||
let context: LeaderboardsTestContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = LeaderboardsTestContext.create();
|
||||
context.clear();
|
||||
});
|
||||
|
||||
it('should retrieve all teams with complete data', async () => {
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Racing Team A', rating: 4.9, memberCount: 5, raceCount: 100 });
|
||||
context.repository.addTeam({ id: 'team-2', name: 'Speed Squad', rating: 4.7, memberCount: 3, raceCount: 80 });
|
||||
context.repository.addTeam({ id: 'team-3', name: 'Champions League', rating: 4.3, memberCount: 4, raceCount: 60 });
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({});
|
||||
|
||||
expect(result.teams).toHaveLength(3);
|
||||
expect(result.teams[0]).toMatchObject({
|
||||
rank: 1,
|
||||
id: 'team-1',
|
||||
name: 'Racing Team A',
|
||||
rating: 4.9,
|
||||
memberCount: 5,
|
||||
raceCount: 100,
|
||||
});
|
||||
expect(result.teams[0].rating).toBe(4.9);
|
||||
expect(result.teams[1].rating).toBe(4.7);
|
||||
expect(result.teams[2].rating).toBe(4.3);
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve teams with pagination', async () => {
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
context.repository.addTeam({
|
||||
id: `team-${i}`,
|
||||
name: `Team ${i}`,
|
||||
rating: 5.0 - i * 0.1,
|
||||
memberCount: 2 + i,
|
||||
raceCount: 20 + i,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({ page: 1, limit: 20 });
|
||||
|
||||
expect(result.teams).toHaveLength(20);
|
||||
expect(result.pagination.total).toBe(25);
|
||||
expect(result.pagination.page).toBe(1);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.totalPages).toBe(2);
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('should retrieve teams with accurate data', async () => {
|
||||
context.repository.addTeam({ id: 'team-1', name: 'Racing Team A', rating: 4.9, memberCount: 5, raceCount: 100 });
|
||||
|
||||
const result = await context.getTeamRankingsUseCase.execute({});
|
||||
|
||||
expect(result.teams[0].rating).toBeGreaterThan(0);
|
||||
expect(typeof result.teams[0].rating).toBe('number');
|
||||
expect(result.teams[0].rank).toBe(1);
|
||||
expect(result.teams[0].name).toBeTruthy();
|
||||
expect(typeof result.teams[0].name).toBe('string');
|
||||
expect(result.teams[0].memberCount).toBeGreaterThan(0);
|
||||
expect(context.eventPublisher.getTeamRankingsAccessedEventCount()).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user