refactor
This commit is contained in:
239
adapters/bootstrap/LeagueScoringPresets.ts
Normal file
239
adapters/bootstrap/LeagueScoringPresets.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
|
||||
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
||||
import type { SessionType } from '@core/racing/domain/types/SessionType';
|
||||
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
||||
import type { DropScorePolicy } from '@core/racing/domain/types/DropScorePolicy';
|
||||
import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType';
|
||||
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
|
||||
|
||||
export type LeagueScoringPresetPrimaryChampionshipType =
|
||||
| 'driver'
|
||||
| 'team'
|
||||
| 'nations'
|
||||
| 'trophy';
|
||||
|
||||
export interface LeagueScoringPreset {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType;
|
||||
dropPolicySummary: string;
|
||||
sessionSummary: string;
|
||||
bonusSummary: string;
|
||||
createConfig: (options: { seasonId: string }) => LeagueScoringConfig;
|
||||
}
|
||||
|
||||
const mainPointsSprintMain = new PointsTable({
|
||||
1: 25,
|
||||
2: 18,
|
||||
3: 15,
|
||||
4: 12,
|
||||
5: 10,
|
||||
6: 8,
|
||||
7: 6,
|
||||
8: 4,
|
||||
9: 2,
|
||||
10: 1,
|
||||
});
|
||||
|
||||
const sprintPointsSprintMain = new PointsTable({
|
||||
1: 8,
|
||||
2: 7,
|
||||
3: 6,
|
||||
4: 5,
|
||||
5: 4,
|
||||
6: 3,
|
||||
7: 2,
|
||||
8: 1,
|
||||
});
|
||||
|
||||
const clubMainPoints = new PointsTable({
|
||||
1: 20,
|
||||
2: 15,
|
||||
3: 12,
|
||||
4: 10,
|
||||
5: 8,
|
||||
6: 6,
|
||||
7: 4,
|
||||
8: 2,
|
||||
9: 1,
|
||||
});
|
||||
|
||||
const enduranceMainPoints = new PointsTable({
|
||||
1: 50,
|
||||
2: 36,
|
||||
3: 30,
|
||||
4: 24,
|
||||
5: 20,
|
||||
6: 16,
|
||||
7: 12,
|
||||
8: 8,
|
||||
9: 4,
|
||||
10: 2,
|
||||
});
|
||||
|
||||
export const leagueScoringPresets: LeagueScoringPreset[] = [
|
||||
{
|
||||
id: 'sprint-main-driver',
|
||||
name: 'Sprint + Main',
|
||||
description:
|
||||
'Short sprint race plus main race; sprint gives fewer points.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'Best 6 results of 8 count towards the championship.',
|
||||
sessionSummary: 'Sprint + Main',
|
||||
bonusSummary: 'Fastest lap +1 point in main race if finishing P10 or better.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const fastestLapBonus: BonusRule = {
|
||||
id: 'fastest-lap-main',
|
||||
type: 'fastestLap',
|
||||
points: 1,
|
||||
requiresFinishInTopN: 10,
|
||||
};
|
||||
|
||||
const sessionTypes: SessionType[] = ['sprint', 'main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: sprintPointsSprintMain,
|
||||
main: mainPointsSprintMain,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {
|
||||
sprint: [],
|
||||
main: [fastestLapBonus],
|
||||
practice: [],
|
||||
qualifying: [],
|
||||
q1: [],
|
||||
q2: [],
|
||||
q3: [],
|
||||
timeTrial: [],
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'bestNResults',
|
||||
count: 6,
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-sprint-main',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
bonusRulesBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return LeagueScoringConfig.create({
|
||||
id: `lsc-${seasonId}-sprint-main-driver`,
|
||||
seasonId,
|
||||
scoringPresetId: 'sprint-main-driver',
|
||||
championships: [championship],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'club-default',
|
||||
name: 'Club ladder',
|
||||
description:
|
||||
'Simple club ladder with a single main race and no bonuses or drop scores.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'All race results count, no drop scores.',
|
||||
sessionSummary: 'Main race only',
|
||||
bonusSummary: 'No bonus points.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const sessionTypes: SessionType[] = ['main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: new PointsTable({}),
|
||||
main: clubMainPoints,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'none',
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-club-default',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return LeagueScoringConfig.create({
|
||||
id: `lsc-${seasonId}-club-default`,
|
||||
seasonId,
|
||||
scoringPresetId: 'club-default',
|
||||
championships: [championship],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'endurance-main-double',
|
||||
name: 'Endurance weekend',
|
||||
description:
|
||||
'Single main endurance race with double points and a simple drop policy.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'Best 4 results of 6 count towards the championship.',
|
||||
sessionSummary: 'Main race only',
|
||||
bonusSummary: 'No bonus points.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const sessionTypes: SessionType[] = ['main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: new PointsTable({}),
|
||||
main: enduranceMainPoints,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'bestNResults',
|
||||
count: 4,
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-endurance-main-double',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return {
|
||||
id: `lsc-${seasonId}-endurance-main-double`,
|
||||
seasonId,
|
||||
scoringPresetId: 'endurance-main-double',
|
||||
championships: [championship],
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function listLeagueScoringPresets(): LeagueScoringPreset[] {
|
||||
return [...leagueScoringPresets];
|
||||
}
|
||||
|
||||
export function getLeagueScoringPresetById(
|
||||
id: string,
|
||||
): LeagueScoringPreset | undefined {
|
||||
return leagueScoringPresets.find((preset) => preset.id === id);
|
||||
}
|
||||
82
adapters/bootstrap/ScoringDemoSetup.ts
Normal file
82
adapters/bootstrap/ScoringDemoSetup.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Game } from '@core/racing/domain/entities/Game';
|
||||
import { Season } from '@core/racing/domain/entities/season/Season';
|
||||
import type { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
|
||||
import { InMemoryGameRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
|
||||
import { InMemorySeasonRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
|
||||
import { InMemoryLeagueScoringConfigRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
|
||||
import { InMemoryChampionshipStandingRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { getLeagueScoringPresetById } from './LeagueScoringPresets';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
class SilentLogger implements Logger {
|
||||
debug(...args: unknown[]): void {
|
||||
// console.debug(...args);
|
||||
}
|
||||
info(...args: unknown[]): void {
|
||||
// console.info(...args);
|
||||
}
|
||||
warn(...args: unknown[]): void {
|
||||
// console.warn(...args);
|
||||
}
|
||||
error(...args: unknown[]): void {
|
||||
// console.error(...args);
|
||||
}
|
||||
}
|
||||
|
||||
export function createSprintMainDemoScoringSetup(params: {
|
||||
leagueId: string;
|
||||
seasonId?: string;
|
||||
}): {
|
||||
gameRepo: InMemoryGameRepository;
|
||||
seasonRepo: InMemorySeasonRepository;
|
||||
scoringConfigRepo: InMemoryLeagueScoringConfigRepository;
|
||||
championshipStandingRepo: InMemoryChampionshipStandingRepository;
|
||||
seasonId: string;
|
||||
championshipId: string;
|
||||
} {
|
||||
const { leagueId } = params;
|
||||
const seasonId = params.seasonId ?? 'season-sprint-main-demo';
|
||||
const championshipId = 'driver-champ';
|
||||
|
||||
const logger = new SilentLogger();
|
||||
|
||||
const game = Game.create({ id: 'iracing', name: 'iRacing' });
|
||||
|
||||
const season = Season.create({
|
||||
id: seasonId,
|
||||
leagueId,
|
||||
gameId: game.id.toString(),
|
||||
name: 'Sprint + Main Demo Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
|
||||
const preset = getLeagueScoringPresetById('sprint-main-driver');
|
||||
if (!preset) {
|
||||
throw new Error('Missing sprint-main-driver scoring preset');
|
||||
}
|
||||
|
||||
const leagueScoringConfig: LeagueScoringConfig = preset.createConfig({
|
||||
seasonId: season.id,
|
||||
});
|
||||
|
||||
const gameRepo = new InMemoryGameRepository(logger, [game]);
|
||||
const seasonRepo = new InMemorySeasonRepository(logger, [season]);
|
||||
const scoringConfigRepo = new InMemoryLeagueScoringConfigRepository(logger, [
|
||||
leagueScoringConfig,
|
||||
]);
|
||||
const championshipStandingRepo = new InMemoryChampionshipStandingRepository(logger);
|
||||
|
||||
return {
|
||||
gameRepo,
|
||||
seasonRepo,
|
||||
scoringConfigRepo,
|
||||
championshipStandingRepo,
|
||||
seasonId: season.id,
|
||||
championshipId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryCarRepository } from './InMemoryCarRepository';
|
||||
import { Car } from '@core/racing/domain/entities/Car';
|
||||
import { CarClass } from '@core/racing/domain/entities/CarClass';
|
||||
import { CarLicense } from '@core/racing/domain/entities/CarLicense';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryCarRepository', () => {
|
||||
let repository: InMemoryCarRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryCarRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestCar = (id: string, name: string, manufacturer: string, gameId: string) => {
|
||||
return Car.create({
|
||||
id,
|
||||
name,
|
||||
manufacturer,
|
||||
gameId,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryCarRepository initialized');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if car not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Attempting to find car with ID: nonexistent.');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Car with ID: nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the car if found', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await repository.create(car);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(car);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Successfully found car with ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all cars', async () => {
|
||||
const car1 = createTestCar('1', 'Car 1', 'Manufacturer 1', 'iracing');
|
||||
const car2 = createTestCar('2', 'Car 2', 'Manufacturer 2', 'iracing');
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(car1);
|
||||
expect(result).toContain(car2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByGameId', () => {
|
||||
it('should return cars filtered by game ID', async () => {
|
||||
const car1 = createTestCar('1', 'Car 1', 'Manufacturer 1', 'iracing');
|
||||
const car2 = createTestCar('2', 'Car 2', 'Manufacturer 2', 'assetto');
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
|
||||
const result = await repository.findByGameId('iracing');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(car1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByClass', () => {
|
||||
it('should return cars filtered by class', async () => {
|
||||
const car1 = Car.create({
|
||||
id: '1',
|
||||
name: 'Car 1',
|
||||
manufacturer: 'Manufacturer 1',
|
||||
gameId: 'iracing',
|
||||
carClass: 'gt',
|
||||
});
|
||||
const car2 = Car.create({
|
||||
id: '2',
|
||||
name: 'Car 2',
|
||||
manufacturer: 'Manufacturer 2',
|
||||
gameId: 'iracing',
|
||||
carClass: 'formula',
|
||||
});
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
|
||||
const result = await repository.findByClass(CarClass.create('gt'));
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(car1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLicense', () => {
|
||||
it('should return cars filtered by license', async () => {
|
||||
const car1 = Car.create({
|
||||
id: '1',
|
||||
name: 'Car 1',
|
||||
manufacturer: 'Manufacturer 1',
|
||||
gameId: 'iracing',
|
||||
license: 'D',
|
||||
});
|
||||
const car2 = Car.create({
|
||||
id: '2',
|
||||
name: 'Car 2',
|
||||
manufacturer: 'Manufacturer 2',
|
||||
gameId: 'iracing',
|
||||
license: 'C',
|
||||
});
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
|
||||
const result = await repository.findByLicense(CarLicense.create('D'));
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(car1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByManufacturer', () => {
|
||||
it('should return cars filtered by manufacturer (case insensitive)', async () => {
|
||||
const car1 = createTestCar('1', 'Car 1', 'Ferrari', 'iracing');
|
||||
const car2 = createTestCar('2', 'Car 2', 'ferrari', 'iracing');
|
||||
const car3 = createTestCar('3', 'Car 3', 'BMW', 'iracing');
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
await repository.create(car3);
|
||||
|
||||
const result = await repository.findByManufacturer('FERRARI');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(car1);
|
||||
expect(result).toContain(car2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByName', () => {
|
||||
it('should return cars matching the query in name, shortName, or manufacturer', async () => {
|
||||
const car1 = Car.create({
|
||||
id: '1',
|
||||
name: 'Ferrari 488',
|
||||
shortName: '488',
|
||||
manufacturer: 'Ferrari',
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const car2 = createTestCar('2', 'BMW M3', 'BMW', 'iracing');
|
||||
await repository.create(car1);
|
||||
await repository.create(car2);
|
||||
|
||||
const result = await repository.searchByName('ferrari');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(car1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new car', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
const result = await repository.create(car);
|
||||
expect(result).toEqual(car);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Car 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if car already exists', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await repository.create(car);
|
||||
await expect(repository.create(car)).rejects.toThrow('Car with ID 1 already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing car', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await repository.create(car);
|
||||
|
||||
const updatedCar = Car.create({
|
||||
id: '1',
|
||||
name: 'Updated Car',
|
||||
manufacturer: 'Test Manufacturer',
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const result = await repository.update(updatedCar);
|
||||
expect(result).toEqual(updatedCar);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Car 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if car does not exist', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await expect(repository.update(car)).rejects.toThrow('Car with ID 1 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing car', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await repository.create(car);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Car 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error if car does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('Car with ID nonexistent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if car exists', async () => {
|
||||
const car = createTestCar('1', 'Test Car', 'Test Manufacturer', 'iracing');
|
||||
await repository.create(car);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if car does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,9 @@
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Car, CarClass, CarLicense } from '@core/racing/domain/entities/Car';
|
||||
import { Car } from '@core/racing/domain/entities/Car';
|
||||
import { CarClass } from '@core/racing/domain/entities/CarClass';
|
||||
import { CarLicense } from '@core/racing/domain/entities/CarLicense';
|
||||
import type { ICarRepository } from '@core/racing/domain/repositories/ICarRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
@@ -14,19 +16,11 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
private cars: Map<string, Car>;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Car[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.cars = new Map();
|
||||
|
||||
this.logger.info('InMemoryCarRepository initialized');
|
||||
|
||||
if (seedData) {
|
||||
this.logger.debug(`Seeding ${seedData.length} cars.`);
|
||||
seedData.forEach(car => {
|
||||
this.cars.set(car.id, car);
|
||||
this.logger.debug(`Car ${car.id} seeded.`);
|
||||
});
|
||||
}
|
||||
this.logger.info('InMemoryCarRepository initialized');
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Car | null> {
|
||||
@@ -61,8 +55,8 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
this.logger.debug(`Attempting to find cars by game ID: ${gameId}.`);
|
||||
try {
|
||||
const cars = Array.from(this.cars.values())
|
||||
.filter(car => car.gameId === gameId)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(car => car.gameId.toString() === gameId)
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
||||
this.logger.info(`Successfully found ${cars.length} cars for game ID: ${gameId}.`);
|
||||
return cars;
|
||||
} catch (error) {
|
||||
@@ -75,8 +69,8 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
this.logger.debug(`Attempting to find cars by class: ${carClass}.`);
|
||||
try {
|
||||
const cars = Array.from(this.cars.values())
|
||||
.filter(car => car.carClass === carClass)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(car => car.carClass.equals(carClass))
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
||||
this.logger.info(`Successfully found ${cars.length} cars for class: ${carClass}.`);
|
||||
return cars;
|
||||
} catch (error) {
|
||||
@@ -89,23 +83,23 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
this.logger.debug(`Attempting to find cars by license: ${license}.`);
|
||||
try {
|
||||
const cars = Array.from(this.cars.values())
|
||||
.filter(car => car.license === license)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(car => car.license.equals(license))
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
||||
this.logger.info(`Successfully found ${cars.length} cars for license: ${license}.`);
|
||||
return cars;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding cars by license ${license}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async findByManufacturer(manufacturer: string): Promise<Car[]> {
|
||||
this.logger.debug(`Attempting to find cars by manufacturer: ${manufacturer}.`);
|
||||
try {
|
||||
const lowerManufacturer = manufacturer.toLowerCase();
|
||||
const cars = Array.from(this.cars.values())
|
||||
.filter(car => car.manufacturer.toLowerCase() === lowerManufacturer)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(car => car.manufacturer.toString().toLowerCase() === lowerManufacturer)
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
||||
this.logger.info(`Successfully found ${cars.length} cars for manufacturer: ${manufacturer}.`);
|
||||
return cars;
|
||||
} catch (error) {
|
||||
@@ -120,11 +114,11 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const cars = Array.from(this.cars.values())
|
||||
.filter(car =>
|
||||
car.name.toLowerCase().includes(lowerQuery) ||
|
||||
car.name.toString().toLowerCase().includes(lowerQuery) ||
|
||||
car.shortName.toLowerCase().includes(lowerQuery) ||
|
||||
car.manufacturer.toLowerCase().includes(lowerQuery)
|
||||
car.manufacturer.toString().toLowerCase().includes(lowerQuery)
|
||||
)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()));
|
||||
this.logger.info(`Successfully found ${cars.length} cars matching search query: ${query}.`);
|
||||
return cars;
|
||||
} catch (error) {
|
||||
@@ -136,12 +130,12 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
async create(car: Car): Promise<Car> {
|
||||
this.logger.debug(`Attempting to create car: ${car.id}.`);
|
||||
try {
|
||||
if (await this.exists(car.id)) {
|
||||
if (await this.exists(car.id.toString())) {
|
||||
this.logger.warn(`Car with ID ${car.id} already exists; creation aborted.`);
|
||||
throw new Error(`Car with ID ${car.id} already exists`);
|
||||
}
|
||||
|
||||
this.cars.set(car.id, car);
|
||||
this.cars.set(car.id.toString(), car);
|
||||
this.logger.info(`Car ${car.id} created successfully.`);
|
||||
return car;
|
||||
} catch (error) {
|
||||
@@ -153,12 +147,12 @@ export class InMemoryCarRepository implements ICarRepository {
|
||||
async update(car: Car): Promise<Car> {
|
||||
this.logger.debug(`Attempting to update car with ID: ${car.id}.`);
|
||||
try {
|
||||
if (!await this.exists(car.id)) {
|
||||
if (!await this.exists(car.id.toString())) {
|
||||
this.logger.warn(`Car with ID ${car.id} not found for update; update aborted.`);
|
||||
throw new Error(`Car with ID ${car.id} not found`);
|
||||
}
|
||||
|
||||
this.cars.set(car.id, car);
|
||||
this.cars.set(car.id.toString(), car);
|
||||
this.logger.info(`Car ${car.id} updated successfully.`);
|
||||
return car;
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryDriverRepository } from './InMemoryDriverRepository';
|
||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryDriverRepository', () => {
|
||||
let repository: InMemoryDriverRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryDriverRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestDriver = (id: string, iracingId: string, name: string, country: string) => {
|
||||
return Driver.create({
|
||||
id,
|
||||
iracingId,
|
||||
name,
|
||||
country,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryDriverRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if driver not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryDriverRepository] Finding driver by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver with ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the driver if found', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(driver);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found driver by ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByIRacingId', () => {
|
||||
it('should return null if driver not found', async () => {
|
||||
const result = await repository.findByIRacingId('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryDriverRepository] Finding driver by iRacing ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver with iRacing ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the driver if found', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const result = await repository.findByIRacingId('12345');
|
||||
expect(result).toEqual(driver);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all drivers', async () => {
|
||||
const driver1 = createTestDriver('1', '12345', 'Driver 1', 'US');
|
||||
const driver2 = createTestDriver('2', '67890', 'Driver 2', 'UK');
|
||||
await repository.create(driver1);
|
||||
await repository.create(driver2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(driver1);
|
||||
expect(result).toContain(driver2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new driver', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
const result = await repository.create(driver);
|
||||
expect(result).toEqual(driver);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Driver 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if driver already exists', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
await expect(repository.create(driver)).rejects.toThrow('Driver already exists');
|
||||
});
|
||||
|
||||
it('should throw error if iRacing ID already registered', async () => {
|
||||
const driver1 = createTestDriver('1', '12345', 'Driver 1', 'US');
|
||||
const driver2 = createTestDriver('2', '12345', 'Driver 2', 'UK');
|
||||
await repository.create(driver1);
|
||||
await expect(repository.create(driver2)).rejects.toThrow('iRacing ID already registered');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing driver', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const updatedDriver = driver.update({ name: 'Updated Driver' });
|
||||
const result = await repository.update(updatedDriver);
|
||||
expect(result).toEqual(updatedDriver);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Driver 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if driver does not exist', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await expect(repository.update(driver)).rejects.toThrow('Driver not found');
|
||||
});
|
||||
|
||||
it('should re-index iRacing ID if changed', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const updatedDriver = Driver.create({
|
||||
id: '1',
|
||||
iracingId: '67890',
|
||||
name: 'Test Driver',
|
||||
country: 'US',
|
||||
});
|
||||
await repository.update(updatedDriver);
|
||||
|
||||
const foundByOldId = await repository.findByIRacingId('12345');
|
||||
expect(foundByOldId).toBeNull();
|
||||
|
||||
const foundByNewId = await repository.findByIRacingId('67890');
|
||||
expect(foundByNewId).toEqual(updatedDriver);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing driver', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Driver 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
const foundByIRacingId = await repository.findByIRacingId('12345');
|
||||
expect(foundByIRacingId).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if driver does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if driver exists', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if driver does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('existsByIRacingId', () => {
|
||||
it('should return true if iRacing ID exists', async () => {
|
||||
const driver = createTestDriver('1', '12345', 'Test Driver', 'US');
|
||||
await repository.create(driver);
|
||||
|
||||
const result = await repository.existsByIRacingId('12345');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if iRacing ID does not exist', async () => {
|
||||
const result = await repository.existsByIRacingId('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,13 +6,8 @@ export class InMemoryDriverRepository implements IDriverRepository {
|
||||
private drivers: Map<string, Driver> = new Map();
|
||||
private iracingIdIndex: Map<string, string> = new Map(); // iracingId -> driverId
|
||||
|
||||
constructor(private readonly logger: Logger, initialDrivers: Driver[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryDriverRepository initialized.');
|
||||
for (const driver of initialDrivers) {
|
||||
this.drivers.set(driver.id, driver);
|
||||
this.iracingIdIndex.set(driver.iracingId, driver.id);
|
||||
this.logger.debug(`Seeded driver: ${driver.id} (iRacing ID: ${driver.iracingId}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Driver | null> {
|
||||
@@ -42,33 +37,33 @@ export class InMemoryDriverRepository implements IDriverRepository {
|
||||
}
|
||||
|
||||
async create(driver: Driver): Promise<Driver> {
|
||||
this.logger.debug(`[InMemoryDriverRepository] Creating driver: ${driver.id} (iRacing ID: ${driver.iracingId})`);
|
||||
this.logger.debug(`[InMemoryDriverRepository] Creating driver: ${driver.id} (iRacing ID: ${driver.iracingId.toString()})`);
|
||||
if (this.drivers.has(driver.id)) {
|
||||
this.logger.warn(`Driver with ID ${driver.id} already exists.`);
|
||||
throw new Error('Driver already exists');
|
||||
}
|
||||
if (this.iracingIdIndex.has(driver.iracingId)) {
|
||||
this.logger.warn(`Driver with iRacing ID ${driver.iracingId} already exists.`);
|
||||
if (this.iracingIdIndex.has(driver.iracingId.toString())) {
|
||||
this.logger.warn(`Driver with iRacing ID ${driver.iracingId.toString()} already exists.`);
|
||||
throw new Error('iRacing ID already registered');
|
||||
}
|
||||
this.drivers.set(driver.id, driver);
|
||||
this.iracingIdIndex.set(driver.iracingId, driver.id);
|
||||
this.iracingIdIndex.set(driver.iracingId.toString(), driver.id);
|
||||
this.logger.info(`Driver ${driver.id} created successfully.`);
|
||||
return Promise.resolve(driver);
|
||||
}
|
||||
|
||||
async update(driver: Driver): Promise<Driver> {
|
||||
this.logger.debug(`[InMemoryDriverRepository] Updating driver: ${driver.id} (iRacing ID: ${driver.iracingId})`);
|
||||
this.logger.debug(`[InMemoryDriverRepository] Updating driver: ${driver.id} (iRacing ID: ${driver.iracingId.toString()})`);
|
||||
if (!this.drivers.has(driver.id)) {
|
||||
this.logger.warn(`Driver with ID ${driver.id} not found for update.`);
|
||||
throw new Error('Driver not found');
|
||||
}
|
||||
const existingDriver = this.drivers.get(driver.id);
|
||||
this.drivers.set(driver.id, driver);
|
||||
// Re-index iRacing ID if it changed
|
||||
const existingDriver = this.drivers.get(driver.id);
|
||||
if (existingDriver && existingDriver.iracingId !== driver.iracingId) {
|
||||
this.iracingIdIndex.delete(existingDriver.iracingId);
|
||||
this.iracingIdIndex.set(driver.iracingId, driver.id);
|
||||
if (existingDriver && existingDriver.iracingId.toString() !== driver.iracingId.toString()) {
|
||||
this.iracingIdIndex.delete(existingDriver.iracingId.toString());
|
||||
this.iracingIdIndex.set(driver.iracingId.toString(), driver.id);
|
||||
}
|
||||
this.logger.info(`Driver ${driver.id} updated successfully.`);
|
||||
return Promise.resolve(driver);
|
||||
@@ -79,7 +74,7 @@ export class InMemoryDriverRepository implements IDriverRepository {
|
||||
const driver = this.drivers.get(id);
|
||||
if (driver) {
|
||||
this.drivers.delete(id);
|
||||
this.iracingIdIndex.delete(driver.iracingId);
|
||||
this.iracingIdIndex.delete(driver.iracingId.toString());
|
||||
this.logger.info(`Driver ${id} deleted successfully.`);
|
||||
} else {
|
||||
this.logger.warn(`Driver with ID ${id} not found for deletion.`);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryGameRepository } from './InMemoryGameRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryGameRepository', () => {
|
||||
let repository: InMemoryGameRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryGameRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryGameRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if game not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryGameRepository] Finding game by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Game with ID nonexistent not found.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an empty array', async () => {
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryGameRepository] Finding all games.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemoryGameRepository implements IGameRepository {
|
||||
private games: Map<string, Game> = new Map();
|
||||
|
||||
constructor(private readonly logger: Logger, initialGames: Game[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryGameRepository initialized.');
|
||||
for (const game of initialGames) {
|
||||
this.games.set(game.id, game);
|
||||
this.logger.debug(`Seeded game: ${game.id} (${game.name}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Game | null> {
|
||||
|
||||
@@ -6,16 +6,8 @@ export class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepo
|
||||
private memberships: Map<string, LeagueMembership> = new Map(); // Key: `${leagueId}:${driverId}`
|
||||
private joinRequests: Map<string, JoinRequest> = new Map(); // Key: requestId
|
||||
|
||||
constructor(private readonly logger: Logger, initialMemberships: LeagueMembership[] = [], initialJoinRequests: JoinRequest[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryLeagueMembershipRepository initialized.');
|
||||
for (const membership of initialMemberships) {
|
||||
this.memberships.set(`${membership.leagueId}:${membership.driverId}`, membership);
|
||||
this.logger.debug(`Seeded membership: ${membership.id}.`);
|
||||
}
|
||||
for (const req of initialJoinRequests) {
|
||||
this.joinRequests.set(req.id, req);
|
||||
this.logger.debug(`Seeded join request: ${req.id}.`);
|
||||
}
|
||||
}
|
||||
|
||||
async getMembership(leagueId: string, driverId: string): Promise<LeagueMembership | null> {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryLeagueRepository } from './InMemoryLeagueRepository';
|
||||
import { League } from '@core/racing/domain/entities/League';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryLeagueRepository', () => {
|
||||
let repository: InMemoryLeagueRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryLeagueRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestLeague = (id: string, name: string, ownerId: string) => {
|
||||
return League.create({
|
||||
id,
|
||||
name,
|
||||
description: 'Test description',
|
||||
ownerId,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLeagueRepository initialized');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if league not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Attempting to find league with ID: nonexistent.');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('League with ID: nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the league if found', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await repository.create(league);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(league);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Successfully found league with ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByOwnerId', () => {
|
||||
it('should return leagues filtered by owner ID', async () => {
|
||||
const league1 = createTestLeague('1', 'League 1', 'owner1');
|
||||
const league2 = createTestLeague('2', 'League 2', 'owner2');
|
||||
const league3 = createTestLeague('3', 'League 3', 'owner1');
|
||||
await repository.create(league1);
|
||||
await repository.create(league2);
|
||||
await repository.create(league3);
|
||||
|
||||
const result = await repository.findByOwnerId('owner1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(league1);
|
||||
expect(result).toContain(league3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByName', () => {
|
||||
it('should return leagues matching the query in name (case insensitive)', async () => {
|
||||
const league1 = createTestLeague('1', 'Ferrari League', 'owner1');
|
||||
const league2 = createTestLeague('2', 'BMW League', 'owner2');
|
||||
await repository.create(league1);
|
||||
await repository.create(league2);
|
||||
|
||||
const result = await repository.searchByName('ferrari');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(league1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all leagues', async () => {
|
||||
const league1 = createTestLeague('1', 'League 1', 'owner1');
|
||||
const league2 = createTestLeague('2', 'League 2', 'owner2');
|
||||
await repository.create(league1);
|
||||
await repository.create(league2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(league1);
|
||||
expect(result).toContain(league2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new league', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
const result = await repository.create(league);
|
||||
expect(result).toEqual(league);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('League 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if league already exists', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await repository.create(league);
|
||||
await expect(repository.create(league)).rejects.toThrow('League with ID 1 already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing league', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await repository.create(league);
|
||||
|
||||
const updatedLeague = league.update({ name: 'Updated League' });
|
||||
const result = await repository.update(updatedLeague);
|
||||
expect(result).toEqual(updatedLeague);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('League 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if league does not exist', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await expect(repository.update(league)).rejects.toThrow('League with ID 1 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing league', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await repository.create(league);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('League 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error if league does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('League with ID nonexistent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if league exists', async () => {
|
||||
const league = createTestLeague('1', 'Test League', 'owner1');
|
||||
await repository.create(league);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if league does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,81 +4,128 @@ import { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryLeagueRepository implements ILeagueRepository {
|
||||
private leagues: Map<string, League> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(private readonly logger: Logger, initialLeagues: League[] = []) {
|
||||
this.logger.info('InMemoryLeagueRepository initialized.');
|
||||
for (const league of initialLeagues) {
|
||||
this.leagues.set(league.id, league);
|
||||
this.logger.debug(`Seeded league: ${league.id} (${league.name}).`);
|
||||
}
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.leagues = new Map();
|
||||
|
||||
this.logger.info('InMemoryLeagueRepository initialized');
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<League | null> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Finding league by ID: ${id}`);
|
||||
const league = this.leagues.get(id) ?? null;
|
||||
if (league) {
|
||||
this.logger.info(`Found league by ID: ${id}.`);
|
||||
} else {
|
||||
this.logger.warn(`League with ID ${id} not found.`);
|
||||
this.logger.debug(`Attempting to find league with ID: ${id}.`);
|
||||
try {
|
||||
const league = this.leagues.get(id) ?? null;
|
||||
if (league) {
|
||||
this.logger.info(`Successfully found league with ID: ${id}.`);
|
||||
} else {
|
||||
this.logger.warn(`League with ID: ${id} not found.`);
|
||||
}
|
||||
return league;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding league by ID ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
return Promise.resolve(league);
|
||||
}
|
||||
|
||||
async findByOwnerId(ownerId: string): Promise<League[]> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Finding leagues by owner ID: ${ownerId}`);
|
||||
const ownedLeagues = Array.from(this.leagues.values()).filter(league => league.ownerId === ownerId);
|
||||
this.logger.info(`Found ${ownedLeagues.length} leagues for owner ID: ${ownerId}.`);
|
||||
return Promise.resolve(ownedLeagues);
|
||||
this.logger.debug(`Attempting to find leagues by owner ID: ${ownerId}.`);
|
||||
try {
|
||||
const ownedLeagues = Array.from(this.leagues.values()).filter(league => league.ownerId.toString() === ownerId);
|
||||
this.logger.info(`Successfully found ${ownedLeagues.length} leagues for owner ID: ${ownerId}.`);
|
||||
return ownedLeagues;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding leagues by owner ID ${ownerId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async searchByName(name: string): Promise<League[]> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Searching leagues by name: ${name}`);
|
||||
const matchingLeagues = Array.from(this.leagues.values()).filter(league =>
|
||||
league.name.toLowerCase().includes(name.toLowerCase()),
|
||||
);
|
||||
this.logger.info(`Found ${matchingLeagues.length} matching leagues for name search: ${name}.`);
|
||||
return Promise.resolve(matchingLeagues);
|
||||
this.logger.debug(`Attempting to search leagues by name query: ${name}.`);
|
||||
try {
|
||||
const matchingLeagues = Array.from(this.leagues.values()).filter(league =>
|
||||
league.name.toString().toLowerCase().includes(name.toLowerCase()),
|
||||
);
|
||||
this.logger.info(`Successfully found ${matchingLeagues.length} leagues matching search query: ${name}.`);
|
||||
return matchingLeagues;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error searching leagues by name query ${name}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<League[]> {
|
||||
this.logger.debug('[InMemoryLeagueRepository] Finding all leagues.');
|
||||
return Promise.resolve(Array.from(this.leagues.values()));
|
||||
this.logger.debug('Attempting to find all leagues.');
|
||||
try {
|
||||
const leagues = Array.from(this.leagues.values());
|
||||
this.logger.info(`Successfully found ${leagues.length} leagues.`);
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
this.logger.error('Error finding all leagues:', error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async create(league: League): Promise<League> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Creating league: ${league.id} (${league.name})`);
|
||||
if (this.leagues.has(league.id)) {
|
||||
this.logger.warn(`League with ID ${league.id} already exists.`);
|
||||
throw new Error('League already exists');
|
||||
this.logger.debug(`Attempting to create league: ${league.id}.`);
|
||||
try {
|
||||
if (await this.exists(league.id.toString())) {
|
||||
this.logger.warn(`League with ID ${league.id} already exists; creation aborted.`);
|
||||
throw new Error(`League with ID ${league.id} already exists`);
|
||||
}
|
||||
|
||||
this.leagues.set(league.id.toString(), league);
|
||||
this.logger.info(`League ${league.id} created successfully.`);
|
||||
return league;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error creating league ${league.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
this.leagues.set(league.id, league);
|
||||
this.logger.info(`League ${league.id} created successfully.`);
|
||||
return Promise.resolve(league);
|
||||
}
|
||||
|
||||
async update(league: League): Promise<League> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Updating league: ${league.id} (${league.name})`);
|
||||
if (!this.leagues.has(league.id)) {
|
||||
this.logger.warn(`League with ID ${league.id} not found for update.`);
|
||||
throw new Error('League not found');
|
||||
this.logger.debug(`Attempting to update league with ID: ${league.id}.`);
|
||||
try {
|
||||
if (!(await this.exists(league.id.toString()))) {
|
||||
this.logger.warn(`League with ID ${league.id} not found for update; update aborted.`);
|
||||
throw new Error(`League with ID ${league.id} not found`);
|
||||
}
|
||||
|
||||
this.leagues.set(league.id.toString(), league);
|
||||
this.logger.info(`League ${league.id} updated successfully.`);
|
||||
return league;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error updating league ${league.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
this.leagues.set(league.id, league);
|
||||
this.logger.info(`League ${league.id} updated successfully.`);
|
||||
return Promise.resolve(league);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Deleting league with ID: ${id}`);
|
||||
if (this.leagues.delete(id)) {
|
||||
this.logger.debug(`Attempting to delete league with ID: ${id}.`);
|
||||
try {
|
||||
if (!(await this.exists(id))) {
|
||||
this.logger.warn(`League with ID ${id} not found for deletion; deletion aborted.`);
|
||||
throw new Error(`League with ID ${id} not found`);
|
||||
}
|
||||
|
||||
this.leagues.delete(id);
|
||||
this.logger.info(`League ${id} deleted successfully.`);
|
||||
} else {
|
||||
this.logger.warn(`League with ID ${id} not found for deletion.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting league ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
this.logger.debug(`[InMemoryLeagueRepository] Checking existence of league with ID: ${id}`);
|
||||
return Promise.resolve(this.leagues.has(id));
|
||||
this.logger.debug(`Checking existence of league with ID: ${id}.`);
|
||||
try {
|
||||
const exists = this.leagues.has(id);
|
||||
this.logger.info(`League with ID ${id} existence check: ${exists}.`);
|
||||
return exists;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error checking existence of league with ID ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryLeagueScoringConfigRepository } from './InMemoryLeagueScoringConfigRepository';
|
||||
import { LeagueScoringConfig, type LeagueScoringConfigProps } from '@core/racing/domain/entities/LeagueScoringConfig';
|
||||
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
||||
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
const mockPointsTable = new PointsTable({ 1: 25, 2: 18, 3: 15 });
|
||||
|
||||
const mockChampionshipConfig: ChampionshipConfig = {
|
||||
id: 'champ1',
|
||||
name: 'Championship 1',
|
||||
type: 'driver',
|
||||
sessionTypes: ['main'],
|
||||
pointsTableBySessionType: {
|
||||
practice: mockPointsTable,
|
||||
qualifying: mockPointsTable,
|
||||
q1: mockPointsTable,
|
||||
q2: mockPointsTable,
|
||||
q3: mockPointsTable,
|
||||
sprint: mockPointsTable,
|
||||
main: mockPointsTable,
|
||||
timeTrial: mockPointsTable,
|
||||
},
|
||||
dropScorePolicy: { strategy: 'none' },
|
||||
};
|
||||
|
||||
describe('InMemoryLeagueScoringConfigRepository', () => {
|
||||
let repository: InMemoryLeagueScoringConfigRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryLeagueScoringConfigRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestConfig = (seasonId: string, id?: string): LeagueScoringConfig => {
|
||||
const props: LeagueScoringConfigProps = {
|
||||
seasonId,
|
||||
championships: [mockChampionshipConfig],
|
||||
};
|
||||
if (id) {
|
||||
props.id = id;
|
||||
}
|
||||
return LeagueScoringConfig.create(props);
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLeagueScoringConfigRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonId', () => {
|
||||
it('should return null if config not found', async () => {
|
||||
const result = await repository.findBySeasonId('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryLeagueScoringConfigRepository] Finding config for season: nonexistent.');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('No config found for season: nonexistent.');
|
||||
});
|
||||
|
||||
it('should return the config if found', async () => {
|
||||
const config = createTestConfig('season1');
|
||||
await repository.save(config);
|
||||
|
||||
const result = await repository.findBySeasonId('season1');
|
||||
expect(result).toEqual(config);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found config for season: season1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should save a new config', async () => {
|
||||
const config = createTestConfig('season1');
|
||||
const result = await repository.save(config);
|
||||
expect(result).toEqual(config);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryLeagueScoringConfigRepository] Saving config for season: season1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Config for season season1 saved successfully.');
|
||||
});
|
||||
|
||||
it('should update existing config', async () => {
|
||||
const config1 = createTestConfig('season1');
|
||||
await repository.save(config1);
|
||||
|
||||
const config2 = createTestConfig('season1', 'updated-id');
|
||||
const result = await repository.save(config2);
|
||||
expect(result).toEqual(config2);
|
||||
|
||||
const found = await repository.findBySeasonId('season1');
|
||||
expect(found).toEqual(config2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemoryLeagueScoringConfigRepository implements ILeagueScoringConfigRepository {
|
||||
private configs: Map<string, LeagueScoringConfig> = new Map(); // Key: seasonId
|
||||
|
||||
constructor(private readonly logger: Logger, initialConfigs: LeagueScoringConfig[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryLeagueScoringConfigRepository initialized.');
|
||||
for (const config of initialConfigs) {
|
||||
this.configs.set(config.seasonId, config);
|
||||
this.logger.debug(`Seeded league scoring config for season: ${config.seasonId}.`);
|
||||
}
|
||||
}
|
||||
|
||||
async findBySeasonId(seasonId: string): Promise<LeagueScoringConfig | null> {
|
||||
@@ -25,9 +21,9 @@ export class InMemoryLeagueScoringConfigRepository implements ILeagueScoringConf
|
||||
}
|
||||
|
||||
async save(config: LeagueScoringConfig): Promise<LeagueScoringConfig> {
|
||||
this.logger.debug(`[InMemoryLeagueScoringConfigRepository] Saving config for season: ${config.seasonId}.`);
|
||||
this.configs.set(config.seasonId, config);
|
||||
this.logger.info(`Config for season ${config.seasonId} saved successfully.`);
|
||||
this.logger.debug(`[InMemoryLeagueScoringConfigRepository] Saving config for season: ${config.seasonId.toString()}.`);
|
||||
this.configs.set(config.seasonId.toString(), config);
|
||||
this.logger.info(`Config for season ${config.seasonId.toString()} saved successfully.`);
|
||||
return Promise.resolve(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { InMemoryLeagueScoringPresetProvider } from './InMemoryLeagueScoringPresetProvider';
|
||||
|
||||
describe('InMemoryLeagueScoringPresetProvider', () => {
|
||||
const provider = new InMemoryLeagueScoringPresetProvider();
|
||||
|
||||
describe('listPresets', () => {
|
||||
it('should return all available presets', () => {
|
||||
const presets = provider.listPresets();
|
||||
|
||||
expect(presets).toHaveLength(3);
|
||||
expect(presets).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'sprint-main-driver',
|
||||
name: 'Sprint + Main',
|
||||
primaryChampionshipType: 'driver',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'club-default',
|
||||
name: 'Club ladder',
|
||||
primaryChampionshipType: 'driver',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'endurance-main-double',
|
||||
name: 'Endurance weekend',
|
||||
primaryChampionshipType: 'driver',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPresetById', () => {
|
||||
it('should return the preset for a valid id', () => {
|
||||
const preset = provider.getPresetById('sprint-main-driver');
|
||||
|
||||
expect(preset).toEqual({
|
||||
id: 'sprint-main-driver',
|
||||
name: 'Sprint + Main',
|
||||
description: 'Short sprint race plus main race; sprint gives fewer points.',
|
||||
primaryChampionshipType: 'driver',
|
||||
sessionSummary: 'Sprint + Main',
|
||||
bonusSummary: 'Fastest lap +1 point in main race if finishing P10 or better.',
|
||||
dropPolicySummary: 'Best 6 results of 8 count towards the championship.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined for an invalid id', () => {
|
||||
const preset = provider.getPresetById('invalid-id');
|
||||
|
||||
expect(preset).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createScoringConfigFromPreset', () => {
|
||||
it('should create a scoring config from a valid preset', () => {
|
||||
const config = provider.createScoringConfigFromPreset('sprint-main-driver', 'season-123');
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.championships).toHaveLength(1);
|
||||
expect(config.championships[0]?.name).toBe('Driver Championship');
|
||||
});
|
||||
|
||||
it('should throw an error for an invalid preset id', () => {
|
||||
expect(() => {
|
||||
provider.createScoringConfigFromPreset('invalid-id', 'season-123');
|
||||
}).toThrow('Scoring preset with id invalid-id not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
getLeagueScoringPresetById,
|
||||
listLeagueScoringPresets,
|
||||
} from './InMemoryScoringRepositories';
|
||||
} from '../../../bootstrap/LeagueScoringPresets';
|
||||
import type {
|
||||
LeagueScoringPresetDTO,
|
||||
LeagueScoringPresetProvider,
|
||||
@@ -42,4 +42,12 @@ export class InMemoryLeagueScoringPresetProvider
|
||||
dropPolicySummary: preset.dropPolicySummary,
|
||||
};
|
||||
}
|
||||
|
||||
createScoringConfigFromPreset(presetId: string, seasonId: string) {
|
||||
const preset = getLeagueScoringPresetById(presetId);
|
||||
if (!preset) {
|
||||
throw new Error(`Scoring preset with id ${presetId} not found`);
|
||||
}
|
||||
return preset.createConfig({ seasonId });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryLeagueStandingsRepository } from './InMemoryLeagueStandingsRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { RawStanding } from '@core/league/application/ports/ILeagueStandingsRepository';
|
||||
|
||||
describe('InMemoryLeagueStandingsRepository', () => {
|
||||
let repository: InMemoryLeagueStandingsRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryLeagueStandingsRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestStanding = (id: string, leagueId: string, driverId: string, position: number, points: number): RawStanding => ({
|
||||
id,
|
||||
leagueId,
|
||||
driverId,
|
||||
position,
|
||||
points,
|
||||
wins: 0,
|
||||
racesCompleted: 0,
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLeagueStandingsRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLeagueStandings', () => {
|
||||
it('should return empty array if league not found', async () => {
|
||||
const result = await repository.getLeagueStandings('nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryLeagueStandingsRepository] Getting standings for league: nonexistent.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 standings for league: nonexistent.');
|
||||
});
|
||||
|
||||
it('should return the standings if league exists', async () => {
|
||||
const standings: RawStanding[] = [
|
||||
createTestStanding('1', 'league1', 'driver1', 1, 25),
|
||||
createTestStanding('2', 'league1', 'driver2', 2, 20),
|
||||
];
|
||||
(repository as InMemoryLeagueStandingsRepository).setLeagueStandings('league1', standings);
|
||||
|
||||
const result = await repository.getLeagueStandings('league1');
|
||||
expect(result).toEqual(standings);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryLeagueStandingsRepository] Getting standings for league: league1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 standings for league: league1.');
|
||||
});
|
||||
|
||||
it('should return empty array if league exists but has no standings', async () => {
|
||||
(repository as InMemoryLeagueStandingsRepository).setLeagueStandings('league1', []);
|
||||
|
||||
const result = await repository.getLeagueStandings('league1');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 standings for league: league1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLeagueStandings', () => {
|
||||
it('should set standings for a league', async () => {
|
||||
const standings: RawStanding[] = [
|
||||
createTestStanding('1', 'league1', 'driver1', 1, 25),
|
||||
];
|
||||
|
||||
(repository as InMemoryLeagueStandingsRepository).setLeagueStandings('league1', standings);
|
||||
|
||||
const result = await repository.getLeagueStandings('league1');
|
||||
expect(result).toEqual(standings);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryLeagueStandingsRepository] Set standings for league: league1.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,15 +4,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemoryLeagueStandingsRepository implements ILeagueStandingsRepository {
|
||||
private standings: Map<string, RawStanding[]> = new Map(); // Key: leagueId
|
||||
|
||||
constructor(private readonly logger: Logger, initialStandings: Record<string, RawStanding[]> = {}) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryLeagueStandingsRepository initialized.');
|
||||
for (const leagueId in initialStandings) {
|
||||
// Ensure initialStandings[leagueId] is not undefined before setting
|
||||
if (initialStandings[leagueId] !== undefined) {
|
||||
this.standings.set(leagueId, initialStandings[leagueId]);
|
||||
this.logger.debug(`Seeded standings for league: ${leagueId}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getLeagueStandings(leagueId: string): Promise<RawStanding[]> {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryLeagueWalletRepository } from './InMemoryLeagueWalletRepository';
|
||||
import { LeagueWallet } from '@core/racing/domain/entities/league-wallet/LeagueWallet';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryLeagueWalletRepository', () => {
|
||||
let repository: InMemoryLeagueWalletRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryLeagueWalletRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestWallet = (id: string, leagueId: string, balanceAmount: number = 10000) => {
|
||||
const balance = Money.create(balanceAmount, 'USD');
|
||||
return LeagueWallet.create({
|
||||
id,
|
||||
leagueId,
|
||||
balance,
|
||||
transactionIds: [],
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLeagueWalletRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if wallet not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding league wallet by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('League wallet with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the wallet if found', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(wallet);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found league wallet: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return null if no wallet found for league id', async () => {
|
||||
const result = await repository.findByLeagueId('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('No league wallet found for league id: nonexistent.');
|
||||
});
|
||||
|
||||
it('should return the wallet if found by league id', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toEqual(wallet);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found league wallet for league id: league1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new wallet', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
const result = await repository.create(wallet);
|
||||
expect(result).toEqual(wallet);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LeagueWallet 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if wallet already exists', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
await expect(repository.create(wallet)).rejects.toThrow('LeagueWallet with this ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing wallet', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
|
||||
const updatedWallet = LeagueWallet.create({
|
||||
id: '1',
|
||||
leagueId: 'league1',
|
||||
balance: Money.create(20000, 'USD'),
|
||||
transactionIds: [],
|
||||
});
|
||||
const result = await repository.update(updatedWallet);
|
||||
expect(result).toEqual(updatedWallet);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LeagueWallet 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if wallet does not exist', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await expect(repository.update(wallet)).rejects.toThrow('LeagueWallet not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing wallet', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LeagueWallet 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw if wallet does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('LeagueWallet with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if wallet exists', async () => {
|
||||
const wallet = createTestWallet('1', 'league1');
|
||||
await repository.create(wallet);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if wallet does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,20 @@
|
||||
/**
|
||||
* In-Memory Implementation: ILeagueWalletRepository
|
||||
*
|
||||
*
|
||||
* Mock repository for testing and development
|
||||
*/
|
||||
|
||||
import type { LeagueWallet } from '../../domain/entities/LeagueWallet';
|
||||
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
|
||||
import { LeagueWallet } from '@core/racing/domain/entities/league-wallet/LeagueWallet';
|
||||
import type { ILeagueWalletRepository } from '@core/racing/domain/repositories/ILeagueWalletRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryLeagueWalletRepository implements ILeagueWalletRepository {
|
||||
private wallets: Map<string, LeagueWallet> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: LeagueWallet[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryLeagueWalletRepository initialized.');
|
||||
if (seedData) {
|
||||
seedData.forEach(wallet => this.wallets.set(wallet.id, wallet));
|
||||
this.logger.debug(`Seeded ${seedData.length} league wallets.`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<LeagueWallet | null> {
|
||||
@@ -41,7 +37,7 @@ export class InMemoryLeagueWalletRepository implements ILeagueWalletRepository {
|
||||
this.logger.debug(`Finding league wallet by league id: ${leagueId}`);
|
||||
try {
|
||||
for (const wallet of this.wallets.values()) {
|
||||
if (wallet.leagueId === leagueId) {
|
||||
if (wallet.leagueId.toString() === leagueId) {
|
||||
this.logger.info(`Found league wallet for league id: ${leagueId}.`);
|
||||
return wallet;
|
||||
}
|
||||
@@ -57,11 +53,11 @@ export class InMemoryLeagueWalletRepository implements ILeagueWalletRepository {
|
||||
async create(wallet: LeagueWallet): Promise<LeagueWallet> {
|
||||
this.logger.debug(`Creating league wallet: ${wallet.id}`);
|
||||
try {
|
||||
if (this.wallets.has(wallet.id)) {
|
||||
if (this.wallets.has(wallet.id.toString())) {
|
||||
this.logger.warn(`LeagueWallet with ID ${wallet.id} already exists.`);
|
||||
throw new Error('LeagueWallet with this ID already exists');
|
||||
}
|
||||
this.wallets.set(wallet.id, wallet);
|
||||
this.wallets.set(wallet.id.toString(), wallet);
|
||||
this.logger.info(`LeagueWallet ${wallet.id} created successfully.`);
|
||||
return wallet;
|
||||
} catch (error) {
|
||||
@@ -73,11 +69,11 @@ export class InMemoryLeagueWalletRepository implements ILeagueWalletRepository {
|
||||
async update(wallet: LeagueWallet): Promise<LeagueWallet> {
|
||||
this.logger.debug(`Updating league wallet: ${wallet.id}`);
|
||||
try {
|
||||
if (!this.wallets.has(wallet.id)) {
|
||||
if (!this.wallets.has(wallet.id.toString())) {
|
||||
this.logger.warn(`LeagueWallet with ID ${wallet.id} not found for update.`);
|
||||
throw new Error('LeagueWallet not found');
|
||||
}
|
||||
this.wallets.set(wallet.id, wallet);
|
||||
this.wallets.set(wallet.id.toString(), wallet);
|
||||
this.logger.info(`LeagueWallet ${wallet.id} updated successfully.`);
|
||||
return wallet;
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryLiveryRepository } from './InMemoryLiveryRepository';
|
||||
import { DriverLivery } from '@core/racing/domain/entities/DriverLivery';
|
||||
import { LiveryTemplate } from '@core/racing/domain/entities/LiveryTemplate';
|
||||
import { LiveryDecal } from '@core/racing/domain/value-objects/LiveryDecal';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryLiveryRepository', () => {
|
||||
let repository: InMemoryLiveryRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryLiveryRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestDriverLivery = (id: string, driverId: string, gameId: string, carId: string, uploadedImageUrl: string) => {
|
||||
return DriverLivery.create({
|
||||
id,
|
||||
driverId,
|
||||
gameId,
|
||||
carId,
|
||||
uploadedImageUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const createTestLiveryTemplate = (id: string, leagueId: string, seasonId: string, carId: string, baseImageUrl: string) => {
|
||||
return LiveryTemplate.create({
|
||||
id,
|
||||
leagueId,
|
||||
seasonId,
|
||||
carId,
|
||||
baseImageUrl,
|
||||
});
|
||||
};
|
||||
|
||||
const createTestDecal = (id: string, type: 'user' | 'sponsor', imageUrl: string) => {
|
||||
return LiveryDecal.create({
|
||||
id,
|
||||
type,
|
||||
imageUrl,
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
width: 0.1,
|
||||
height: 0.1,
|
||||
zIndex: 1,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLiveryRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DriverLivery operations', () => {
|
||||
describe('findDriverLiveryById', () => {
|
||||
it('should return null if driver livery not found', async () => {
|
||||
const result = await repository.findDriverLiveryById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding driver livery by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver livery with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the driver livery if found', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await repository.createDriverLivery(livery);
|
||||
|
||||
const result = await repository.findDriverLiveryById('1');
|
||||
expect(result).toEqual(livery);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found driver livery: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDriverLiveriesByDriverId', () => {
|
||||
it('should return driver liveries for the given driver', async () => {
|
||||
const livery1 = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image1.jpg');
|
||||
const livery2 = createTestDriverLivery('2', 'driver1', 'game2', 'car2', 'http://example.com/image2.jpg');
|
||||
const livery3 = createTestDriverLivery('3', 'driver2', 'game1', 'car1', 'http://example.com/image3.jpg');
|
||||
await repository.createDriverLivery(livery1);
|
||||
await repository.createDriverLivery(livery2);
|
||||
await repository.createDriverLivery(livery3);
|
||||
|
||||
const result = await repository.findDriverLiveriesByDriverId('driver1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(livery1);
|
||||
expect(result).toContain(livery2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDriverLiveryByDriverAndCar', () => {
|
||||
it('should return null if no livery found', async () => {
|
||||
const result = await repository.findDriverLiveryByDriverAndCar('driver1', 'car1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the livery if found', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await repository.createDriverLivery(livery);
|
||||
|
||||
const result = await repository.findDriverLiveryByDriverAndCar('driver1', 'car1');
|
||||
expect(result).toEqual(livery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDriverLiveriesByGameId', () => {
|
||||
it('should return driver liveries for the given game', async () => {
|
||||
const livery1 = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image1.jpg');
|
||||
const livery2 = createTestDriverLivery('2', 'driver2', 'game1', 'car2', 'http://example.com/image2.jpg');
|
||||
const livery3 = createTestDriverLivery('3', 'driver1', 'game2', 'car1', 'http://example.com/image3.jpg');
|
||||
await repository.createDriverLivery(livery1);
|
||||
await repository.createDriverLivery(livery2);
|
||||
await repository.createDriverLivery(livery3);
|
||||
|
||||
const result = await repository.findDriverLiveriesByGameId('game1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(livery1);
|
||||
expect(result).toContain(livery2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDriverLiveryByDriverAndGame', () => {
|
||||
it('should return driver liveries for the given driver and game', async () => {
|
||||
const livery1 = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image1.jpg');
|
||||
const livery2 = createTestDriverLivery('2', 'driver1', 'game1', 'car2', 'http://example.com/image2.jpg');
|
||||
const livery3 = createTestDriverLivery('3', 'driver1', 'game2', 'car1', 'http://example.com/image3.jpg');
|
||||
await repository.createDriverLivery(livery1);
|
||||
await repository.createDriverLivery(livery2);
|
||||
await repository.createDriverLivery(livery3);
|
||||
|
||||
const result = await repository.findDriverLiveryByDriverAndGame('driver1', 'game1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(livery1);
|
||||
expect(result).toContain(livery2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDriverLivery', () => {
|
||||
it('should create a new driver livery', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
const result = await repository.createDriverLivery(livery);
|
||||
expect(result).toEqual(livery);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('DriverLivery 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if driver livery already exists', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await repository.createDriverLivery(livery);
|
||||
await expect(repository.createDriverLivery(livery)).rejects.toThrow('DriverLivery with this ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateDriverLivery', () => {
|
||||
it('should update an existing driver livery', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await repository.createDriverLivery(livery);
|
||||
|
||||
const decal = createTestDecal('decal1', 'user', 'http://example.com/decal.jpg');
|
||||
const updatedLivery = livery.addDecal(decal);
|
||||
const result = await repository.updateDriverLivery(updatedLivery);
|
||||
expect(result).toEqual(updatedLivery);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('DriverLivery 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if driver livery does not exist', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await expect(repository.updateDriverLivery(livery)).rejects.toThrow('DriverLivery not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteDriverLivery', () => {
|
||||
it('should delete an existing driver livery', async () => {
|
||||
const livery = createTestDriverLivery('1', 'driver1', 'game1', 'car1', 'http://example.com/image.jpg');
|
||||
await repository.createDriverLivery(livery);
|
||||
|
||||
await repository.deleteDriverLivery('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('DriverLivery 1 deleted successfully.');
|
||||
const found = await repository.findDriverLiveryById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if driver livery does not exist', async () => {
|
||||
await repository.deleteDriverLivery('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('DriverLivery with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('LiveryTemplate operations', () => {
|
||||
describe('findTemplateById', () => {
|
||||
it('should return null if template not found', async () => {
|
||||
const result = await repository.findTemplateById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Livery template with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the template if found', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await repository.createTemplate(template);
|
||||
|
||||
const result = await repository.findTemplateById('1');
|
||||
expect(result).toEqual(template);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found livery template: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findTemplatesBySeasonId', () => {
|
||||
it('should return templates for the given season', async () => {
|
||||
const template1 = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base1.jpg');
|
||||
const template2 = createTestLiveryTemplate('2', 'league2', 'season1', 'car2', 'http://example.com/base2.jpg');
|
||||
const template3 = createTestLiveryTemplate('3', 'league1', 'season2', 'car1', 'http://example.com/base3.jpg');
|
||||
await repository.createTemplate(template1);
|
||||
await repository.createTemplate(template2);
|
||||
await repository.createTemplate(template3);
|
||||
|
||||
const result = await repository.findTemplatesBySeasonId('season1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(template1);
|
||||
expect(result).toContain(template2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findTemplateBySeasonAndCar', () => {
|
||||
it('should return null if no template found', async () => {
|
||||
const result = await repository.findTemplateBySeasonAndCar('season1', 'car1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the template if found', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await repository.createTemplate(template);
|
||||
|
||||
const result = await repository.findTemplateBySeasonAndCar('season1', 'car1');
|
||||
expect(result).toEqual(template);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTemplate', () => {
|
||||
it('should create a new template', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
const result = await repository.createTemplate(template);
|
||||
expect(result).toEqual(template);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LiveryTemplate 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if template already exists', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await repository.createTemplate(template);
|
||||
await expect(repository.createTemplate(template)).rejects.toThrow('LiveryTemplate with this ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTemplate', () => {
|
||||
it('should update an existing template', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await repository.createTemplate(template);
|
||||
|
||||
const decal = createTestDecal('decal1', 'sponsor', 'http://example.com/decal.jpg');
|
||||
const updatedTemplate = template.addDecal(decal);
|
||||
const result = await repository.updateTemplate(updatedTemplate);
|
||||
expect(result).toEqual(updatedTemplate);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LiveryTemplate 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if template does not exist', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await expect(repository.updateTemplate(template)).rejects.toThrow('LiveryTemplate not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteTemplate', () => {
|
||||
it('should delete an existing template', async () => {
|
||||
const template = createTestLiveryTemplate('1', 'league1', 'season1', 'car1', 'http://example.com/base.jpg');
|
||||
await repository.createTemplate(template);
|
||||
|
||||
await repository.deleteTemplate('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('LiveryTemplate 1 deleted successfully.');
|
||||
const found = await repository.findTemplateById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if template does not exist', async () => {
|
||||
await repository.deleteTemplate('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('LiveryTemplate with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,9 +4,9 @@
|
||||
* Mock repository for testing and development
|
||||
*/
|
||||
|
||||
import type { DriverLivery } from '../../domain/entities/DriverLivery';
|
||||
import type { LiveryTemplate } from '../../domain/entities/LiveryTemplate';
|
||||
import type { ILiveryRepository } from '../../domain/repositories/ILiveryRepository';
|
||||
import type { DriverLivery } from '../../../../core/racing/domain/entities/DriverLivery';
|
||||
import type { LiveryTemplate } from '../../../../core/racing/domain/entities/LiveryTemplate';
|
||||
import type { ILiveryRepository } from '../../../../core/racing/domain/repositories/ILiveryRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
@@ -22,7 +22,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.debug(`Seeded ${seedDriverLiveries.length} driver liveries.`);
|
||||
}
|
||||
if (seedTemplates) {
|
||||
seedTemplates.forEach(template => this.templates.set(template.id, template));
|
||||
seedTemplates.forEach(template => this.templates.set(template.id.toString(), template));
|
||||
this.logger.debug(`Seeded ${seedTemplates.length} livery templates.`);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
async findDriverLiveriesByDriverId(driverId: string): Promise<DriverLivery[]> {
|
||||
this.logger.debug(`Finding driver liveries by driver id: ${driverId}`);
|
||||
try {
|
||||
const liveries = Array.from(this.driverLiveries.values()).filter(l => l.driverId === driverId);
|
||||
const liveries = Array.from(this.driverLiveries.values()).filter(l => l.driverId.toString() === driverId);
|
||||
this.logger.info(`Found ${liveries.length} driver liveries for driver id: ${driverId}.`);
|
||||
return liveries;
|
||||
} catch (error) {
|
||||
@@ -60,7 +60,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.debug(`Finding driver livery by driver: ${driverId} and car: ${carId}`);
|
||||
try {
|
||||
for (const livery of this.driverLiveries.values()) {
|
||||
if (livery.driverId === driverId && livery.carId === carId) {
|
||||
if (livery.driverId.toString() === driverId && livery.carId.toString() === carId) {
|
||||
this.logger.info(`Found driver livery for driver: ${driverId}, car: ${carId}.`);
|
||||
return livery;
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.warn(`Driver livery for driver ${driverId} and car ${carId} not found.`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding driver livery by driver ${driverId}, car ${carId}:`, error);
|
||||
this.logger.error(`Error finding driver livery by driver ${driverId}, car ${carId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
async findDriverLiveriesByGameId(gameId: string): Promise<DriverLivery[]> {
|
||||
this.logger.debug(`Finding driver liveries by game id: ${gameId}`);
|
||||
try {
|
||||
const liveries = Array.from(this.driverLiveries.values()).filter(l => l.gameId === gameId);
|
||||
const liveries = Array.from(this.driverLiveries.values()).filter(l => l.gameId.toString() === gameId);
|
||||
this.logger.info(`Found ${liveries.length} driver liveries for game id: ${gameId}.`);
|
||||
return liveries;
|
||||
} catch (error) {
|
||||
@@ -89,12 +89,12 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.debug(`Finding driver liveries by driver: ${driverId} and game: ${gameId}`);
|
||||
try {
|
||||
const liveries = Array.from(this.driverLiveries.values()).filter(
|
||||
l => l.driverId === driverId && l.gameId === gameId
|
||||
l => l.driverId.toString() === driverId && l.gameId.toString() === gameId
|
||||
);
|
||||
this.logger.info(`Found ${liveries.length} driver liveries for driver: ${driverId}, game: ${gameId}.`);
|
||||
return liveries;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding driver liveries by driver ${driverId}, game ${gameId}:`, error);
|
||||
this.logger.error(`Error finding driver liveries by driver ${driverId}, game ${gameId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
async findTemplatesBySeasonId(seasonId: string): Promise<LiveryTemplate[]> {
|
||||
this.logger.debug(`Finding livery templates by season id: ${seasonId}`);
|
||||
try {
|
||||
const templates = Array.from(this.templates.values()).filter(t => t.seasonId === seasonId);
|
||||
const templates = Array.from(this.templates.values()).filter(t => t.seasonId.toString() === seasonId);
|
||||
this.logger.info(`Found ${templates.length} livery templates for season id: ${seasonId}.`);
|
||||
return templates;
|
||||
} catch (error) {
|
||||
@@ -178,7 +178,7 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.debug(`Finding livery template by season: ${seasonId} and car: ${carId}`);
|
||||
try {
|
||||
for (const template of this.templates.values()) {
|
||||
if (template.seasonId === seasonId && template.carId === carId) {
|
||||
if (template.seasonId.toString() === seasonId && template.carId.toString() === carId) {
|
||||
this.logger.info(`Found livery template for season: ${seasonId}, car: ${carId}.`);
|
||||
return template;
|
||||
}
|
||||
@@ -186,39 +186,39 @@ export class InMemoryLiveryRepository implements ILiveryRepository {
|
||||
this.logger.warn(`Livery template for season ${seasonId} and car ${carId} not found.`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding livery template by season ${seasonId}, car ${carId}:`, error);
|
||||
this.logger.error(`Error finding livery template by season ${seasonId}, car ${carId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createTemplate(template: LiveryTemplate): Promise<LiveryTemplate> {
|
||||
this.logger.debug(`Creating livery template: ${template.id}`);
|
||||
this.logger.debug(`Creating livery template: ${template.id.toString()}`);
|
||||
try {
|
||||
if (this.templates.has(template.id)) {
|
||||
this.logger.warn(`LiveryTemplate with ID ${template.id} already exists.`);
|
||||
if (this.templates.has(template.id.toString())) {
|
||||
this.logger.warn(`LiveryTemplate with ID ${template.id.toString()} already exists.`);
|
||||
throw new Error('LiveryTemplate with this ID already exists');
|
||||
}
|
||||
this.templates.set(template.id, template);
|
||||
this.logger.info(`LiveryTemplate ${template.id} created successfully.`);
|
||||
this.templates.set(template.id.toString(), template);
|
||||
this.logger.info(`LiveryTemplate ${template.id.toString()} created successfully.`);
|
||||
return template;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error creating livery template ${template.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`Error creating livery template ${template.id.toString()}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateTemplate(template: LiveryTemplate): Promise<LiveryTemplate> {
|
||||
this.logger.debug(`Updating livery template: ${template.id}`);
|
||||
this.logger.debug(`Updating livery template: ${template.id.toString()}`);
|
||||
try {
|
||||
if (!this.templates.has(template.id)) {
|
||||
this.logger.warn(`LiveryTemplate with ID ${template.id} not found for update.`);
|
||||
if (!this.templates.has(template.id.toString())) {
|
||||
this.logger.warn(`LiveryTemplate with ID ${template.id.toString()} not found for update.`);
|
||||
throw new Error('LiveryTemplate not found');
|
||||
}
|
||||
this.templates.set(template.id, template);
|
||||
this.logger.info(`LiveryTemplate ${template.id} updated successfully.`);
|
||||
this.templates.set(template.id.toString(), template);
|
||||
this.logger.info(`LiveryTemplate ${template.id.toString()} updated successfully.`);
|
||||
return template;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error updating livery template ${template.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`Error updating livery template ${template.id.toString()}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryPenaltyRepository } from './InMemoryPenaltyRepository';
|
||||
import { Penalty } from '@core/racing/domain/entities/penalty/Penalty';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryPenaltyRepository', () => {
|
||||
let repository: InMemoryPenaltyRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryPenaltyRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestPenalty = (id: string, raceId: string, driverId: string, issuedBy: string) => {
|
||||
return Penalty.create({
|
||||
id,
|
||||
leagueId: 'league1',
|
||||
raceId,
|
||||
driverId,
|
||||
type: 'time_penalty',
|
||||
value: 5,
|
||||
reason: 'Test reason',
|
||||
issuedBy,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryPenaltyRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if penalty not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding penalty by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Penalty with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the penalty if found', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await repository.create(penalty);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(penalty);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found penalty with id: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByRaceId', () => {
|
||||
it('should return penalties filtered by race ID', async () => {
|
||||
const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
const penalty2 = createTestPenalty('2', 'race2', 'driver2', 'steward1');
|
||||
await repository.create(penalty1);
|
||||
await repository.create(penalty2);
|
||||
|
||||
const result = await repository.findByRaceId('race1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(penalty1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByDriverId', () => {
|
||||
it('should return penalties filtered by driver ID', async () => {
|
||||
const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward1');
|
||||
await repository.create(penalty1);
|
||||
await repository.create(penalty2);
|
||||
|
||||
const result = await repository.findByDriverId('driver1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(penalty1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByProtestId', () => {
|
||||
it('should return penalties filtered by protest ID', async () => {
|
||||
const penalty1 = Penalty.create({
|
||||
id: '1',
|
||||
leagueId: 'league1',
|
||||
raceId: 'race1',
|
||||
driverId: 'driver1',
|
||||
type: 'time_penalty',
|
||||
value: 5,
|
||||
reason: 'Test reason',
|
||||
protestId: 'protest1',
|
||||
issuedBy: 'steward1',
|
||||
});
|
||||
const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward1');
|
||||
await repository.create(penalty1);
|
||||
await repository.create(penalty2);
|
||||
|
||||
const result = await repository.findByProtestId('protest1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(penalty1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPending', () => {
|
||||
it('should return only pending penalties', async () => {
|
||||
const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
const penalty2 = Penalty.create({
|
||||
id: '2',
|
||||
leagueId: 'league1',
|
||||
raceId: 'race1',
|
||||
driverId: 'driver2',
|
||||
type: 'time_penalty',
|
||||
value: 5,
|
||||
reason: 'Test reason',
|
||||
issuedBy: 'steward1',
|
||||
status: 'applied',
|
||||
});
|
||||
await repository.create(penalty1);
|
||||
await repository.create(penalty2);
|
||||
|
||||
const result = await repository.findPending();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(penalty1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findIssuedBy', () => {
|
||||
it('should return penalties issued by a specific steward', async () => {
|
||||
const penalty1 = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
const penalty2 = createTestPenalty('2', 'race1', 'driver2', 'steward2');
|
||||
await repository.create(penalty1);
|
||||
await repository.create(penalty2);
|
||||
|
||||
const result = await repository.findIssuedBy('steward1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(penalty1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new penalty', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await repository.create(penalty);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Penalty 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if penalty already exists', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await repository.create(penalty);
|
||||
await expect(repository.create(penalty)).rejects.toThrow('Penalty with ID 1 already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing penalty', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await repository.create(penalty);
|
||||
|
||||
const updatedPenalty = penalty.markAsApplied();
|
||||
await repository.update(updatedPenalty);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Penalty 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if penalty does not exist', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await expect(repository.update(penalty)).rejects.toThrow('Penalty with ID 1 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if penalty exists', async () => {
|
||||
const penalty = createTestPenalty('1', 'race1', 'driver1', 'steward1');
|
||||
await repository.create(penalty);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if penalty does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* In-Memory Implementation: InMemoryPenaltyRepository
|
||||
*
|
||||
*
|
||||
* Provides an in-memory storage implementation for penalties.
|
||||
*/
|
||||
|
||||
import type { Penalty } from '../../domain/entities/Penalty';
|
||||
import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository';
|
||||
import type { Penalty } from '@core/racing/domain/entities/Penalty';
|
||||
import type { IPenaltyRepository } from '@core/racing/domain/repositories/IPenaltyRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryPenaltyRepository implements IPenaltyRepository {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryProtestRepository } from './InMemoryProtestRepository';
|
||||
import { Protest } from '@core/racing/domain/entities/Protest';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryProtestRepository', () => {
|
||||
let repository: InMemoryProtestRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryProtestRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestProtest = (id: string, raceId: string, protestingDriverId: string, accusedDriverId: string, status?: string) => {
|
||||
const baseProps = {
|
||||
id,
|
||||
raceId,
|
||||
protestingDriverId,
|
||||
accusedDriverId,
|
||||
incident: { lap: 1, description: 'Test incident' },
|
||||
};
|
||||
if (status) {
|
||||
return Protest.create({ ...baseProps, status });
|
||||
}
|
||||
return Protest.create(baseProps);
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryProtestRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if protest not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryProtestRepository] Finding protest by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Protest with ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the protest if found', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await repository.create(protest);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(protest);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found protest by ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByRaceId', () => {
|
||||
it('should return protests filtered by race ID', async () => {
|
||||
const protest1 = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
const protest2 = createTestProtest('2', 'race2', 'driver3', 'driver4');
|
||||
await repository.create(protest1);
|
||||
await repository.create(protest2);
|
||||
|
||||
const result = await repository.findByRaceId('race1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(protest1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByProtestingDriverId', () => {
|
||||
it('should return protests filtered by protesting driver ID', async () => {
|
||||
const protest1 = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
const protest2 = createTestProtest('2', 'race1', 'driver3', 'driver1');
|
||||
await repository.create(protest1);
|
||||
await repository.create(protest2);
|
||||
|
||||
const result = await repository.findByProtestingDriverId('driver1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(protest1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByAccusedDriverId', () => {
|
||||
it('should return protests filtered by accused driver ID', async () => {
|
||||
const protest1 = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
const protest2 = createTestProtest('2', 'race1', 'driver2', 'driver3');
|
||||
await repository.create(protest1);
|
||||
await repository.create(protest2);
|
||||
|
||||
const result = await repository.findByAccusedDriverId('driver2');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(protest1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPending', () => {
|
||||
it('should return only pending protests', async () => {
|
||||
const protest1 = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
const protest2 = createTestProtest('2', 'race1', 'driver3', 'driver4', 'dismissed');
|
||||
await repository.create(protest1);
|
||||
await repository.create(protest2);
|
||||
|
||||
const result = await repository.findPending();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(protest1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUnderReviewBy', () => {
|
||||
it('should return protests under review by a specific steward', async () => {
|
||||
const protest1 = Protest.create({
|
||||
id: '1',
|
||||
raceId: 'race1',
|
||||
protestingDriverId: 'driver1',
|
||||
accusedDriverId: 'driver2',
|
||||
incident: { lap: 1, description: 'Test incident' },
|
||||
status: 'under_review',
|
||||
reviewedBy: 'steward1',
|
||||
});
|
||||
const protest2 = createTestProtest('2', 'race1', 'driver3', 'driver4');
|
||||
await repository.create(protest1);
|
||||
await repository.create(protest2);
|
||||
|
||||
const result = await repository.findUnderReviewBy('steward1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(protest1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new protest', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await repository.create(protest);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Protest 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if protest already exists', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await repository.create(protest);
|
||||
await expect(repository.create(protest)).rejects.toThrow('Protest already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing protest', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await repository.create(protest);
|
||||
|
||||
const updatedProtest = protest.startReview('steward1');
|
||||
await repository.update(updatedProtest);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Protest 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if protest does not exist', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await expect(repository.update(protest)).rejects.toThrow('Protest not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if protest exists', async () => {
|
||||
const protest = createTestProtest('1', 'race1', 'driver1', 'driver2');
|
||||
await repository.create(protest);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if protest does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,12 @@
|
||||
import { IProtestRepository } from '@core/racing/domain/repositories/IProtestRepository';
|
||||
import { Protest, ProtestStatus } from '@core/racing/domain/entities/Protest';
|
||||
import { Protest } from '@core/racing/domain/entities/Protest';
|
||||
import { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryProtestRepository implements IProtestRepository {
|
||||
private protests: Map<string, Protest> = new Map();
|
||||
|
||||
constructor(private readonly logger: Logger, initialProtests: Protest[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryProtestRepository initialized.');
|
||||
for (const protest of initialProtests) {
|
||||
this.protests.set(protest.id, protest);
|
||||
this.logger.debug(`Seeded protest: ${protest.id}.`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Protest | null> {
|
||||
@@ -47,14 +43,14 @@ export class InMemoryProtestRepository implements IProtestRepository {
|
||||
|
||||
async findPending(): Promise<Protest[]> {
|
||||
this.logger.debug('[InMemoryProtestRepository] Finding all pending protests.');
|
||||
const pendingProtests = Array.from(this.protests.values()).filter(p => p.status === 'pending');
|
||||
const pendingProtests = Array.from(this.protests.values()).filter(p => p.status.toString() === 'pending');
|
||||
this.logger.info(`Found ${pendingProtests.length} pending protests.`);
|
||||
return Promise.resolve(pendingProtests);
|
||||
}
|
||||
|
||||
async findUnderReviewBy(stewardId: string): Promise<Protest[]> {
|
||||
this.logger.debug(`[InMemoryProtestRepository] Finding protests under review by steward: ${stewardId}.`);
|
||||
const underReviewProtests = Array.from(this.protests.values()).filter(p => p.reviewedBy === stewardId && p.status === 'under_review');
|
||||
const underReviewProtests = Array.from(this.protests.values()).filter(p => p.reviewedBy === stewardId && p.status.toString() === 'under_review');
|
||||
this.logger.info(`Found ${underReviewProtests.length} protests under review by steward ${stewardId}.`);
|
||||
return Promise.resolve(underReviewProtests);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryRaceEventRepository } from './InMemoryRaceEventRepository';
|
||||
import { RaceEvent } from '@core/racing/domain/entities/RaceEvent';
|
||||
import { Session } from '@core/racing/domain/entities/Session';
|
||||
import { SessionType } from '@core/racing/domain/value-objects/SessionType';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryRaceEventRepository', () => {
|
||||
let repository: InMemoryRaceEventRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
const createMockSession = (overrides: Partial<{
|
||||
id: string;
|
||||
raceEventId: string;
|
||||
sessionType: SessionType;
|
||||
status: 'scheduled' | 'running' | 'completed' | 'cancelled';
|
||||
scheduledAt: Date;
|
||||
}> = {}): Session => {
|
||||
return Session.create({
|
||||
id: overrides.id ?? 'session-1',
|
||||
raceEventId: overrides.raceEventId ?? 'race-event-1',
|
||||
scheduledAt: overrides.scheduledAt ?? new Date('2023-01-01T10:00:00Z'),
|
||||
track: 'Monza',
|
||||
car: 'Ferrari SF21',
|
||||
sessionType: overrides.sessionType ?? SessionType.main(),
|
||||
status: overrides.status ?? 'scheduled',
|
||||
});
|
||||
};
|
||||
|
||||
const createTestRaceEvent = (id: string, seasonId: string, leagueId: string, name?: string, status?: 'scheduled' | 'in_progress' | 'awaiting_stewarding' | 'closed' | 'cancelled') => {
|
||||
const sessions = [createMockSession({ raceEventId: id, sessionType: SessionType.main() })];
|
||||
const baseProps = {
|
||||
id,
|
||||
seasonId,
|
||||
leagueId,
|
||||
name: name ?? 'Test Race Event',
|
||||
sessions,
|
||||
};
|
||||
if (status) {
|
||||
return RaceEvent.create({ ...baseProps, status });
|
||||
}
|
||||
return RaceEvent.create(baseProps);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryRaceEventRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryRaceEventRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if race event not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding race event by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Race event with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the race event if found', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(raceEvent);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found race event: 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all race events', async () => {
|
||||
const raceEvent1 = createTestRaceEvent('1', 'season1', 'league1');
|
||||
const raceEvent2 = createTestRaceEvent('2', 'season2', 'league2');
|
||||
await repository.create(raceEvent1);
|
||||
await repository.create(raceEvent2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(raceEvent1);
|
||||
expect(result).toContain(raceEvent2);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 race events.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonId', () => {
|
||||
it('should return race events filtered by season ID', async () => {
|
||||
const raceEvent1 = createTestRaceEvent('1', 'season1', 'league1');
|
||||
const raceEvent2 = createTestRaceEvent('2', 'season2', 'league1');
|
||||
await repository.create(raceEvent1);
|
||||
await repository.create(raceEvent2);
|
||||
|
||||
const result = await repository.findBySeasonId('season1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(raceEvent1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return race events filtered by league ID', async () => {
|
||||
const raceEvent1 = createTestRaceEvent('1', 'season1', 'league1');
|
||||
const raceEvent2 = createTestRaceEvent('2', 'season1', 'league2');
|
||||
await repository.create(raceEvent1);
|
||||
await repository.create(raceEvent2);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(raceEvent1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByStatus', () => {
|
||||
it('should return race events filtered by status', async () => {
|
||||
const raceEvent1 = createTestRaceEvent('1', 'season1', 'league1');
|
||||
const raceEvent2 = createTestRaceEvent('2', 'season1', 'league1', 'Test', 'in_progress');
|
||||
await repository.create(raceEvent1);
|
||||
await repository.create(raceEvent2);
|
||||
|
||||
const result = await repository.findByStatus('scheduled');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(raceEvent1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAwaitingStewardingClose', () => {
|
||||
it('should return race events awaiting stewarding close', async () => {
|
||||
const pastDate = new Date(Date.now() - 3600000);
|
||||
const sessions = [createMockSession({ raceEventId: '1', sessionType: SessionType.main() })];
|
||||
const raceEvent = RaceEvent.create({
|
||||
id: '1',
|
||||
seasonId: 'season1',
|
||||
leagueId: 'league1',
|
||||
name: 'Test',
|
||||
sessions,
|
||||
status: 'awaiting_stewarding',
|
||||
stewardingClosesAt: pastDate,
|
||||
});
|
||||
await repository.create(raceEvent);
|
||||
|
||||
const result = await repository.findAwaitingStewardingClose();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(raceEvent);
|
||||
});
|
||||
|
||||
it('should not return race events not awaiting stewarding', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
|
||||
const result = await repository.findAwaitingStewardingClose();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new race event', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race event 1 created successfully.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing race event', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
|
||||
const updatedRaceEvent = raceEvent.start();
|
||||
await repository.update(updatedRaceEvent);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race event 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should create new if not exists', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.update(raceEvent);
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Race event with id 1 not found for update. Creating new.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing race event', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race event 1 deleted successfully.');
|
||||
});
|
||||
|
||||
it('should warn if race event not found', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Race event with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if race event exists', async () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
await repository.create(raceEvent);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if race event does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should clear all race events', () => {
|
||||
repository.clear();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Clearing all race events.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('All race events cleared.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all race events', () => {
|
||||
const raceEvent = createTestRaceEvent('1', 'season1', 'league1');
|
||||
repository.create(raceEvent);
|
||||
|
||||
const result = repository.getAll();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(raceEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,21 +1,17 @@
|
||||
/**
|
||||
* In-memory implementation of IRaceEventRepository for development/testing.
|
||||
*/
|
||||
import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
|
||||
import type { RaceEvent } from '../../domain/entities/RaceEvent';
|
||||
import type { IRaceEventRepository } from '@core/racing/domain/repositories/IRaceEventRepository';
|
||||
import type { RaceEvent } from '@core/racing/domain/entities/RaceEvent';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryRaceEventRepository implements IRaceEventRepository {
|
||||
private raceEvents: Map<string, RaceEvent> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: RaceEvent[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryRaceEventRepository initialized.');
|
||||
if (seedData) {
|
||||
seedData.forEach(event => this.raceEvents.set(event.id, event));
|
||||
this.logger.debug(`Seeded ${seedData.length} race events.`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<RaceEvent | null> {
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryRaceRegistrationRepository } from './InMemoryRaceRegistrationRepository';
|
||||
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryRaceRegistrationRepository', () => {
|
||||
let repository: InMemoryRaceRegistrationRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
const createTestRegistration = (raceId: string, driverId: string, id?: string) => {
|
||||
const props: { raceId: string; driverId: string; id?: string } = {
|
||||
raceId,
|
||||
driverId,
|
||||
};
|
||||
if (id) {
|
||||
props.id = id;
|
||||
}
|
||||
return RaceRegistration.create(props);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryRaceRegistrationRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryRaceRegistrationRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRegistered', () => {
|
||||
it('should return false if driver is not registered for race', async () => {
|
||||
const result = await repository.isRegistered('race1', 'driver1');
|
||||
expect(result).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Checking if driver driver1 is registered for race race1.');
|
||||
});
|
||||
|
||||
it('should return true if driver is registered for race', async () => {
|
||||
const registration = createTestRegistration('race1', 'driver1');
|
||||
await repository.register(registration);
|
||||
|
||||
const result = await repository.isRegistered('race1', 'driver1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRegisteredDrivers', () => {
|
||||
it('should return empty array if no drivers registered for race', async () => {
|
||||
const result = await repository.getRegisteredDrivers('race1');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registered drivers for race race1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 registered drivers for race race1.');
|
||||
});
|
||||
|
||||
it('should return registered drivers for race', async () => {
|
||||
const registration1 = createTestRegistration('race1', 'driver1');
|
||||
const registration2 = createTestRegistration('race1', 'driver2');
|
||||
const registration3 = createTestRegistration('race2', 'driver1');
|
||||
await repository.register(registration1);
|
||||
await repository.register(registration2);
|
||||
await repository.register(registration3);
|
||||
|
||||
const result = await repository.getRegisteredDrivers('race1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain('driver1');
|
||||
expect(result).toContain('driver2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRegistrationCount', () => {
|
||||
it('should return 0 if no registrations for race', async () => {
|
||||
const result = await repository.getRegistrationCount('race1');
|
||||
expect(result).toBe(0);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registration count for race race1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Registration count for race race1: 0.');
|
||||
});
|
||||
|
||||
it('should return correct count of registrations for race', async () => {
|
||||
const registration1 = createTestRegistration('race1', 'driver1');
|
||||
const registration2 = createTestRegistration('race1', 'driver2');
|
||||
await repository.register(registration1);
|
||||
await repository.register(registration2);
|
||||
|
||||
const result = await repository.getRegistrationCount('race1');
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register a driver for a race', async () => {
|
||||
const registration = createTestRegistration('race1', 'driver1');
|
||||
|
||||
await repository.register(registration);
|
||||
|
||||
const isRegistered = await repository.isRegistered('race1', 'driver1');
|
||||
expect(isRegistered).toBe(true);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Registering driver driver1 for race race1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Driver driver1 registered for race race1.');
|
||||
});
|
||||
|
||||
it('should throw error if driver already registered', async () => {
|
||||
const registration = createTestRegistration('race1', 'driver1');
|
||||
await repository.register(registration);
|
||||
|
||||
await expect(repository.register(registration)).rejects.toThrow('Driver already registered for this race');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver driver1 already registered for race race1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdraw', () => {
|
||||
it('should withdraw a driver from a race', async () => {
|
||||
const registration = createTestRegistration('race1', 'driver1');
|
||||
await repository.register(registration);
|
||||
|
||||
await repository.withdraw('race1', 'driver1');
|
||||
|
||||
const isRegistered = await repository.isRegistered('race1', 'driver1');
|
||||
expect(isRegistered).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Withdrawing driver driver1 from race race1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Driver driver1 withdrawn from race race1.');
|
||||
});
|
||||
|
||||
it('should throw error if driver not registered', async () => {
|
||||
await expect(repository.withdraw('race1', 'driver1')).rejects.toThrow('Driver not registered for this race');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Driver driver1 not registered for race race1. No withdrawal needed.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDriverRegistrations', () => {
|
||||
it('should return empty array if driver has no registrations', async () => {
|
||||
const result = await repository.getDriverRegistrations('driver1');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Getting registrations for driver: driver1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 registrations for driver driver1.');
|
||||
});
|
||||
|
||||
it('should return race IDs for driver registrations', async () => {
|
||||
const registration1 = createTestRegistration('race1', 'driver1');
|
||||
const registration2 = createTestRegistration('race2', 'driver1');
|
||||
const registration3 = createTestRegistration('race1', 'driver2');
|
||||
await repository.register(registration1);
|
||||
await repository.register(registration2);
|
||||
await repository.register(registration3);
|
||||
|
||||
const result = await repository.getDriverRegistrations('driver1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain('race1');
|
||||
expect(result).toContain('race2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearRaceRegistrations', () => {
|
||||
it('should clear all registrations for a race', async () => {
|
||||
const registration1 = createTestRegistration('race1', 'driver1');
|
||||
const registration2 = createTestRegistration('race1', 'driver2');
|
||||
const registration3 = createTestRegistration('race2', 'driver1');
|
||||
await repository.register(registration1);
|
||||
await repository.register(registration2);
|
||||
await repository.register(registration3);
|
||||
|
||||
await repository.clearRaceRegistrations('race1');
|
||||
|
||||
const count = await repository.getRegistrationCount('race1');
|
||||
expect(count).toBe(0);
|
||||
const race2Count = await repository.getRegistrationCount('race2');
|
||||
expect(race2Count).toBe(1);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRegistrationRepository] Clearing all registrations for race: race1.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Cleared 2 registrations for race race1.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepository {
|
||||
private registrations: Map<string, RaceRegistration> = new Map(); // Key: `${raceId}:${driverId}`
|
||||
|
||||
constructor(private readonly logger: Logger, initialRegistrations: RaceRegistration[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryRaceRegistrationRepository initialized.');
|
||||
for (const reg of initialRegistrations) {
|
||||
this.registrations.set(reg.id, reg);
|
||||
this.logger.debug(`Seeded registration: ${reg.id}.`);
|
||||
}
|
||||
}
|
||||
|
||||
async isRegistered(raceId: string, driverId: string): Promise<boolean> {
|
||||
@@ -23,8 +19,8 @@ export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepo
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registered drivers for race ${raceId}.`);
|
||||
const driverIds: string[] = [];
|
||||
for (const registration of this.registrations.values()) {
|
||||
if (registration.raceId === raceId) {
|
||||
driverIds.push(registration.driverId);
|
||||
if (registration.raceId.toString() === raceId) {
|
||||
driverIds.push(registration.driverId.toString());
|
||||
}
|
||||
}
|
||||
this.logger.info(`Found ${driverIds.length} registered drivers for race ${raceId}.`);
|
||||
@@ -33,19 +29,19 @@ export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepo
|
||||
|
||||
async getRegistrationCount(raceId: string): Promise<number> {
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registration count for race ${raceId}.`);
|
||||
const count = Array.from(this.registrations.values()).filter(reg => reg.raceId === raceId).length;
|
||||
const count = Array.from(this.registrations.values()).filter(reg => reg.raceId.toString() === raceId).length;
|
||||
this.logger.info(`Registration count for race ${raceId}: ${count}.`);
|
||||
return Promise.resolve(count);
|
||||
}
|
||||
|
||||
async register(registration: RaceRegistration): Promise<void> {
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Registering driver ${registration.driverId} for race ${registration.raceId}.`);
|
||||
if (await this.isRegistered(registration.raceId, registration.driverId)) {
|
||||
this.logger.warn(`Driver ${registration.driverId} already registered for race ${registration.raceId}.`);
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Registering driver ${registration.driverId.toString()} for race ${registration.raceId.toString()}.`);
|
||||
if (await this.isRegistered(registration.raceId.toString(), registration.driverId.toString())) {
|
||||
this.logger.warn(`Driver ${registration.driverId.toString()} already registered for race ${registration.raceId.toString()}.`);
|
||||
throw new Error('Driver already registered for this race');
|
||||
}
|
||||
this.registrations.set(registration.id, registration);
|
||||
this.logger.info(`Driver ${registration.driverId} registered for race ${registration.raceId}.`);
|
||||
this.logger.info(`Driver ${registration.driverId.toString()} registered for race ${registration.raceId.toString()}.`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -65,8 +61,8 @@ export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepo
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registrations for driver: ${driverId}.`);
|
||||
const raceIds: string[] = [];
|
||||
for (const registration of this.registrations.values()) {
|
||||
if (registration.driverId === driverId) {
|
||||
raceIds.push(registration.raceId);
|
||||
if (registration.driverId.toString() === driverId) {
|
||||
raceIds.push(registration.raceId.toString());
|
||||
}
|
||||
}
|
||||
this.logger.info(`Found ${raceIds.length} registrations for driver ${driverId}.`);
|
||||
@@ -77,7 +73,7 @@ export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepo
|
||||
this.logger.debug(`[InMemoryRaceRegistrationRepository] Clearing all registrations for race: ${raceId}.`);
|
||||
const registrationsToDelete: string[] = [];
|
||||
for (const registration of this.registrations.values()) {
|
||||
if (registration.raceId === raceId) {
|
||||
if (registration.raceId.toString() === raceId) {
|
||||
registrationsToDelete.push(registration.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryRaceRepository } from './InMemoryRaceRepository';
|
||||
import { Race, RaceStatus } from '@core/racing/domain/entities/Race';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryRaceRepository', () => {
|
||||
let repository: InMemoryRaceRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryRaceRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestRace = (
|
||||
id: string,
|
||||
leagueId: string,
|
||||
track: string,
|
||||
car: string,
|
||||
scheduledAt: Date,
|
||||
status: RaceStatus = 'scheduled'
|
||||
) => {
|
||||
return Race.create({
|
||||
id,
|
||||
leagueId,
|
||||
scheduledAt,
|
||||
track,
|
||||
car,
|
||||
status,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryRaceRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if race not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryRaceRepository] Finding race by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Race with ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the race if found', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await repository.create(race);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(race);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found race by ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all races', async () => {
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
const race2 = createTestRace('2', 'league2', 'Track2', 'Car2', new Date());
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(race1);
|
||||
expect(result).toContain(race2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return races filtered by league ID', async () => {
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
const race2 = createTestRace('2', 'league2', 'Track2', 'Car2', new Date());
|
||||
const race3 = createTestRace('3', 'league1', 'Track3', 'Car3', new Date());
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
await repository.create(race3);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(race1);
|
||||
expect(result).toContain(race3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUpcomingByLeagueId', () => {
|
||||
it('should return upcoming races for league ID', async () => {
|
||||
const pastDate = new Date(Date.now() - 1000 * 60 * 60); // 1 hour ago
|
||||
const futureDate = new Date(Date.now() + 1000 * 60 * 60); // 1 hour from now
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', pastDate, 'scheduled');
|
||||
const race2 = createTestRace('2', 'league1', 'Track2', 'Car2', futureDate, 'scheduled');
|
||||
const race3 = createTestRace('3', 'league1', 'Track3', 'Car3', futureDate, 'completed');
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
await repository.create(race3);
|
||||
|
||||
const result = await repository.findUpcomingByLeagueId('league1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toContain(race2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCompletedByLeagueId', () => {
|
||||
it('should return completed races for league ID', async () => {
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', new Date(), 'scheduled');
|
||||
const race2 = createTestRace('2', 'league1', 'Track2', 'Car2', new Date(), 'completed');
|
||||
const race3 = createTestRace('3', 'league1', 'Track3', 'Car3', new Date(), 'completed');
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
await repository.create(race3);
|
||||
|
||||
const result = await repository.findCompletedByLeagueId('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(race2);
|
||||
expect(result).toContain(race3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByStatus', () => {
|
||||
it('should return races filtered by status', async () => {
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', new Date(), 'scheduled');
|
||||
const race2 = createTestRace('2', 'league1', 'Track2', 'Car2', new Date(), 'completed');
|
||||
const race3 = createTestRace('3', 'league1', 'Track3', 'Car3', new Date(), 'scheduled');
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
await repository.create(race3);
|
||||
|
||||
const result = await repository.findByStatus('scheduled');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(race1);
|
||||
expect(result).toContain(race3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByDateRange', () => {
|
||||
it('should return races within date range', async () => {
|
||||
const date1 = new Date('2023-01-01');
|
||||
const date2 = new Date('2023-01-02');
|
||||
const date3 = new Date('2023-01-03');
|
||||
const race1 = createTestRace('1', 'league1', 'Track1', 'Car1', date1);
|
||||
const race2 = createTestRace('2', 'league1', 'Track2', 'Car2', date2);
|
||||
const race3 = createTestRace('3', 'league1', 'Track3', 'Car3', date3);
|
||||
await repository.create(race1);
|
||||
await repository.create(race2);
|
||||
await repository.create(race3);
|
||||
|
||||
const result = await repository.findByDateRange(date1, date2);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(race1);
|
||||
expect(result).toContain(race2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new race', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
const result = await repository.create(race);
|
||||
expect(result).toEqual(race);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if race already exists', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await repository.create(race);
|
||||
await expect(repository.create(race)).rejects.toThrow('Race already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing race', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await repository.create(race);
|
||||
|
||||
const updatedRace = race.complete();
|
||||
const result = await repository.update(updatedRace);
|
||||
expect(result).toEqual(updatedRace);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if race does not exist', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await expect(repository.update(race)).rejects.toThrow('Race not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing race', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await repository.create(race);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Race 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw if race does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Race with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if race exists', async () => {
|
||||
const race = createTestRace('1', 'league1', 'Track1', 'Car1', new Date());
|
||||
await repository.create(race);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if race does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemoryRaceRepository implements IRaceRepository {
|
||||
private races: Map<string, Race> = new Map();
|
||||
|
||||
constructor(private readonly logger: Logger, initialRaces: Race[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemoryRaceRepository initialized.');
|
||||
for (const race of initialRaces) {
|
||||
this.races.set(race.id, race);
|
||||
this.logger.debug(`Seeded race: ${race.id} (${race.track}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Race | null> {
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryResultRepository } from './InMemoryResultRepository';
|
||||
import { Result } from '@core/racing/domain/entities/result/Result';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
|
||||
describe('InMemoryResultRepository', () => {
|
||||
let repository: InMemoryResultRepository;
|
||||
let mockLogger: Logger;
|
||||
let mockRaceRepository: IRaceRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
mockRaceRepository = {
|
||||
findById: vi.fn(),
|
||||
findAll: vi.fn(),
|
||||
findByLeagueId: vi.fn(),
|
||||
findUpcomingByLeagueId: vi.fn(),
|
||||
findCompletedByLeagueId: vi.fn(),
|
||||
findByStatus: vi.fn(),
|
||||
findByDateRange: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
exists: vi.fn(),
|
||||
} as any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
repository = new InMemoryResultRepository(mockLogger, mockRaceRepository);
|
||||
});
|
||||
|
||||
const createTestResult = (
|
||||
id: string,
|
||||
raceId: string,
|
||||
driverId: string,
|
||||
position: number,
|
||||
fastestLap: number = 120.5,
|
||||
incidents: number = 0,
|
||||
startPosition: number = position
|
||||
) => {
|
||||
return Result.create({
|
||||
id,
|
||||
raceId,
|
||||
driverId,
|
||||
position,
|
||||
fastestLap,
|
||||
incidents,
|
||||
startPosition,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger and race repository', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if result not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemoryResultRepository] Finding result by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('[InMemoryResultRepository] Result with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the result if found', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toEqual(result);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Found result with id: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all results', async () => {
|
||||
const result1 = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const result2 = createTestResult('2', 'race1', 'driver2', 2);
|
||||
await repository.create(result1);
|
||||
await repository.create(result2);
|
||||
|
||||
const results = await repository.findAll();
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results).toContain(result1);
|
||||
expect(results).toContain(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByRaceId', () => {
|
||||
it('should return results filtered by race ID', async () => {
|
||||
const result1 = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const result2 = createTestResult('2', 'race2', 'driver2', 1);
|
||||
const result3 = createTestResult('3', 'race1', 'driver3', 2);
|
||||
await repository.create(result1);
|
||||
await repository.create(result2);
|
||||
await repository.create(result3);
|
||||
|
||||
const results = await repository.findByRaceId('race1');
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results).toContain(result1);
|
||||
expect(results).toContain(result3);
|
||||
expect(results[0]?.id).toBe('1'); // sorted by position
|
||||
expect(results[1]?.id).toBe('3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByDriverId', () => {
|
||||
it('should return results filtered by driver ID', async () => {
|
||||
const result1 = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const result2 = createTestResult('2', 'race2', 'driver1', 2);
|
||||
const result3 = createTestResult('3', 'race1', 'driver2', 1);
|
||||
await repository.create(result1);
|
||||
await repository.create(result2);
|
||||
await repository.create(result3);
|
||||
|
||||
const results = await repository.findByDriverId('driver1');
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results).toContain(result1);
|
||||
expect(results).toContain(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByDriverIdAndLeagueId', () => {
|
||||
it('should return results for driver in league', async () => {
|
||||
const result1 = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const result2 = createTestResult('2', 'race2', 'driver1', 2);
|
||||
await repository.create(result1);
|
||||
await repository.create(result2);
|
||||
|
||||
(mockRaceRepository.findByLeagueId as any).mockResolvedValue([ // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
{ id: 'race1' } as { id: string },
|
||||
]);
|
||||
|
||||
const results = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results).toContain(result1);
|
||||
});
|
||||
|
||||
it('should return empty if no race repository', async () => {
|
||||
repository = new InMemoryResultRepository(mockLogger); // no race repo
|
||||
const results = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
|
||||
expect(results).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new result', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const created = await repository.create(result);
|
||||
expect(created).toEqual(result);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Result 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if result already exists', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
await expect(repository.create(result)).rejects.toThrow('Result with ID 1 already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createMany', () => {
|
||||
it('should create multiple results', async () => {
|
||||
const results = [
|
||||
createTestResult('1', 'race1', 'driver1', 1),
|
||||
createTestResult('2', 'race1', 'driver2', 2),
|
||||
];
|
||||
const created = await repository.createMany(results);
|
||||
expect(created).toHaveLength(2);
|
||||
expect(created).toContain(results[0]);
|
||||
expect(created).toContain(results[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing result', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
|
||||
const updated = createTestResult('1', 'race1', 'driver1', 2);
|
||||
const result2 = await repository.update(updated);
|
||||
expect(result2).toEqual(updated);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Result 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if result does not exist', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await expect(repository.update(result)).rejects.toThrow('Result with ID 1 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing result', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Result 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error if result does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('Result with ID nonexistent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteByRaceId', () => {
|
||||
it('should delete results for a race', async () => {
|
||||
const result1 = createTestResult('1', 'race1', 'driver1', 1);
|
||||
const result2 = createTestResult('2', 'race2', 'driver2', 1);
|
||||
await repository.create(result1);
|
||||
await repository.create(result2);
|
||||
|
||||
await repository.deleteByRaceId('race1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[InMemoryResultRepository] Deleted 1 results for race id: race1.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
const found2 = await repository.findById('2');
|
||||
expect(found2).toEqual(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if result exists', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
|
||||
const exists = await repository.exists('1');
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if result does not exist', async () => {
|
||||
const exists = await repository.exists('nonexistent');
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('existsByRaceId', () => {
|
||||
it('should return true if results exist for race', async () => {
|
||||
const result = createTestResult('1', 'race1', 'driver1', 1);
|
||||
await repository.create(result);
|
||||
|
||||
const exists = await repository.existsByRaceId('race1');
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if no results for race', async () => {
|
||||
const exists = await repository.existsByRaceId('nonexistent');
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Result } from '@core/racing/domain/entities/Result';
|
||||
import { Result } from '@core/racing/domain/entities/result/Result';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
@@ -16,214 +16,207 @@ export class InMemoryResultRepository implements IResultRepository {
|
||||
private raceRepository: IRaceRepository | null;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Result[], raceRepository?: IRaceRepository | null) {
|
||||
constructor(logger: Logger, raceRepository?: IRaceRepository | null) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryResultRepository initialized.');
|
||||
this.logger.info('[InMemoryResultRepository] Initialized.');
|
||||
this.results = new Map();
|
||||
this.raceRepository = raceRepository ?? null;
|
||||
|
||||
if (seedData) {
|
||||
seedData.forEach(result => {
|
||||
this.results.set(result.id, result);
|
||||
this.logger.debug(`Seeded result: ${result.id}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Result | null> {
|
||||
this.logger.debug(`Finding result by id: ${id}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Finding result by id: ${id}`);
|
||||
try {
|
||||
const result = this.results.get(id) ?? null;
|
||||
if (result) {
|
||||
this.logger.info(`Found result with id: ${id}.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Found result with id: ${id}.`);
|
||||
} else {
|
||||
this.logger.warn(`Result with id ${id} not found.`);
|
||||
this.logger.warn(`[InMemoryResultRepository] Result with id ${id} not found.`);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding result by id ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error finding result by id ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<Result[]> {
|
||||
this.logger.debug('Finding all results.');
|
||||
this.logger.debug('[InMemoryResultRepository] Finding all results.');
|
||||
try {
|
||||
const results = Array.from(this.results.values());
|
||||
this.logger.info(`Found ${results.length} results.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Found ${results.length} results.`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error('Error finding all results:', error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error('[InMemoryResultRepository] Error finding all results:', error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findByRaceId(raceId: string): Promise<Result[]> {
|
||||
this.logger.debug(`Finding results for race id: ${raceId}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Finding results for race id: ${raceId}`);
|
||||
try {
|
||||
const results = Array.from(this.results.values())
|
||||
.filter(result => result.raceId === raceId)
|
||||
.sort((a, b) => a.position - b.position);
|
||||
this.logger.info(`Found ${results.length} results for race id: ${raceId}.`);
|
||||
.filter(result => result.raceId.toString() === raceId)
|
||||
.sort((a, b) => a.position.toNumber() - b.position.toNumber());
|
||||
this.logger.info(`[InMemoryResultRepository] Found ${results.length} results for race id: ${raceId}.`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error finding results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findByDriverId(driverId: string): Promise<Result[]> {
|
||||
this.logger.debug(`Finding results for driver id: ${driverId}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Finding results for driver id: ${driverId}`);
|
||||
try {
|
||||
const results = Array.from(this.results.values())
|
||||
.filter(result => result.driverId === driverId);
|
||||
this.logger.info(`Found ${results.length} results for driver id: ${driverId}.`);
|
||||
.filter(result => result.driverId.toString() === driverId);
|
||||
this.logger.info(`[InMemoryResultRepository] Found ${results.length} results for driver id: ${driverId}.`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding results for driver id ${driverId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error finding results for driver id ${driverId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Result[]> {
|
||||
this.logger.debug(`Finding results for driver id: ${driverId} and league id: ${leagueId}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Finding results for driver id: ${driverId} and league id: ${leagueId}`);
|
||||
try {
|
||||
if (!this.raceRepository) {
|
||||
this.logger.warn('Race repository not provided to InMemoryResultRepository. Skipping league-filtered search.');
|
||||
this.logger.warn('[InMemoryResultRepository] Race repository not provided to InMemoryResultRepository. Skipping league-filtered search.');
|
||||
return [];
|
||||
}
|
||||
|
||||
const leagueRaces = await this.raceRepository.findByLeagueId(leagueId);
|
||||
const leagueRaceIds = new Set(leagueRaces.map(race => race.id));
|
||||
this.logger.debug(`Found ${leagueRaces.length} races in league ${leagueId}.`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Found ${leagueRaces.length} races in league ${leagueId}.`);
|
||||
|
||||
const results = Array.from(this.results.values())
|
||||
.filter(result =>
|
||||
result.driverId === driverId &&
|
||||
leagueRaceIds.has(result.raceId)
|
||||
result.driverId.toString() === driverId &&
|
||||
leagueRaceIds.has(result.raceId.toString())
|
||||
);
|
||||
this.logger.info(`Found ${results.length} results for driver ${driverId} in league ${leagueId}.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Found ${results.length} results for driver ${driverId} in league ${leagueId}.`);
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding results for driver ${driverId} and league ${leagueId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error finding results for driver ${driverId} and league ${leagueId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async create(result: Result): Promise<Result> {
|
||||
this.logger.debug(`Creating result: ${result.id}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Creating result: ${result.id}`);
|
||||
try {
|
||||
if (await this.exists(result.id)) {
|
||||
this.logger.warn(`Result with ID ${result.id} already exists. Throwing error.`);
|
||||
this.logger.warn(`[InMemoryResultRepository] Result with ID ${result.id} already exists. Throwing error.`);
|
||||
throw new Error(`Result with ID ${result.id} already exists`);
|
||||
}
|
||||
|
||||
this.results.set(result.id, result);
|
||||
this.logger.info(`Result ${result.id} created successfully.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Result ${result.id} created successfully.`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error creating result ${result.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error creating result ${result.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createMany(results: Result[]): Promise<Result[]> {
|
||||
this.logger.debug(`Creating ${results.length} results.`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Creating ${results.length} results.`);
|
||||
try {
|
||||
const created: Result[] = [];
|
||||
|
||||
|
||||
for (const result of results) {
|
||||
if (await this.exists(result.id)) {
|
||||
this.logger.warn(`Result with ID ${result.id} already exists. Skipping creation.`);
|
||||
this.logger.warn(`[InMemoryResultRepository] Result with ID ${result.id} already exists. Skipping creation.`);
|
||||
// In a real system, decide if this should throw or log and skip
|
||||
continue;
|
||||
}
|
||||
this.results.set(result.id, result);
|
||||
created.push(result);
|
||||
}
|
||||
this.logger.info(`Created ${created.length} results successfully.`);
|
||||
|
||||
this.logger.info(`[InMemoryResultRepository] Created ${created.length} results successfully.`);
|
||||
|
||||
return created;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error creating many results:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error creating many results:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update(result: Result): Promise<Result> {
|
||||
this.logger.debug(`Updating result: ${result.id}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Updating result: ${result.id}`);
|
||||
try {
|
||||
if (!await this.exists(result.id)) {
|
||||
this.logger.warn(`Result with ID ${result.id} not found for update. Throwing error.`);
|
||||
this.logger.warn(`[InMemoryResultRepository] Result with ID ${result.id} not found for update. Throwing error.`);
|
||||
throw new Error(`Result with ID ${result.id} not found`);
|
||||
}
|
||||
|
||||
this.results.set(result.id, result);
|
||||
this.logger.info(`Result ${result.id} updated successfully.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Result ${result.id} updated successfully.`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error updating result ${result.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error updating result ${result.id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
this.logger.debug(`Deleting result: ${id}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Deleting result: ${id}`);
|
||||
try {
|
||||
if (!await this.exists(id)) {
|
||||
this.logger.warn(`Result with ID ${id} not found for deletion. Throwing error.`);
|
||||
this.logger.warn(`[InMemoryResultRepository] Result with ID ${id} not found for deletion. Throwing error.`);
|
||||
throw new Error(`Result with ID ${id} not found`);
|
||||
}
|
||||
|
||||
this.results.delete(id);
|
||||
this.logger.info(`Result ${id} deleted successfully.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Result ${id} deleted successfully.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting result ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error deleting result ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteByRaceId(raceId: string): Promise<void> {
|
||||
this.logger.debug(`Deleting results for race id: ${raceId}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Deleting results for race id: ${raceId}`);
|
||||
try {
|
||||
const initialCount = this.results.size;
|
||||
const raceResults = Array.from(this.results.values()).filter(
|
||||
result => result.raceId === raceId
|
||||
result => result.raceId.toString() === raceId
|
||||
);
|
||||
raceResults.forEach(result => {
|
||||
this.results.delete(result.id);
|
||||
});
|
||||
this.logger.info(`Deleted ${raceResults.length} results for race id: ${raceId}.`);
|
||||
this.logger.info(`[InMemoryResultRepository] Deleted ${raceResults.length} results for race id: ${raceId}.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error deleting results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
this.logger.debug(`Checking existence of result with id: ${id}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Checking existence of result with id: ${id}`);
|
||||
try {
|
||||
const exists = this.results.has(id);
|
||||
this.logger.debug(`Result ${id} exists: ${exists}.`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Result ${id} exists: ${exists}.`);
|
||||
return exists;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error checking existence of result with id ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error checking existence of result with id ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async existsByRaceId(raceId: string): Promise<boolean> {
|
||||
this.logger.debug(`Checking existence of results for race id: ${raceId}`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Checking existence of results for race id: ${raceId}`);
|
||||
try {
|
||||
const exists = Array.from(this.results.values()).some(
|
||||
result => result.raceId === raceId
|
||||
result => result.raceId.toString() === raceId
|
||||
);
|
||||
this.logger.debug(`Results for race ${raceId} exist: ${exists}.`);
|
||||
this.logger.debug(`[InMemoryResultRepository] Results for race ${raceId} exist: ${exists}.`);
|
||||
return exists;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error checking existence of results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
this.logger.error(`[InMemoryResultRepository] Error checking existence of results for race id ${raceId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to generate a new UUID
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
InMemoryGameRepository,
|
||||
InMemorySeasonRepository,
|
||||
InMemoryLeagueScoringConfigRepository,
|
||||
InMemoryChampionshipStandingRepository,
|
||||
} from './InMemoryScoringRepositories';
|
||||
import { Game } from '@core/racing/domain/entities/Game';
|
||||
import { Season } from '@core/racing/domain/entities/season/Season';
|
||||
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
|
||||
import { ChampionshipStanding } from '@core/racing/domain/entities/championship/ChampionshipStanding';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryScoringRepositories', () => {
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('InMemoryGameRepository', () => {
|
||||
let repository: InMemoryGameRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryGameRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryGameRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if game not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding game by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Game with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the game if found', async () => {
|
||||
const game = Game.create({ id: 'test-game', name: 'Test Game' });
|
||||
repository.seed(game);
|
||||
const result = await repository.findById('test-game');
|
||||
expect(result).toEqual(game);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(`Found game: ${game.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all games', async () => {
|
||||
const game1 = Game.create({ id: 'game1', name: 'Game 1' });
|
||||
const game2 = Game.create({ id: 'game2', name: 'Game 2' });
|
||||
repository.seed(game1);
|
||||
repository.seed(game2);
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([game1, game2]);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 games.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InMemorySeasonRepository', () => {
|
||||
let repository: InMemorySeasonRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemorySeasonRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySeasonRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if season not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Season with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the season if found', async () => {
|
||||
const season = Season.create({
|
||||
id: 'test-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Test Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
repository.seed(season);
|
||||
const result = await repository.findById('test-season');
|
||||
expect(result).toEqual(season);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return seasons for the league', async () => {
|
||||
const season1 = Season.create({
|
||||
id: 'season1',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Season 1',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
const season2 = Season.create({
|
||||
id: 'season2',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Season 2',
|
||||
year: 2025,
|
||||
order: 2,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
repository.seed(season1);
|
||||
repository.seed(season2);
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toEqual([season1, season2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a season', async () => {
|
||||
const season = Season.create({
|
||||
id: 'new-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'New Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
const result = await repository.create(season);
|
||||
expect(result).toEqual(season);
|
||||
const found = await repository.findById('new-season');
|
||||
expect(found).toEqual(season);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
it('should add a season', async () => {
|
||||
const season = Season.create({
|
||||
id: 'add-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Add Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
await repository.add(season);
|
||||
const found = await repository.findById('add-season');
|
||||
expect(found).toEqual(season);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing season', async () => {
|
||||
const season = Season.create({
|
||||
id: 'update-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Update Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
repository.seed(season);
|
||||
const updatedSeason = { ...season, name: 'Updated Name' } as Season;
|
||||
await repository.update(updatedSeason);
|
||||
const found = await repository.findById('update-season');
|
||||
expect(found?.name).toBe('Updated Name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('listByLeague', () => {
|
||||
it('should list seasons by league', async () => {
|
||||
const season = Season.create({
|
||||
id: 'list-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'List Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
repository.seed(season);
|
||||
const result = await repository.listByLeague('league1');
|
||||
expect(result).toEqual([season]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listActiveByLeague', () => {
|
||||
it('should list active seasons by league', async () => {
|
||||
const activeSeason = Season.create({
|
||||
id: 'active-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Active Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
const inactiveSeason = Season.create({
|
||||
id: 'inactive-season',
|
||||
leagueId: 'league1',
|
||||
gameId: 'game1',
|
||||
name: 'Inactive Season',
|
||||
year: 2025,
|
||||
order: 2,
|
||||
status: 'completed',
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
});
|
||||
repository.seed(activeSeason);
|
||||
repository.seed(inactiveSeason);
|
||||
const result = await repository.listActiveByLeague('league1');
|
||||
expect(result).toEqual([activeSeason]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InMemoryLeagueScoringConfigRepository', () => {
|
||||
let repository: InMemoryLeagueScoringConfigRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryLeagueScoringConfigRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryLeagueScoringConfigRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonId', () => {
|
||||
it('should return null if config not found', async () => {
|
||||
const result = await repository.findBySeasonId('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('League scoring config for seasonId nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the config if found', async () => {
|
||||
const config = {
|
||||
id: 'config1',
|
||||
seasonId: 'season1',
|
||||
scoringPresetId: 'preset1',
|
||||
championships: [],
|
||||
equals: vi.fn(),
|
||||
} as unknown as LeagueScoringConfig;
|
||||
repository.seed(config);
|
||||
const result = await repository.findBySeasonId('season1');
|
||||
expect(result).toEqual(config);
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should save a new config', async () => {
|
||||
const config = {
|
||||
id: 'new-config',
|
||||
seasonId: 'season1',
|
||||
scoringPresetId: 'preset1',
|
||||
championships: [],
|
||||
equals: vi.fn(),
|
||||
} as unknown as LeagueScoringConfig;
|
||||
const result = await repository.save(config);
|
||||
expect(result).toEqual(config);
|
||||
const found = await repository.findBySeasonId('season1');
|
||||
expect(found).toEqual(config);
|
||||
});
|
||||
|
||||
it('should update an existing config', async () => {
|
||||
const config = {
|
||||
id: 'update-config',
|
||||
seasonId: 'season1',
|
||||
scoringPresetId: 'preset1',
|
||||
championships: [],
|
||||
equals: vi.fn(),
|
||||
} as unknown as LeagueScoringConfig;
|
||||
repository.seed(config);
|
||||
const updatedConfig = { ...config, scoringPresetId: 'preset2' } as unknown as LeagueScoringConfig;
|
||||
const result = await repository.save(updatedConfig);
|
||||
expect(result).toEqual(updatedConfig);
|
||||
const found = await repository.findBySeasonId('season1');
|
||||
expect(found?.scoringPresetId).toBe('preset2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('InMemoryChampionshipStandingRepository', () => {
|
||||
let repository: InMemoryChampionshipStandingRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
repository = new InMemoryChampionshipStandingRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryChampionshipStandingRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonAndChampionship', () => {
|
||||
it('should return standings for season and championship', async () => {
|
||||
const standing = ChampionshipStanding.create({
|
||||
seasonId: 'season1',
|
||||
championshipId: 'champ1',
|
||||
participant: { type: 'driver', id: 'driver1' },
|
||||
totalPoints: 100,
|
||||
resultsCounted: 10,
|
||||
resultsDropped: 0,
|
||||
position: 1,
|
||||
});
|
||||
repository.seed(standing);
|
||||
const result = await repository.findBySeasonAndChampionship('season1', 'champ1');
|
||||
expect(result).toEqual([standing]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveAll', () => {
|
||||
it('should save all standings', async () => {
|
||||
const standing1 = ChampionshipStanding.create({
|
||||
seasonId: 'season1',
|
||||
championshipId: 'champ1',
|
||||
participant: { type: 'driver', id: 'driver1' },
|
||||
totalPoints: 100,
|
||||
resultsCounted: 10,
|
||||
resultsDropped: 0,
|
||||
position: 1,
|
||||
});
|
||||
const standing2 = ChampionshipStanding.create({
|
||||
seasonId: 'season1',
|
||||
championshipId: 'champ1',
|
||||
participant: { type: 'driver', id: 'driver2' },
|
||||
totalPoints: 80,
|
||||
resultsCounted: 10,
|
||||
resultsDropped: 0,
|
||||
position: 2,
|
||||
});
|
||||
await repository.saveAll([standing1, standing2]);
|
||||
const result = await repository.findBySeasonAndChampionship('season1', 'champ1');
|
||||
expect(result).toEqual([standing1, standing2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all standings', () => {
|
||||
const standing = ChampionshipStanding.create({
|
||||
seasonId: 'season1',
|
||||
championshipId: 'champ1',
|
||||
participant: { type: 'driver', id: 'driver1' },
|
||||
totalPoints: 100,
|
||||
resultsCounted: 10,
|
||||
resultsDropped: 0,
|
||||
position: 1,
|
||||
});
|
||||
repository.seed(standing);
|
||||
const result = repository.getAll();
|
||||
expect(result).toEqual([standing]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,266 +1,13 @@
|
||||
import { Game } from '@core/racing/domain/entities/Game';
|
||||
import { Season } from '@core/racing/domain/entities/season/Season';
|
||||
import type { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
|
||||
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
|
||||
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
|
||||
import type { SessionType } from '@core/racing/domain/types/SessionType';
|
||||
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
|
||||
import type { DropScorePolicy } from '@core/racing/domain/types/DropScorePolicy';
|
||||
import type { IGameRepository } from '@core/racing/domain/repositories/IGameRepository';
|
||||
import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository';
|
||||
import type { ILeagueScoringConfigRepository } from '@core/racing/domain/repositories/ILeagueScoringConfigRepository';
|
||||
import type { IChampionshipStandingRepository } from '@core/racing/domain/repositories/IChampionshipStandingRepository';
|
||||
import { ChampionshipStanding } from '@core/racing/domain/entities/championship/ChampionshipStanding';
|
||||
import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType';
|
||||
import type { ParticipantRef } from '@core/racing/domain/types/ParticipantRef';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
class SilentLogger implements Logger {
|
||||
debug(..._args: unknown[]): void {
|
||||
// console.debug(..._args);
|
||||
}
|
||||
info(..._args: unknown[]): void {
|
||||
// console.info(..._args);
|
||||
}
|
||||
warn(..._args: unknown[]): void {
|
||||
// console.warn(..._args);
|
||||
}
|
||||
error(..._args: unknown[]): void {
|
||||
// console.error(..._args);
|
||||
}
|
||||
}
|
||||
|
||||
export type LeagueScoringPresetPrimaryChampionshipType =
|
||||
| 'driver'
|
||||
| 'team'
|
||||
| 'nations'
|
||||
| 'trophy';
|
||||
|
||||
export interface LeagueScoringPreset {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType;
|
||||
dropPolicySummary: string;
|
||||
sessionSummary: string;
|
||||
bonusSummary: string;
|
||||
createConfig: (options: { seasonId: string }) => LeagueScoringConfig;
|
||||
}
|
||||
|
||||
const mainPointsSprintMain = new PointsTable({
|
||||
1: 25,
|
||||
2: 18,
|
||||
3: 15,
|
||||
4: 12,
|
||||
5: 10,
|
||||
6: 8,
|
||||
7: 6,
|
||||
8: 4,
|
||||
9: 2,
|
||||
10: 1,
|
||||
});
|
||||
|
||||
const sprintPointsSprintMain = new PointsTable({
|
||||
1: 8,
|
||||
2: 7,
|
||||
3: 6,
|
||||
4: 5,
|
||||
5: 4,
|
||||
6: 3,
|
||||
7: 2,
|
||||
8: 1,
|
||||
});
|
||||
|
||||
const clubMainPoints = new PointsTable({
|
||||
1: 20,
|
||||
2: 15,
|
||||
3: 12,
|
||||
4: 10,
|
||||
5: 8,
|
||||
6: 6,
|
||||
7: 4,
|
||||
8: 2,
|
||||
9: 1,
|
||||
});
|
||||
|
||||
const enduranceMainPoints = new PointsTable({
|
||||
1: 50,
|
||||
2: 36,
|
||||
3: 30,
|
||||
4: 24,
|
||||
5: 20,
|
||||
6: 16,
|
||||
7: 12,
|
||||
8: 8,
|
||||
9: 4,
|
||||
10: 2,
|
||||
});
|
||||
|
||||
const leagueScoringPresets: LeagueScoringPreset[] = [
|
||||
{
|
||||
id: 'sprint-main-driver',
|
||||
name: 'Sprint + Main',
|
||||
description:
|
||||
'Short sprint race plus main race; sprint gives fewer points.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'Best 6 results of 8 count towards the championship.',
|
||||
sessionSummary: 'Sprint + Main',
|
||||
bonusSummary: 'Fastest lap +1 point in main race if finishing P10 or better.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const fastestLapBonus: BonusRule = {
|
||||
id: 'fastest-lap-main',
|
||||
type: 'fastestLap',
|
||||
points: 1,
|
||||
requiresFinishInTopN: 10,
|
||||
};
|
||||
|
||||
const sessionTypes: SessionType[] = ['sprint', 'main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: sprintPointsSprintMain,
|
||||
main: mainPointsSprintMain,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {
|
||||
sprint: [],
|
||||
main: [fastestLapBonus],
|
||||
practice: [],
|
||||
qualifying: [],
|
||||
q1: [],
|
||||
q2: [],
|
||||
q3: [],
|
||||
timeTrial: [],
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'bestNResults',
|
||||
count: 6,
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-sprint-main',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
bonusRulesBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return {
|
||||
id: `lsc-${seasonId}-sprint-main-driver`,
|
||||
seasonId,
|
||||
scoringPresetId: 'sprint-main-driver',
|
||||
championships: [championship],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'club-default',
|
||||
name: 'Club ladder',
|
||||
description:
|
||||
'Simple club ladder with a single main race and no bonuses or drop scores.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'All race results count, no drop scores.',
|
||||
sessionSummary: 'Main race only',
|
||||
bonusSummary: 'No bonus points.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const sessionTypes: SessionType[] = ['main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: new PointsTable({}),
|
||||
main: clubMainPoints,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'none',
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-club-default',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return {
|
||||
id: `lsc-${seasonId}-club-default`,
|
||||
seasonId,
|
||||
scoringPresetId: 'club-default',
|
||||
championships: [championship],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'endurance-main-double',
|
||||
name: 'Endurance weekend',
|
||||
description:
|
||||
'Single main endurance race with double points and a simple drop policy.',
|
||||
primaryChampionshipType: 'driver',
|
||||
dropPolicySummary: 'Best 4 results of 6 count towards the championship.',
|
||||
sessionSummary: 'Main race only',
|
||||
bonusSummary: 'No bonus points.',
|
||||
createConfig: ({ seasonId }) => {
|
||||
const sessionTypes: SessionType[] = ['main'];
|
||||
|
||||
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
|
||||
sprint: new PointsTable({}),
|
||||
main: enduranceMainPoints,
|
||||
practice: new PointsTable({}),
|
||||
qualifying: new PointsTable({}),
|
||||
q1: new PointsTable({}),
|
||||
q2: new PointsTable({}),
|
||||
q3: new PointsTable({}),
|
||||
timeTrial: new PointsTable({}),
|
||||
};
|
||||
|
||||
const dropScorePolicy: DropScorePolicy = {
|
||||
strategy: 'bestNResults',
|
||||
count: 4,
|
||||
};
|
||||
|
||||
const championship: ChampionshipConfig = {
|
||||
id: 'driver-champ-endurance-main-double',
|
||||
name: 'Driver Championship',
|
||||
type: 'driver' as ChampionshipType,
|
||||
sessionTypes,
|
||||
pointsTableBySessionType,
|
||||
dropScorePolicy,
|
||||
};
|
||||
|
||||
return {
|
||||
id: `lsc-${seasonId}-endurance-main-double`,
|
||||
seasonId,
|
||||
scoringPresetId: 'endurance-main-double',
|
||||
championships: [championship],
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function listLeagueScoringPresets(): LeagueScoringPreset[] {
|
||||
return [...leagueScoringPresets];
|
||||
}
|
||||
|
||||
export function getLeagueScoringPresetById(
|
||||
id: string,
|
||||
): LeagueScoringPreset | undefined {
|
||||
return leagueScoringPresets.find((preset) => preset.id === id);
|
||||
}
|
||||
|
||||
export class InMemoryGameRepository implements IGameRepository {
|
||||
private games: Game[];
|
||||
@@ -275,7 +22,7 @@ export class InMemoryGameRepository implements IGameRepository {
|
||||
async findById(id: string): Promise<Game | null> {
|
||||
this.logger.debug(`Finding game by id: ${id}`);
|
||||
try {
|
||||
const game = this.games.find((g) => g.id === id) ?? null;
|
||||
const game = this.games.find((g) => g.id.toString() === id) ?? null;
|
||||
if (game) {
|
||||
this.logger.info(`Found game: ${game.id}`);
|
||||
} else {
|
||||
@@ -444,7 +191,7 @@ export class InMemoryLeagueScoringConfigRepository
|
||||
async findBySeasonId(seasonId: string): Promise<LeagueScoringConfig | null> {
|
||||
this.logger.debug(`Finding league scoring config by seasonId: ${seasonId}`);
|
||||
try {
|
||||
const config = this.configs.find((c) => c.seasonId === seasonId) ?? null;
|
||||
const config = this.configs.find((c) => c.seasonId.toString() === seasonId) ?? null;
|
||||
if (config) {
|
||||
this.logger.info(`Found league scoring config for seasonId: ${seasonId}.`);
|
||||
} else {
|
||||
@@ -513,7 +260,7 @@ export class InMemoryChampionshipStandingRepository
|
||||
this.logger.info(`Found ${standings.length} championship standings.`);
|
||||
return standings;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding championship standings for season ${seasonId}, championship ${championshipId}:`, error);
|
||||
this.logger.error(`Error finding championship standings for season ${seasonId}, championship ${championshipId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -551,68 +298,4 @@ export class InMemoryChampionshipStandingRepository
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSprintMainDemoScoringSetup(params: {
|
||||
leagueId: string;
|
||||
seasonId?: string;
|
||||
}): {
|
||||
gameRepo: InMemoryGameRepository;
|
||||
seasonRepo: InMemorySeasonRepository;
|
||||
scoringConfigRepo: InMemoryLeagueScoringConfigRepository;
|
||||
championshipStandingRepo: InMemoryChampionshipStandingRepository;
|
||||
seasonId: string;
|
||||
championshipId: string;
|
||||
} {
|
||||
const { leagueId } = params;
|
||||
const seasonId = params.seasonId ?? 'season-sprint-main-demo';
|
||||
const championshipId = 'driver-champ';
|
||||
|
||||
const logger = new SilentLogger();
|
||||
|
||||
const game = Game.create({ id: 'iracing', name: 'iRacing' });
|
||||
|
||||
const season = Season.create({
|
||||
id: seasonId,
|
||||
leagueId,
|
||||
gameId: game.id,
|
||||
name: 'Sprint + Main Demo Season',
|
||||
year: 2025,
|
||||
order: 1,
|
||||
status: 'active',
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
});
|
||||
|
||||
const preset = getLeagueScoringPresetById('sprint-main-driver');
|
||||
if (!preset) {
|
||||
throw new Error('Missing sprint-main-driver scoring preset');
|
||||
}
|
||||
|
||||
const leagueScoringConfig: LeagueScoringConfig = preset.createConfig({
|
||||
seasonId: season.id,
|
||||
});
|
||||
|
||||
const gameRepo = new InMemoryGameRepository(logger, [game]);
|
||||
const seasonRepo = new InMemorySeasonRepository(logger, [season]);
|
||||
const scoringConfigRepo = new InMemoryLeagueScoringConfigRepository(logger, [
|
||||
leagueScoringConfig,
|
||||
]);
|
||||
const championshipStandingRepo = new InMemoryChampionshipStandingRepository(logger);
|
||||
|
||||
return {
|
||||
gameRepo,
|
||||
seasonRepo,
|
||||
scoringConfigRepo,
|
||||
championshipStandingRepo,
|
||||
seasonId: season.id,
|
||||
championshipId,
|
||||
};
|
||||
}
|
||||
|
||||
export function createParticipantRef(driverId: string): ParticipantRef {
|
||||
return {
|
||||
type: 'driver' as ChampionshipType,
|
||||
id: driverId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySeasonRepository } from './InMemorySeasonRepository';
|
||||
import { Season } from '@core/racing/domain/entities/season/Season';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySeasonRepository', () => {
|
||||
let repository: InMemorySeasonRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySeasonRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestSeason = (
|
||||
id: string,
|
||||
leagueId: string,
|
||||
name: string = 'Test Season',
|
||||
status: 'planned' | 'active' | 'completed' = 'planned'
|
||||
) => {
|
||||
return Season.create({
|
||||
id,
|
||||
leagueId,
|
||||
gameId: 'iracing',
|
||||
name,
|
||||
status,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySeasonRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if season not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemorySeasonRepository] Finding season by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Season with ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the season if found', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.create(season);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(season);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found season by ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return seasons filtered by league ID', async () => {
|
||||
const season1 = createTestSeason('1', 'league1');
|
||||
const season2 = createTestSeason('2', 'league2');
|
||||
const season3 = createTestSeason('3', 'league1');
|
||||
await repository.create(season1);
|
||||
await repository.create(season2);
|
||||
await repository.create(season3);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(season1);
|
||||
expect(result).toContain(season3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new season', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
const result = await repository.create(season);
|
||||
expect(result).toEqual(season);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Season 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if season already exists', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.create(season);
|
||||
await expect(repository.create(season)).rejects.toThrow('Season already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
it('should add a new season', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.add(season);
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toEqual(season);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Season 1 added successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if season already exists', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.add(season);
|
||||
await expect(repository.add(season)).rejects.toThrow('Season already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing season', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.create(season);
|
||||
|
||||
const updatedSeason = Season.create({
|
||||
...season,
|
||||
name: 'Updated Season',
|
||||
});
|
||||
await repository.update(updatedSeason);
|
||||
const found = await repository.findById('1');
|
||||
expect(found?.name).toBe('Updated Season');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Season 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if season does not exist', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await expect(repository.update(season)).rejects.toThrow('Season not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing season', async () => {
|
||||
const season = createTestSeason('1', 'league1');
|
||||
await repository.create(season);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Season 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw if season does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Season with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('listByLeague', () => {
|
||||
it('should list seasons by league ID', async () => {
|
||||
const season1 = createTestSeason('1', 'league1');
|
||||
const season2 = createTestSeason('2', 'league2');
|
||||
const season3 = createTestSeason('3', 'league1');
|
||||
await repository.create(season1);
|
||||
await repository.create(season2);
|
||||
await repository.create(season3);
|
||||
|
||||
const result = await repository.listByLeague('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(season1);
|
||||
expect(result).toContain(season3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listActiveByLeague', () => {
|
||||
it('should list active seasons by league ID', async () => {
|
||||
const activeSeason = createTestSeason('1', 'league1', 'Active Season', 'active');
|
||||
const plannedSeason = createTestSeason('2', 'league1', 'Planned Season', 'planned');
|
||||
const otherLeagueSeason = createTestSeason('3', 'league2', 'Other League', 'active');
|
||||
await repository.create(activeSeason);
|
||||
await repository.create(plannedSeason);
|
||||
await repository.create(otherLeagueSeason);
|
||||
|
||||
const result = await repository.listActiveByLeague('league1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toContain(activeSeason);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,8 @@ import { Logger } from '@core/shared/application';
|
||||
export class InMemorySeasonRepository implements ISeasonRepository {
|
||||
private seasons: Map<string, Season> = new Map(); // Key: seasonId
|
||||
|
||||
constructor(private readonly logger: Logger, initialSeasons: Season[] = []) {
|
||||
constructor(private readonly logger: Logger) {
|
||||
this.logger.info('InMemorySeasonRepository initialized.');
|
||||
for (const season of initialSeasons) {
|
||||
this.seasons.set(season.id, season);
|
||||
this.logger.debug(`Seeded season: ${season.id} (${season.name}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Season | null> {
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySeasonSponsorshipRepository } from './InMemorySeasonSponsorshipRepository';
|
||||
import { SeasonSponsorship, type SponsorshipTier } from '@core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySeasonSponsorshipRepository', () => {
|
||||
let repository: InMemorySeasonSponsorshipRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySeasonSponsorshipRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestSeasonSponsorship = (
|
||||
id: string,
|
||||
seasonId: string,
|
||||
sponsorId: string,
|
||||
tier: SponsorshipTier = 'main',
|
||||
leagueId?: string
|
||||
) => {
|
||||
return SeasonSponsorship.create({
|
||||
id,
|
||||
seasonId,
|
||||
sponsorId,
|
||||
tier,
|
||||
pricing: Money.create(1000),
|
||||
...(leagueId ? { leagueId } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySeasonSponsorshipRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if sponsorship not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding season sponsorship by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Season sponsorship with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the sponsorship if found', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(sponsorship);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found season sponsorship: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonId', () => {
|
||||
it('should return sponsorships filtered by season ID', async () => {
|
||||
const sponsorship1 = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
const sponsorship2 = createTestSeasonSponsorship('2', 'season2', 'sponsor2');
|
||||
const sponsorship3 = createTestSeasonSponsorship('3', 'season1', 'sponsor3');
|
||||
await repository.create(sponsorship1);
|
||||
await repository.create(sponsorship2);
|
||||
await repository.create(sponsorship3);
|
||||
|
||||
const result = await repository.findBySeasonId('season1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(sponsorship1);
|
||||
expect(result).toContain(sponsorship3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return sponsorships filtered by league ID', async () => {
|
||||
const sponsorship1 = createTestSeasonSponsorship('1', 'season1', 'sponsor1', 'main', 'league1');
|
||||
const sponsorship2 = createTestSeasonSponsorship('2', 'season2', 'sponsor2', 'main', 'league2');
|
||||
const sponsorship3 = createTestSeasonSponsorship('3', 'season1', 'sponsor3', 'main', 'league1');
|
||||
await repository.create(sponsorship1);
|
||||
await repository.create(sponsorship2);
|
||||
await repository.create(sponsorship3);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(sponsorship1);
|
||||
expect(result).toContain(sponsorship3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySponsorId', () => {
|
||||
it('should return sponsorships filtered by sponsor ID', async () => {
|
||||
const sponsorship1 = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
const sponsorship2 = createTestSeasonSponsorship('2', 'season2', 'sponsor2');
|
||||
const sponsorship3 = createTestSeasonSponsorship('3', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship1);
|
||||
await repository.create(sponsorship2);
|
||||
await repository.create(sponsorship3);
|
||||
|
||||
const result = await repository.findBySponsorId('sponsor1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(sponsorship1);
|
||||
expect(result).toContain(sponsorship3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySeasonAndTier', () => {
|
||||
it('should return sponsorships filtered by season ID and tier', async () => {
|
||||
const sponsorship1 = createTestSeasonSponsorship('1', 'season1', 'sponsor1', 'main');
|
||||
const sponsorship2 = createTestSeasonSponsorship('2', 'season1', 'sponsor2', 'secondary');
|
||||
const sponsorship3 = createTestSeasonSponsorship('3', 'season2', 'sponsor3', 'main');
|
||||
await repository.create(sponsorship1);
|
||||
await repository.create(sponsorship2);
|
||||
await repository.create(sponsorship3);
|
||||
|
||||
const result = await repository.findBySeasonAndTier('season1', 'main');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toContain(sponsorship1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new sponsorship', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
const result = await repository.create(sponsorship);
|
||||
expect(result).toEqual(sponsorship);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('SeasonSponsorship 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if sponsorship already exists', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship);
|
||||
await expect(repository.create(sponsorship)).rejects.toThrow('SeasonSponsorship with this ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing sponsorship', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship);
|
||||
|
||||
const updatedSponsorship = sponsorship.activate();
|
||||
await repository.update(updatedSponsorship);
|
||||
const found = await repository.findById('1');
|
||||
expect(found?.status).toBe('active');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('SeasonSponsorship 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if sponsorship does not exist', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await expect(repository.update(sponsorship)).rejects.toThrow('SeasonSponsorship not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing sponsorship', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('SeasonSponsorship 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw if sponsorship does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('SeasonSponsorship with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if sponsorship exists', async () => {
|
||||
const sponsorship = createTestSeasonSponsorship('1', 'season1', 'sponsor1');
|
||||
await repository.create(sponsorship);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if sponsorship does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* Mock repository for testing and development
|
||||
*/
|
||||
|
||||
import type { SeasonSponsorship, SponsorshipTier } from '@core/racing/domain/entities/SeasonSponsorship';
|
||||
import type { SeasonSponsorship, SponsorshipTier } from '@core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import type { ISeasonSponsorshipRepository } from '@core/racing/domain/repositories/ISeasonSponsorshipRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
@@ -12,12 +12,9 @@ export class InMemorySeasonSponsorshipRepository implements ISeasonSponsorshipRe
|
||||
private sponsorships: Map<string, SeasonSponsorship> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: SeasonSponsorship[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemorySeasonSponsorshipRepository initialized.');
|
||||
if (seedData) {
|
||||
this.seed(seedData);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<SeasonSponsorship | null> {
|
||||
@@ -81,7 +78,7 @@ export class InMemorySeasonSponsorshipRepository implements ISeasonSponsorshipRe
|
||||
this.logger.info(`Found ${sponsorships.length} season sponsorships for season id: ${seasonId}, tier: ${tier}.`);
|
||||
return sponsorships;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding season sponsorships by season id ${seasonId}, tier ${tier}:`, error);
|
||||
this.logger.error(`Error finding season sponsorships by season id ${seasonId}, tier ${tier}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -144,22 +141,6 @@ export class InMemorySeasonSponsorshipRepository implements ISeasonSponsorshipRe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed initial data
|
||||
*/
|
||||
seed(sponsorships: SeasonSponsorship[]): void {
|
||||
this.logger.debug(`Seeding ${sponsorships.length} season sponsorships.`);
|
||||
try {
|
||||
for (const sponsorship of sponsorships) {
|
||||
this.sponsorships.set(sponsorship.id, sponsorship);
|
||||
this.logger.debug(`Seeded season sponsorship: ${sponsorship.id}.`);
|
||||
}
|
||||
this.logger.info(`Successfully seeded ${sponsorships.length} season sponsorships.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error seeding season sponsorships:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Test helper
|
||||
clear(): void {
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySessionRepository } from './InMemorySessionRepository';
|
||||
import { Session, SessionStatus } from '@core/racing/domain/entities/Session';
|
||||
import { SessionType } from '@core/racing/domain/value-objects/SessionType';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySessionRepository', () => {
|
||||
let repository: InMemorySessionRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySessionRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestSession = (id: string, raceEventId: string, scheduledAt: Date, track: string, car: string, sessionType: SessionType, status?: SessionStatus) => {
|
||||
return Session.create({
|
||||
id,
|
||||
raceEventId,
|
||||
scheduledAt,
|
||||
track,
|
||||
car,
|
||||
sessionType,
|
||||
status: status || 'scheduled',
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySessionRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if session not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding session by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Session with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the session if found', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await repository.create(session);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(session);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found session: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all sessions', async () => {
|
||||
const session1 = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
const session2 = createTestSession('2', 'race2', new Date(), 'track2', 'car2', SessionType.qualifying());
|
||||
await repository.create(session1);
|
||||
await repository.create(session2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(session1);
|
||||
expect(result).toContain(session2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByRaceEventId', () => {
|
||||
it('should return sessions for the given race event id', async () => {
|
||||
const session1 = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
const session2 = createTestSession('2', 'race1', new Date(), 'track2', 'car2', SessionType.qualifying());
|
||||
const session3 = createTestSession('3', 'race2', new Date(), 'track3', 'car3', SessionType.main());
|
||||
await repository.create(session1);
|
||||
await repository.create(session2);
|
||||
await repository.create(session3);
|
||||
|
||||
const result = await repository.findByRaceEventId('race1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(session1);
|
||||
expect(result).toContain(session2);
|
||||
});
|
||||
|
||||
it('should return empty array if no sessions for race event id', async () => {
|
||||
const result = await repository.findByRaceEventId('nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return empty array as leagueId is not directly supported', async () => {
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByStatus', () => {
|
||||
it('should return sessions with the given status', async () => {
|
||||
const session1 = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice(), 'scheduled');
|
||||
const session2 = createTestSession('2', 'race2', new Date(), 'track2', 'car2', SessionType.qualifying(), 'running');
|
||||
await repository.create(session1);
|
||||
await repository.create(session2);
|
||||
|
||||
const result = await repository.findByStatus('scheduled');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toContain(session1);
|
||||
});
|
||||
|
||||
it('should return empty array if no sessions with status', async () => {
|
||||
const result = await repository.findByStatus('completed');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new session', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
const result = await repository.create(session);
|
||||
expect(result).toEqual(session);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Session 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if session already exists', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await repository.create(session);
|
||||
await expect(repository.create(session)).rejects.toThrow('Session with ID 1 already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing session', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await repository.create(session);
|
||||
|
||||
const updatedSession = session.start(); // Assuming start changes status
|
||||
const result = await repository.update(updatedSession);
|
||||
expect(result).toEqual(updatedSession);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Session 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if session does not exist', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await expect(repository.update(session)).rejects.toThrow('Session with ID 1 not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing session', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await repository.create(session);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Session 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if session does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Session with id nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if session exists', async () => {
|
||||
const session = createTestSession('1', 'race1', new Date(), 'track1', 'car1', SessionType.practice());
|
||||
await repository.create(session);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if session does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* In-memory implementation of ISessionRepository for development/testing.
|
||||
*/
|
||||
import type { ISessionRepository } from '../../domain/repositories/ISessionRepository';
|
||||
import type { Session } from '../../domain/entities/Session';
|
||||
import type { ISessionRepository } from '@core/racing/domain/repositories/ISessionRepository';
|
||||
import type { Session } from '@core/racing/domain/entities/Session';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemorySessionRepository implements ISessionRepository {
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorRepository } from './InMemorySponsorRepository';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySponsorRepository', () => {
|
||||
let repository: InMemorySponsorRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySponsorRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestSponsor = (id: string, name: string, contactEmail: string) => {
|
||||
return Sponsor.create({
|
||||
id,
|
||||
name,
|
||||
contactEmail,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySponsorRepository initialized.');
|
||||
});
|
||||
|
||||
it('should seed initial sponsors', () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
new InMemorySponsorRepository(mockLogger, [sponsor]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(`Seeded sponsor: 1 (Test Sponsor).`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if sponsor not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemorySponsorRepository] Finding sponsor by ID: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Sponsor with ID nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the sponsor if found', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(sponsor);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found sponsor by ID: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all sponsors', async () => {
|
||||
const sponsor1 = createTestSponsor('1', 'Sponsor 1', 's1@example.com');
|
||||
const sponsor2 = createTestSponsor('2', 'Sponsor 2', 's2@example.com');
|
||||
await repository.create(sponsor1);
|
||||
await repository.create(sponsor2);
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(sponsor1);
|
||||
expect(result).toContain(sponsor2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByEmail', () => {
|
||||
it('should return null if sponsor not found', async () => {
|
||||
const result = await repository.findByEmail('nonexistent@example.com');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('[InMemorySponsorRepository] Finding sponsor by email: nonexistent@example.com');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Sponsor with email nonexistent@example.com not found.');
|
||||
});
|
||||
|
||||
it('should return the sponsor if found', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
const result = await repository.findByEmail('TEST@EXAMPLE.COM');
|
||||
expect(result).toEqual(sponsor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new sponsor', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
const result = await repository.create(sponsor);
|
||||
expect(result).toEqual(sponsor);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsor 1 (Test Sponsor) created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if sponsor already exists', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
await expect(repository.create(sponsor)).rejects.toThrow('Sponsor already exists');
|
||||
});
|
||||
|
||||
it('should throw error if email already exists', async () => {
|
||||
const sponsor1 = createTestSponsor('1', 'Sponsor 1', 'test@example.com');
|
||||
const sponsor2 = createTestSponsor('2', 'Sponsor 2', 'test@example.com');
|
||||
await repository.create(sponsor1);
|
||||
await expect(repository.create(sponsor2)).rejects.toThrow('Sponsor with this email already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing sponsor', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
const updatedSponsor = sponsor.update({ name: 'Updated Sponsor' });
|
||||
const result = await repository.update(updatedSponsor);
|
||||
expect(result).toEqual(updatedSponsor);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsor 1 (Updated Sponsor) updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if sponsor does not exist', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await expect(repository.update(sponsor)).rejects.toThrow('Sponsor not found');
|
||||
});
|
||||
|
||||
it('should update email index when email changes', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
const updatedSponsor = sponsor.update({ contactEmail: 'new@example.com' });
|
||||
await repository.update(updatedSponsor);
|
||||
const found = await repository.findByEmail('new@example.com');
|
||||
expect(found).toEqual(updatedSponsor);
|
||||
});
|
||||
|
||||
it('should throw error if updating to existing email', async () => {
|
||||
const sponsor1 = createTestSponsor('1', 'Sponsor 1', 'test1@example.com');
|
||||
const sponsor2 = createTestSponsor('2', 'Sponsor 2', 'test2@example.com');
|
||||
await repository.create(sponsor1);
|
||||
await repository.create(sponsor2);
|
||||
const updatedSponsor1 = sponsor1.update({ contactEmail: 'test2@example.com' });
|
||||
await expect(repository.update(updatedSponsor1)).rejects.toThrow('Sponsor with this email already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing sponsor', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsor 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if sponsor does not exist', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Sponsor with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if sponsor exists', async () => {
|
||||
const sponsor = createTestSponsor('1', 'Test Sponsor', 'test@example.com');
|
||||
await repository.create(sponsor);
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if sponsor does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ISponsorRepository } from '@core/racing/domain/repositories/ISponsorRepository';
|
||||
import { Sponsor } from '@core/racing/domain/entities/Sponsor';
|
||||
import { Sponsor } from '@core/racing/domain/entities/sponsor/Sponsor';
|
||||
import { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemorySponsorRepository implements ISponsorRepository {
|
||||
@@ -9,8 +9,8 @@ export class InMemorySponsorRepository implements ISponsorRepository {
|
||||
constructor(private readonly logger: Logger, initialSponsors: Sponsor[] = []) {
|
||||
this.logger.info('InMemorySponsorRepository initialized.');
|
||||
for (const sponsor of initialSponsors) {
|
||||
this.sponsors.set(sponsor.id, sponsor);
|
||||
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
|
||||
this.sponsors.set(sponsor.id.toString(), sponsor);
|
||||
this.emailIndex.set(sponsor.contactEmail.toString().toLowerCase(), sponsor.id.toString());
|
||||
this.logger.debug(`Seeded sponsor: ${sponsor.id} (${sponsor.name}).`);
|
||||
}
|
||||
}
|
||||
@@ -43,37 +43,37 @@ export class InMemorySponsorRepository implements ISponsorRepository {
|
||||
|
||||
async create(sponsor: Sponsor): Promise<Sponsor> {
|
||||
this.logger.debug(`[InMemorySponsorRepository] Creating sponsor: ${sponsor.id} (${sponsor.name})`);
|
||||
if (this.sponsors.has(sponsor.id)) {
|
||||
if (this.sponsors.has(sponsor.id.toString())) {
|
||||
this.logger.warn(`Sponsor with ID ${sponsor.id} already exists.`);
|
||||
throw new Error('Sponsor already exists');
|
||||
}
|
||||
if (this.emailIndex.has(sponsor.contactEmail.toLowerCase())) {
|
||||
if (this.emailIndex.has(sponsor.contactEmail.toString().toLowerCase())) {
|
||||
this.logger.warn(`Sponsor with email ${sponsor.contactEmail} already exists.`);
|
||||
throw new Error('Sponsor with this email already exists');
|
||||
}
|
||||
this.sponsors.set(sponsor.id, sponsor);
|
||||
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
|
||||
this.sponsors.set(sponsor.id.toString(), sponsor);
|
||||
this.emailIndex.set(sponsor.contactEmail.toString().toLowerCase(), sponsor.id.toString());
|
||||
this.logger.info(`Sponsor ${sponsor.id} (${sponsor.name}) created successfully.`);
|
||||
return Promise.resolve(sponsor);
|
||||
}
|
||||
|
||||
async update(sponsor: Sponsor): Promise<Sponsor> {
|
||||
this.logger.debug(`[InMemorySponsorRepository] Updating sponsor: ${sponsor.id} (${sponsor.name})`);
|
||||
if (!this.sponsors.has(sponsor.id)) {
|
||||
if (!this.sponsors.has(sponsor.id.toString())) {
|
||||
this.logger.warn(`Sponsor with ID ${sponsor.id} not found for update.`);
|
||||
throw new Error('Sponsor not found');
|
||||
}
|
||||
const existingSponsor = this.sponsors.get(sponsor.id);
|
||||
const existingSponsor = this.sponsors.get(sponsor.id.toString());
|
||||
// If email changed, update index
|
||||
if (existingSponsor && existingSponsor.contactEmail.toLowerCase() !== sponsor.contactEmail.toLowerCase()) {
|
||||
if (this.emailIndex.has(sponsor.contactEmail.toLowerCase()) && this.emailIndex.get(sponsor.contactEmail.toLowerCase()) !== sponsor.id) {
|
||||
if (existingSponsor && existingSponsor.contactEmail.toString().toLowerCase() !== sponsor.contactEmail.toString().toLowerCase()) {
|
||||
if (this.emailIndex.has(sponsor.contactEmail.toString().toLowerCase()) && this.emailIndex.get(sponsor.contactEmail.toString().toLowerCase()) !== sponsor.id.toString()) {
|
||||
this.logger.warn(`Cannot update sponsor ${sponsor.id} to email ${sponsor.contactEmail} as it's already taken.`);
|
||||
throw new Error('Sponsor with this email already exists');
|
||||
}
|
||||
this.emailIndex.delete(existingSponsor.contactEmail.toLowerCase());
|
||||
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
|
||||
this.emailIndex.delete(existingSponsor.contactEmail.toString().toLowerCase());
|
||||
this.emailIndex.set(sponsor.contactEmail.toString().toLowerCase(), sponsor.id.toString());
|
||||
}
|
||||
this.sponsors.set(sponsor.id, sponsor);
|
||||
this.sponsors.set(sponsor.id.toString(), sponsor);
|
||||
this.logger.info(`Sponsor ${sponsor.id} (${sponsor.name}) updated successfully.`);
|
||||
return Promise.resolve(sponsor);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export class InMemorySponsorRepository implements ISponsorRepository {
|
||||
const sponsor = this.sponsors.get(id);
|
||||
if (sponsor) {
|
||||
this.sponsors.delete(id);
|
||||
this.emailIndex.delete(sponsor.contactEmail.toLowerCase());
|
||||
this.emailIndex.delete(sponsor.contactEmail.toString().toLowerCase());
|
||||
this.logger.info(`Sponsor ${id} deleted successfully.`);
|
||||
} else {
|
||||
this.logger.warn(`Sponsor with ID ${id} not found for deletion.`);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorshipPricingRepository } from './InMemorySponsorshipPricingRepository';
|
||||
import { SponsorshipPricing } from '@core/racing/domain/value-objects/SponsorshipPricing';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySponsorshipPricingRepository', () => {
|
||||
let repository: InMemorySponsorshipPricingRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySponsorshipPricingRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestPricing = (acceptingApplications: boolean = true) => {
|
||||
return SponsorshipPricing.create({ acceptingApplications });
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySponsorshipPricingRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByEntity', () => {
|
||||
it('should return null if pricing not found', async () => {
|
||||
const result = await repository.findByEntity('team', 'nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding sponsorship pricing for entity: team, nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Sponsorship pricing for entity team, nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the pricing if found', async () => {
|
||||
const pricing = createTestPricing();
|
||||
await repository.save('team', '1', pricing);
|
||||
const result = await repository.findByEntity('team', '1');
|
||||
expect(result).toEqual(pricing);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found sponsorship pricing for entity: team, 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should save new pricing', async () => {
|
||||
const pricing = createTestPricing();
|
||||
await repository.save('team', '1', pricing);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Creating new sponsorship pricing for entity: team, 1.');
|
||||
const found = await repository.findByEntity('team', '1');
|
||||
expect(found).toEqual(pricing);
|
||||
});
|
||||
|
||||
it('should update existing pricing', async () => {
|
||||
const pricing1 = createTestPricing(true);
|
||||
const pricing2 = createTestPricing(false);
|
||||
await repository.save('team', '1', pricing1);
|
||||
await repository.save('team', '1', pricing2);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Updating existing sponsorship pricing for entity: team, 1.');
|
||||
const found = await repository.findByEntity('team', '1');
|
||||
expect(found).toEqual(pricing2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete existing pricing', async () => {
|
||||
const pricing = createTestPricing();
|
||||
await repository.save('team', '1', pricing);
|
||||
await repository.delete('team', '1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship pricing deleted for entity: team, 1.');
|
||||
const found = await repository.findByEntity('team', '1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw error if pricing does not exist', async () => {
|
||||
await repository.delete('team', 'nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Sponsorship pricing for entity team, nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if pricing exists', async () => {
|
||||
const pricing = createTestPricing();
|
||||
await repository.save('team', '1', pricing);
|
||||
const result = await repository.exists('team', '1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if pricing does not exist', async () => {
|
||||
const result = await repository.exists('team', 'nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAcceptingApplications', () => {
|
||||
it('should return entities accepting applications', async () => {
|
||||
const accepting = createTestPricing(true);
|
||||
const notAccepting = createTestPricing(false);
|
||||
await repository.save('team', '1', accepting);
|
||||
await repository.save('team', '2', notAccepting);
|
||||
await repository.save('driver', '3', accepting);
|
||||
const result = await repository.findAcceptingApplications('team');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({ entityId: '1', pricing: accepting });
|
||||
});
|
||||
|
||||
it('should return empty array if no entities accepting', async () => {
|
||||
const notAccepting = createTestPricing(false);
|
||||
await repository.save('team', '1', notAccepting);
|
||||
const result = await repository.findAcceptingApplications('team');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,21 +7,13 @@ import { SponsorshipPricing } from '@core/racing/domain/value-objects/Sponsorshi
|
||||
import type { SponsorableEntityType } from '@core/racing/domain/entities/SponsorshipRequest';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
interface StorageKey {
|
||||
entityType: SponsorableEntityType;
|
||||
entityId: string;
|
||||
}
|
||||
|
||||
export class InMemorySponsorshipPricingRepository implements ISponsorshipPricingRepository {
|
||||
private pricings: Map<string, { entityType: SponsorableEntityType; entityId: string; pricing: SponsorshipPricing }> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Array<{ entityType: SponsorableEntityType; entityId: string; pricing: SponsorshipPricing }>) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemorySponsorshipPricingRepository initialized.');
|
||||
if (seedData) {
|
||||
this.seed(seedData);
|
||||
}
|
||||
}
|
||||
|
||||
private makeKey(entityType: SponsorableEntityType, entityId: string): string {
|
||||
@@ -41,7 +33,7 @@ export class InMemorySponsorshipPricingRepository implements ISponsorshipPricing
|
||||
}
|
||||
return pricing;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error finding sponsorship pricing for entity ${entityType}, ${entityId}:`, error);
|
||||
this.logger.error(`Error finding sponsorship pricing for entity ${entityType}, ${entityId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +50,7 @@ export class InMemorySponsorshipPricingRepository implements ISponsorshipPricing
|
||||
this.pricings.set(key, { entityType, entityId, pricing });
|
||||
this.logger.info(`Sponsorship pricing saved for entity: ${entityType}, ${entityId}.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error saving sponsorship pricing for entity ${entityType}, ${entityId}:`, error);
|
||||
this.logger.error(`Error saving sponsorship pricing for entity ${entityType}, ${entityId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +65,7 @@ export class InMemorySponsorshipPricingRepository implements ISponsorshipPricing
|
||||
this.logger.warn(`Sponsorship pricing for entity ${entityType}, ${entityId} not found for deletion.`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting sponsorship pricing for entity ${entityType}, ${entityId}:`, error);
|
||||
this.logger.error(`Error deleting sponsorship pricing for entity ${entityType}, ${entityId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +78,7 @@ export class InMemorySponsorshipPricingRepository implements ISponsorshipPricing
|
||||
this.logger.debug(`Sponsorship pricing for entity ${entityType}, ${entityId} exists: ${exists}.`);
|
||||
return exists;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error checking existence of sponsorship pricing for entity ${entityType}, ${entityId}:`, error);
|
||||
this.logger.error(`Error checking existence of sponsorship pricing for entity ${entityType}, ${entityId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -107,31 +99,4 @@ export class InMemorySponsorshipPricingRepository implements ISponsorshipPricing
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed initial data
|
||||
*/
|
||||
seed(data: Array<{ entityType: SponsorableEntityType; entityId: string; pricing: SponsorshipPricing }>): void {
|
||||
this.logger.debug(`Seeding ${data.length} sponsorship pricing entries.`);
|
||||
try {
|
||||
for (const entry of data) {
|
||||
const key = this.makeKey(entry.entityType, entry.entityId);
|
||||
this.pricings.set(key, entry);
|
||||
this.logger.debug(`Seeded pricing for entity ${entry.entityType}, ${entry.entityId}.`);
|
||||
}
|
||||
this.logger.info(`Successfully seeded ${data.length} sponsorship pricing entries.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error seeding sponsorship pricing data:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data (for testing)
|
||||
*/
|
||||
clear(): void {
|
||||
this.logger.debug('Clearing all sponsorship pricing data.');
|
||||
this.pricings.clear();
|
||||
this.logger.info('All sponsorship pricing data cleared.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemorySponsorshipRequestRepository } from './InMemorySponsorshipRequestRepository';
|
||||
import { SponsorshipRequest, SponsorableEntityType, SponsorshipRequestStatus } from '@core/racing/domain/entities/SponsorshipRequest';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
import { SponsorshipTier } from '@core/racing/domain/entities/season/SeasonSponsorship';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemorySponsorshipRequestRepository', () => {
|
||||
let repository: InMemorySponsorshipRequestRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemorySponsorshipRequestRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestRequest = (
|
||||
id: string,
|
||||
sponsorId: string = 'sponsor-1',
|
||||
entityType: SponsorableEntityType = 'team',
|
||||
entityId: string = 'entity-1',
|
||||
status: SponsorshipRequestStatus = 'pending',
|
||||
tier: SponsorshipTier = 'main',
|
||||
offeredAmount: Money = Money.create(1000, 'USD')
|
||||
): SponsorshipRequest => {
|
||||
return SponsorshipRequest.create({
|
||||
id,
|
||||
sponsorId,
|
||||
entityType,
|
||||
entityId,
|
||||
tier,
|
||||
offeredAmount,
|
||||
status,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemorySponsorshipRequestRepository initialized.');
|
||||
});
|
||||
|
||||
it('should seed initial requests', () => {
|
||||
const request = createTestRequest('req-1');
|
||||
const repoWithSeed = new InMemorySponsorshipRequestRepository(mockLogger, [request]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Seeded sponsorship request: req-1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return the request if found', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await repository.create(request);
|
||||
const result = await repository.findById('req-1');
|
||||
expect(result).toEqual(request);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found request by ID: req-1.');
|
||||
});
|
||||
|
||||
it('should return null if not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID nonexistent not found.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByEntity', () => {
|
||||
it('should return requests for the entity', async () => {
|
||||
const request1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1');
|
||||
const request2 = createTestRequest('req-2', 'sponsor-2', 'team', 'entity-1');
|
||||
const request3 = createTestRequest('req-3', 'sponsor-1', 'driver', 'entity-2');
|
||||
await repository.create(request1);
|
||||
await repository.create(request2);
|
||||
await repository.create(request3);
|
||||
const result = await repository.findByEntity('team', 'entity-1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(request1);
|
||||
expect(result).toContain(request2);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 requests for entity team:entity-1.');
|
||||
});
|
||||
|
||||
it('should return empty array if no requests', async () => {
|
||||
const result = await repository.findByEntity('team', 'nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 requests for entity team:nonexistent.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPendingByEntity', () => {
|
||||
it('should return pending requests for the entity', async () => {
|
||||
const pending = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
|
||||
const accepted = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
|
||||
await repository.create(pending);
|
||||
await repository.create(accepted);
|
||||
const result = await repository.findPendingByEntity('team', 'entity-1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(pending);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 pending requests for entity team:entity-1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySponsorId', () => {
|
||||
it('should return requests by sponsor', async () => {
|
||||
const request1 = createTestRequest('req-1', 'sponsor-1');
|
||||
const request2 = createTestRequest('req-2', 'sponsor-1');
|
||||
const request3 = createTestRequest('req-3', 'sponsor-2');
|
||||
await repository.create(request1);
|
||||
await repository.create(request2);
|
||||
await repository.create(request3);
|
||||
const result = await repository.findBySponsorId('sponsor-1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(request1);
|
||||
expect(result).toContain(request2);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 requests by sponsor ID: sponsor-1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByStatus', () => {
|
||||
it('should return requests by status', async () => {
|
||||
const pending = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
|
||||
const accepted = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
|
||||
await repository.create(pending);
|
||||
await repository.create(accepted);
|
||||
const result = await repository.findByStatus('pending');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(pending);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 requests with status: pending.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBySponsorIdAndStatus', () => {
|
||||
it('should return requests by sponsor and status', async () => {
|
||||
const pending1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
|
||||
const accepted1 = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
|
||||
const pending2 = createTestRequest('req-3', 'sponsor-2', 'team', 'entity-1', 'pending');
|
||||
await repository.create(pending1);
|
||||
await repository.create(accepted1);
|
||||
await repository.create(pending2);
|
||||
const result = await repository.findBySponsorIdAndStatus('sponsor-1', 'pending');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(pending1);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 requests by sponsor ID sponsor-1 and status pending.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPendingRequest', () => {
|
||||
it('should return true if pending request exists', async () => {
|
||||
const request = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
|
||||
await repository.create(request);
|
||||
const result = await repository.hasPendingRequest('sponsor-1', 'team', 'entity-1');
|
||||
expect(result).toBe(true);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Pending request for sponsor sponsor-1, entity team:entity-1 exists: true.');
|
||||
});
|
||||
|
||||
it('should return false if no pending request', async () => {
|
||||
const result = await repository.hasPendingRequest('sponsor-1', 'team', 'entity-1');
|
||||
expect(result).toBe(false);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Pending request for sponsor sponsor-1, entity team:entity-1 exists: false.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('countPendingByEntity', () => {
|
||||
it('should count pending requests for entity', async () => {
|
||||
const request1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
|
||||
const request2 = createTestRequest('req-2', 'sponsor-2', 'team', 'entity-1', 'pending');
|
||||
const accepted = createTestRequest('req-3', 'sponsor-3', 'team', 'entity-1', 'accepted');
|
||||
await repository.create(request1);
|
||||
await repository.create(request2);
|
||||
await repository.create(accepted);
|
||||
const result = await repository.countPendingByEntity('team', 'entity-1');
|
||||
expect(result).toBe(2);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Count of pending requests for entity team:entity-1: 2.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new request', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
const result = await repository.create(request);
|
||||
expect(result).toEqual(request);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw if request already exists', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await repository.create(request);
|
||||
await expect(repository.create(request)).rejects.toThrow('Sponsorship request already exists');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID req-1 already exists.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update existing request', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await repository.create(request);
|
||||
const updated = request.accept('responder-1');
|
||||
const result = await repository.update(updated);
|
||||
expect(result).toEqual(updated);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw if request not found', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await expect(repository.update(request)).rejects.toThrow('Sponsorship request not found');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID req-1 not found for update.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete existing request', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await repository.create(request);
|
||||
await repository.delete('req-1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 deleted successfully.');
|
||||
const found = await repository.findById('req-1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should not throw if request not found', async () => {
|
||||
await repository.delete('nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if exists', async () => {
|
||||
const request = createTestRequest('req-1');
|
||||
await repository.create(request);
|
||||
const result = await repository.exists('req-1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if not exists', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,296 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryStandingRepository } from './InMemoryStandingRepository';
|
||||
import { Standing } from '@core/racing/domain/entities/Standing';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('InMemoryStandingRepository', () => {
|
||||
let repository: InMemoryStandingRepository;
|
||||
let mockLogger: Logger;
|
||||
let mockResultRepository: any;
|
||||
let mockRaceRepository: any;
|
||||
let mockLeagueRepository: any;
|
||||
const testPointsSystems: Record<string, Record<number, number>> = {
|
||||
'f1-2024': {
|
||||
1: 25, 2: 18, 3: 15, 4: 12, 5: 10,
|
||||
6: 8, 7: 6, 8: 4, 9: 2, 10: 1
|
||||
},
|
||||
'indycar': {
|
||||
1: 50, 2: 40, 3: 35, 4: 32, 5: 30,
|
||||
6: 28, 7: 26, 8: 24, 9: 22, 10: 20,
|
||||
11: 19, 12: 18, 13: 17, 14: 16, 15: 15
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockResultRepository = {
|
||||
findById: vi.fn(),
|
||||
findAll: vi.fn(),
|
||||
findByRaceId: vi.fn(),
|
||||
findByDriverId: vi.fn(),
|
||||
findByDriverIdAndLeagueId: vi.fn(),
|
||||
create: vi.fn(),
|
||||
createMany: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
deleteByRaceId: vi.fn(),
|
||||
exists: vi.fn(),
|
||||
existsByRaceId: vi.fn(),
|
||||
} as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockRaceRepository = {
|
||||
findById: vi.fn(),
|
||||
findAll: vi.fn(),
|
||||
findByLeagueId: vi.fn(),
|
||||
findUpcomingByLeagueId: vi.fn(),
|
||||
findCompletedByLeagueId: vi.fn(),
|
||||
findByStatus: vi.fn(),
|
||||
findByDateRange: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
exists: vi.fn(),
|
||||
} as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockLeagueRepository = {
|
||||
findById: vi.fn(),
|
||||
findByOwnerId: vi.fn(),
|
||||
searchByName: vi.fn(),
|
||||
findAll: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
exists: vi.fn(),
|
||||
} as any;
|
||||
repository = new InMemoryStandingRepository(
|
||||
mockLogger,
|
||||
testPointsSystems,
|
||||
mockResultRepository,
|
||||
mockRaceRepository,
|
||||
mockLeagueRepository
|
||||
);
|
||||
});
|
||||
|
||||
const createTestStanding = (leagueId: string, driverId: string, position: number = 1, points: number = 0) => {
|
||||
return Standing.create({
|
||||
leagueId,
|
||||
driverId,
|
||||
position,
|
||||
points,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with required parameters', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryStandingRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return standings for the specified league', async () => {
|
||||
const standing1 = createTestStanding('league1', 'driver1', 1, 25);
|
||||
const standing2 = createTestStanding('league1', 'driver2', 2, 18);
|
||||
const standing3 = createTestStanding('league2', 'driver3', 1, 50);
|
||||
await repository.save(standing1);
|
||||
await repository.save(standing2);
|
||||
await repository.save(standing3);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual(standing1);
|
||||
expect(result[1]).toEqual(standing2);
|
||||
});
|
||||
|
||||
it('should return empty array if no standings for league', async () => {
|
||||
const result = await repository.findByLeagueId('nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should sort standings by position then points', async () => {
|
||||
const standing1 = createTestStanding('league1', 'driver1', 2, 18);
|
||||
const standing2 = createTestStanding('league1', 'driver2', 1, 25);
|
||||
await repository.save(standing1);
|
||||
await repository.save(standing2);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result[0]).toEqual(standing2); // position 1
|
||||
expect(result[1]).toEqual(standing1); // position 2
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByDriverIdAndLeagueId', () => {
|
||||
it('should return the standing if found', async () => {
|
||||
const standing = createTestStanding('league1', 'driver1');
|
||||
await repository.save(standing);
|
||||
|
||||
const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
|
||||
expect(result).toEqual(standing);
|
||||
});
|
||||
|
||||
it('should return null if not found', async () => {
|
||||
const result = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all standings', async () => {
|
||||
const standing1 = createTestStanding('league1', 'driver1');
|
||||
const standing2 = createTestStanding('league2', 'driver2');
|
||||
await repository.save(standing1);
|
||||
await repository.save(standing2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(standing1);
|
||||
expect(result).toContain(standing2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', () => {
|
||||
it('should save a new standing', async () => {
|
||||
const standing = createTestStanding('league1', 'driver1');
|
||||
const result = await repository.save(standing);
|
||||
expect(result).toEqual(standing);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 saved successfully.');
|
||||
});
|
||||
|
||||
it('should update existing standing', async () => {
|
||||
const standing = createTestStanding('league1', 'driver1', 1, 25);
|
||||
await repository.save(standing);
|
||||
const updatedStanding = createTestStanding('league1', 'driver1', 1, 35);
|
||||
const result = await repository.save(updatedStanding);
|
||||
expect(result).toEqual(updatedStanding);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveMany', () => {
|
||||
it('should save multiple standings', async () => {
|
||||
const standings = [
|
||||
createTestStanding('league1', 'driver1'),
|
||||
createTestStanding('league1', 'driver2'),
|
||||
];
|
||||
const result = await repository.saveMany(standings);
|
||||
expect(result).toEqual(standings);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('2 standings saved successfully.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete existing standing', async () => {
|
||||
const standing = createTestStanding('league1', 'driver1');
|
||||
await repository.save(standing);
|
||||
await repository.delete('league1', 'driver1');
|
||||
const found = await repository.findByDriverIdAndLeagueId('driver1', 'league1');
|
||||
expect(found).toBeNull();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Standing for league league1, driver driver1 deleted successfully.');
|
||||
});
|
||||
|
||||
it('should log warning if standing not found', async () => {
|
||||
await repository.delete('league1', 'driver1');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Standing for league league1, driver driver1 not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteByLeagueId', () => {
|
||||
it('should delete all standings for a league', async () => {
|
||||
const standing1 = createTestStanding('league1', 'driver1');
|
||||
const standing2 = createTestStanding('league1', 'driver2');
|
||||
const standing3 = createTestStanding('league2', 'driver3');
|
||||
await repository.save(standing1);
|
||||
await repository.save(standing2);
|
||||
await repository.save(standing3);
|
||||
|
||||
await repository.deleteByLeagueId('league1');
|
||||
const league1Standings = await repository.findByLeagueId('league1');
|
||||
const league2Standings = await repository.findByLeagueId('league2');
|
||||
expect(league1Standings).toHaveLength(0);
|
||||
expect(league2Standings).toHaveLength(1);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Deleted 2 standings for league id: league1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if standing exists', async () => {
|
||||
const standing = createTestStanding('league1', 'driver1');
|
||||
await repository.save(standing);
|
||||
const result = await repository.exists('league1', 'driver1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if standing does not exist', async () => {
|
||||
const result = await repository.exists('league1', 'driver1');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recalculate', () => {
|
||||
it('should throw error if repositories are missing', async () => {
|
||||
const repoWithoutDeps = new InMemoryStandingRepository(mockLogger, testPointsSystems);
|
||||
await expect(repoWithoutDeps.recalculate('league1')).rejects.toThrow('Cannot recalculate standings: missing required repositories');
|
||||
});
|
||||
|
||||
it('should throw error if league not found', async () => {
|
||||
mockLeagueRepository.findById.mockResolvedValue(null);
|
||||
await expect(repository.recalculate('nonexistent')).rejects.toThrow('League with ID nonexistent not found');
|
||||
});
|
||||
|
||||
it('should return empty array if no completed races', async () => {
|
||||
const mockLeague = {
|
||||
id: 'league1',
|
||||
settings: { pointsSystem: 'f1-2024', customPoints: null },
|
||||
};
|
||||
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
|
||||
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([]);
|
||||
const result = await repository.recalculate('league1');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should recalculate standings based on race results', async () => {
|
||||
const mockLeague = {
|
||||
id: 'league1',
|
||||
settings: { pointsSystem: 'f1-2024', customPoints: null },
|
||||
};
|
||||
const mockRace = { id: 'race1' };
|
||||
const mockResult1 = { driverId: 'driver1', position: 1 };
|
||||
const mockResult2 = { driverId: 'driver2', position: 2 };
|
||||
|
||||
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
|
||||
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
|
||||
mockResultRepository.findByRaceId.mockResolvedValue([mockResult1, mockResult2]);
|
||||
|
||||
const result = await repository.recalculate('league1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]?.driverId.toString()).toBe('driver1');
|
||||
expect(result[0]?.points.toNumber()).toBe(25); // 1st place in f1-2024
|
||||
expect(result[1]?.driverId.toString()).toBe('driver2');
|
||||
expect(result[1]?.points.toNumber()).toBe(18); // 2nd place
|
||||
});
|
||||
|
||||
it('should use custom points if available', async () => {
|
||||
const customPoints = { 1: 100, 2: 80 };
|
||||
const mockLeague = {
|
||||
id: 'league1',
|
||||
settings: { pointsSystem: 'f1-2024', customPoints },
|
||||
};
|
||||
const mockRace = { id: 'race1' };
|
||||
const mockResult = { driverId: 'driver1', position: 1 };
|
||||
|
||||
mockLeagueRepository.findById.mockResolvedValue(mockLeague);
|
||||
mockRaceRepository.findCompletedByLeagueId.mockResolvedValue([mockRace]);
|
||||
mockResultRepository.findByRaceId.mockResolvedValue([mockResult]);
|
||||
|
||||
const result = await repository.recalculate('league1');
|
||||
expect(result[0]?.points.toNumber()).toBe(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,49 +12,28 @@ import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepo
|
||||
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
/**
|
||||
* Points systems presets
|
||||
*/
|
||||
const POINTS_SYSTEMS: Record<string, Record<number, number>> = {
|
||||
'f1-2024': {
|
||||
1: 25, 2: 18, 3: 15, 4: 12, 5: 10,
|
||||
6: 8, 7: 6, 8: 4, 9: 2, 10: 1
|
||||
},
|
||||
'indycar': {
|
||||
1: 50, 2: 40, 3: 35, 4: 32, 5: 30,
|
||||
6: 28, 7: 26, 8: 24, 9: 22, 10: 20,
|
||||
11: 19, 12: 18, 13: 17, 14: 16, 15: 15
|
||||
}
|
||||
};
|
||||
|
||||
export class InMemoryStandingRepository implements IStandingRepository {
|
||||
private standings: Map<string, Standing>;
|
||||
private resultRepository: IResultRepository | null;
|
||||
private raceRepository: IRaceRepository | null;
|
||||
private leagueRepository: ILeagueRepository | null;
|
||||
private readonly logger: Logger;
|
||||
private readonly pointsSystems: Record<string, Record<number, number>>;
|
||||
|
||||
constructor(
|
||||
logger: Logger,
|
||||
seedData?: Standing[],
|
||||
pointsSystems: Record<string, Record<number, number>>,
|
||||
resultRepository?: IResultRepository | null,
|
||||
raceRepository?: IRaceRepository | null,
|
||||
leagueRepository?: ILeagueRepository | null
|
||||
) {
|
||||
this.logger = logger;
|
||||
this.pointsSystems = pointsSystems;
|
||||
this.logger.info('InMemoryStandingRepository initialized.');
|
||||
this.standings = new Map();
|
||||
this.resultRepository = resultRepository ?? null;
|
||||
this.raceRepository = raceRepository ?? null;
|
||||
this.leagueRepository = leagueRepository ?? null;
|
||||
|
||||
if (seedData) {
|
||||
seedData.forEach(standing => {
|
||||
const key = this.getKey(standing.leagueId, standing.driverId);
|
||||
this.standings.set(key, standing);
|
||||
this.logger.debug(`Seeded standing for league ${standing.leagueId}, driver ${standing.driverId}.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getKey(leagueId: string, driverId: string): string {
|
||||
@@ -65,14 +44,14 @@ export class InMemoryStandingRepository implements IStandingRepository {
|
||||
this.logger.debug(`Finding standings for league id: ${leagueId}`);
|
||||
try {
|
||||
const standings = Array.from(this.standings.values())
|
||||
.filter(standing => standing.leagueId === leagueId)
|
||||
.filter(standing => standing.leagueId.toString() === leagueId)
|
||||
.sort((a, b) => {
|
||||
// Sort by position (lower is better)
|
||||
if (a.position !== b.position) {
|
||||
return a.position - b.position;
|
||||
if (a.position.toNumber() !== b.position.toNumber()) {
|
||||
return a.position.toNumber() - b.position.toNumber();
|
||||
}
|
||||
// If positions are equal, sort by points (higher is better)
|
||||
return b.points - a.points;
|
||||
return b.points.toNumber() - a.points.toNumber();
|
||||
});
|
||||
this.logger.info(`Found ${standings.length} standings for league id: ${leagueId}.`);
|
||||
return standings;
|
||||
@@ -162,12 +141,11 @@ export class InMemoryStandingRepository implements IStandingRepository {
|
||||
async deleteByLeagueId(leagueId: string): Promise<void> {
|
||||
this.logger.debug(`Deleting all standings for league id: ${leagueId}`);
|
||||
try {
|
||||
const initialCount = Array.from(this.standings.values()).filter(s => s.leagueId === leagueId).length;
|
||||
const toDelete = Array.from(this.standings.values())
|
||||
.filter(standing => standing.leagueId === leagueId);
|
||||
|
||||
.filter(standing => standing.leagueId.toString() === leagueId);
|
||||
|
||||
toDelete.forEach(standing => {
|
||||
const key = this.getKey(standing.leagueId, standing.driverId);
|
||||
const key = this.getKey(standing.leagueId.toString(), standing.driverId.toString());
|
||||
this.standings.delete(key);
|
||||
});
|
||||
this.logger.info(`Deleted ${toDelete.length} standings for league id: ${leagueId}.`);
|
||||
@@ -209,8 +187,8 @@ export class InMemoryStandingRepository implements IStandingRepository {
|
||||
// Get points system
|
||||
const resolvedPointsSystem =
|
||||
league.settings.customPoints ??
|
||||
POINTS_SYSTEMS[league.settings.pointsSystem] ??
|
||||
POINTS_SYSTEMS['f1-2024'];
|
||||
this.pointsSystems[league.settings.pointsSystem] ??
|
||||
this.pointsSystems['f1-2024'];
|
||||
|
||||
if (!resolvedPointsSystem) {
|
||||
this.logger.error(`No points system configured for league ${leagueId}.`);
|
||||
@@ -292,11 +270,4 @@ export class InMemoryStandingRepository implements IStandingRepository {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available points systems
|
||||
*/
|
||||
static getPointsSystems(): Record<string, Record<number, number>> {
|
||||
return POINTS_SYSTEMS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryTeamMembershipRepository } from './InMemoryTeamMembershipRepository';
|
||||
import type { TeamMembership, TeamJoinRequest } from '@core/racing/domain/types/TeamMembership';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
describe('InMemoryTeamMembershipRepository', () => {
|
||||
let repository: InMemoryTeamMembershipRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryTeamMembershipRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestMembership = (
|
||||
teamId: string,
|
||||
driverId: string,
|
||||
role: 'owner' | 'manager' | 'driver' = 'driver',
|
||||
status: 'active' | 'pending' | 'none' = 'active',
|
||||
joinedAt: Date = new Date()
|
||||
): TeamMembership => ({
|
||||
teamId,
|
||||
driverId,
|
||||
role,
|
||||
status,
|
||||
joinedAt,
|
||||
});
|
||||
|
||||
const createTestJoinRequest = (
|
||||
id: string,
|
||||
teamId: string,
|
||||
driverId: string,
|
||||
requestedAt: Date = new Date(),
|
||||
message?: string
|
||||
): TeamJoinRequest => ({
|
||||
id,
|
||||
teamId,
|
||||
driverId,
|
||||
requestedAt,
|
||||
...(message !== undefined ? { message } : {}),
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryTeamMembershipRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMembership', () => {
|
||||
it('should return membership if found', async () => {
|
||||
const membership = createTestMembership('team1', 'driver1');
|
||||
await repository.saveMembership(membership);
|
||||
|
||||
const result = await repository.getMembership('team1', 'driver1');
|
||||
expect(result).toEqual(membership);
|
||||
});
|
||||
|
||||
it('should return null if membership not found', async () => {
|
||||
const result = await repository.getMembership('team1', 'driver1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if team has no memberships', async () => {
|
||||
const result = await repository.getMembership('nonexistent', 'driver1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActiveMembershipForDriver', () => {
|
||||
it('should return active membership for driver', async () => {
|
||||
const activeMembership = createTestMembership('team1', 'driver1', 'driver', 'active');
|
||||
const pendingMembership = createTestMembership('team2', 'driver1', 'driver', 'pending');
|
||||
await repository.saveMembership(activeMembership);
|
||||
await repository.saveMembership(pendingMembership);
|
||||
|
||||
const result = await repository.getActiveMembershipForDriver('driver1');
|
||||
expect(result).toEqual(activeMembership);
|
||||
});
|
||||
|
||||
it('should return null if no active membership', async () => {
|
||||
const pendingMembership = createTestMembership('team1', 'driver1', 'driver', 'pending');
|
||||
await repository.saveMembership(pendingMembership);
|
||||
|
||||
const result = await repository.getActiveMembershipForDriver('driver1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null if driver has no memberships', async () => {
|
||||
const result = await repository.getActiveMembershipForDriver('driver1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTeamMembers', () => {
|
||||
it('should return all members for team', async () => {
|
||||
const member1 = createTestMembership('team1', 'driver1');
|
||||
const member2 = createTestMembership('team1', 'driver2');
|
||||
const member3 = createTestMembership('team2', 'driver3');
|
||||
await repository.saveMembership(member1);
|
||||
await repository.saveMembership(member2);
|
||||
await repository.saveMembership(member3);
|
||||
|
||||
const result = await repository.getTeamMembers('team1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(member1);
|
||||
expect(result).toContain(member2);
|
||||
});
|
||||
|
||||
it('should return empty array if team has no members', async () => {
|
||||
const result = await repository.getTeamMembers('team1');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('countByTeamId', () => {
|
||||
it('should count active members for team', async () => {
|
||||
const active1 = createTestMembership('team1', 'driver1', 'driver', 'active');
|
||||
const active2 = createTestMembership('team1', 'driver2', 'driver', 'active');
|
||||
const pending = createTestMembership('team1', 'driver3', 'driver', 'pending');
|
||||
await repository.saveMembership(active1);
|
||||
await repository.saveMembership(active2);
|
||||
await repository.saveMembership(pending);
|
||||
|
||||
const result = await repository.countByTeamId('team1');
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
|
||||
it('should return 0 if team has no active members', async () => {
|
||||
const pending = createTestMembership('team1', 'driver1', 'driver', 'pending');
|
||||
await repository.saveMembership(pending);
|
||||
|
||||
const result = await repository.countByTeamId('team1');
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 if team has no members', async () => {
|
||||
const result = await repository.countByTeamId('team1');
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveMembership', () => {
|
||||
it('should save new membership', async () => {
|
||||
const membership = createTestMembership('team1', 'driver1');
|
||||
const result = await repository.saveMembership(membership);
|
||||
expect(result).toEqual(membership);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[saveMembership] Success - created new membership for team: team1, driver: driver1.');
|
||||
});
|
||||
|
||||
it('should update existing membership', async () => {
|
||||
const membership = createTestMembership('team1', 'driver1', 'driver', 'active');
|
||||
await repository.saveMembership(membership);
|
||||
const updated = createTestMembership('team1', 'driver1', 'manager', 'active');
|
||||
const result = await repository.saveMembership(updated);
|
||||
expect(result).toEqual(updated);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[saveMembership] Success - updated existing membership for team: team1, driver: driver1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeMembership', () => {
|
||||
it('should remove existing membership', async () => {
|
||||
const membership = createTestMembership('team1', 'driver1');
|
||||
await repository.saveMembership(membership);
|
||||
await repository.removeMembership('team1', 'driver1');
|
||||
const found = await repository.getMembership('team1', 'driver1');
|
||||
expect(found).toBeNull();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[removeMembership] Success - removed membership for team: team1, driver: driver1.');
|
||||
});
|
||||
|
||||
it('should log warning if membership not found', async () => {
|
||||
await repository.removeMembership('team1', 'driver1');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('[removeMembership] No membership list found for team: team1. Cannot remove.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getJoinRequests', () => {
|
||||
it('should return join requests for team', async () => {
|
||||
const request1 = createTestJoinRequest('req1', 'team1', 'driver1');
|
||||
const request2 = createTestJoinRequest('req2', 'team1', 'driver2');
|
||||
const request3 = createTestJoinRequest('req3', 'team2', 'driver3');
|
||||
await repository.saveJoinRequest(request1);
|
||||
await repository.saveJoinRequest(request2);
|
||||
await repository.saveJoinRequest(request3);
|
||||
|
||||
const result = await repository.getJoinRequests('team1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toContain(request1);
|
||||
expect(result).toContain(request2);
|
||||
});
|
||||
|
||||
it('should return empty array if team has no requests', async () => {
|
||||
const result = await repository.getJoinRequests('team1');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveJoinRequest', () => {
|
||||
it('should save new join request', async () => {
|
||||
const request = createTestJoinRequest('req1', 'team1', 'driver1');
|
||||
const result = await repository.saveJoinRequest(request);
|
||||
expect(result).toEqual(request);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[saveJoinRequest] Success - created new join request: req1.');
|
||||
});
|
||||
|
||||
it('should update existing join request', async () => {
|
||||
const request = createTestJoinRequest('req1', 'team1', 'driver1');
|
||||
await repository.saveJoinRequest(request);
|
||||
const updated = createTestJoinRequest('req1', 'team1', 'driver1', new Date(), 'updated message');
|
||||
const result = await repository.saveJoinRequest(updated);
|
||||
expect(result).toEqual(updated);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[saveJoinRequest] Success - updated existing join request: req1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeJoinRequest', () => {
|
||||
it('should remove join request by id', async () => {
|
||||
const request = createTestJoinRequest('req1', 'team1', 'driver1');
|
||||
await repository.saveJoinRequest(request);
|
||||
await repository.removeJoinRequest('req1');
|
||||
const requests = await repository.getJoinRequests('team1');
|
||||
expect(requests).toHaveLength(0);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('[removeJoinRequest] Success - removed join request req1 from team team1.');
|
||||
});
|
||||
|
||||
it('should log warning if request not found', async () => {
|
||||
await repository.removeJoinRequest('req1');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('[removeJoinRequest] Not found - join request with ID req1 not found for removal.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,29 +17,11 @@ export class InMemoryTeamMembershipRepository implements ITeamMembershipReposito
|
||||
private joinRequestsByTeam: Map<string, TeamJoinRequest[]>;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedMemberships?: TeamMembership[], seedJoinRequests?: TeamJoinRequest[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryTeamMembershipRepository initialized.');
|
||||
this.membershipsByTeam = new Map();
|
||||
this.joinRequestsByTeam = new Map();
|
||||
|
||||
if (seedMemberships) {
|
||||
seedMemberships.forEach((membership) => {
|
||||
const list = this.membershipsByTeam.get(membership.teamId) ?? [];
|
||||
list.push(membership);
|
||||
this.membershipsByTeam.set(membership.teamId, list);
|
||||
this.logger.debug(`Seeded membership for team ${membership.teamId}, driver ${membership.driverId}.`);
|
||||
});
|
||||
}
|
||||
|
||||
if (seedJoinRequests) {
|
||||
seedJoinRequests.forEach((request) => {
|
||||
const list = this.joinRequestsByTeam.get(request.teamId) ?? [];
|
||||
list.push(request);
|
||||
this.joinRequestsByTeam.set(request.teamId, list);
|
||||
this.logger.debug(`Seeded join request for team ${request.teamId}, driver ${request.driverId}.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getMembershipList(teamId: string): TeamMembership[] {
|
||||
@@ -78,7 +60,7 @@ async getMembership(teamId: string, driverId: string): Promise<TeamMembership |
|
||||
}
|
||||
return membership;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getMembership] Error getting membership for team ${teamId}, driver ${driverId}:`, error);
|
||||
this.logger.error(`[getMembership] Error getting membership for team ${teamId}, driver ${driverId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -147,7 +129,7 @@ async getMembership(teamId: string, driverId: string): Promise<TeamMembership |
|
||||
this.membershipsByTeam.set(membership.teamId, list);
|
||||
return membership;
|
||||
} catch (error) {
|
||||
this.logger.error(`[saveMembership] Error saving membership for team ${membership.teamId}, driver ${membership.driverId}:`, error);
|
||||
this.logger.error(`[saveMembership] Error saving membership for team ${membership.teamId}, driver ${membership.driverId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -169,7 +151,7 @@ async getMembership(teamId: string, driverId: string): Promise<TeamMembership |
|
||||
this.logger.info(`[removeMembership] Not found - membership for team: ${teamId}, driver: ${driverId}. Cannot remove.`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`[removeMembership] Error removing membership for team ${teamId}, driver ${driverId}:`, error);
|
||||
this.logger.error(`[removeMembership] Error removing membership for team ${teamId}, driver ${driverId}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryTeamRepository } from './InMemoryTeamRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Team } from '@core/racing/domain/entities/Team';
|
||||
|
||||
describe('InMemoryTeamRepository', () => {
|
||||
let repository: InMemoryTeamRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryTeamRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryTeamRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if team not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding team by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Team with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the team if found', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
await repository.create(team);
|
||||
|
||||
const result = await repository.findById('team1');
|
||||
expect(result).toEqual(team);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding team by id: team1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found team: team1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an empty array when no teams', async () => {
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding all teams.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 teams.');
|
||||
});
|
||||
|
||||
it('should return all teams', async () => {
|
||||
const team1 = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team 1',
|
||||
tag: 'TT1',
|
||||
description: 'A test team 1',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
const team2 = Team.create({
|
||||
id: 'team2',
|
||||
name: 'Test Team 2',
|
||||
tag: 'TT2',
|
||||
description: 'A test team 2',
|
||||
ownerId: 'owner2',
|
||||
leagues: ['league2'],
|
||||
});
|
||||
await repository.create(team1);
|
||||
await repository.create(team2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([team1, team2]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding all teams.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 teams.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByLeagueId', () => {
|
||||
it('should return empty array if no teams in league', async () => {
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding teams by league id: league1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 teams for league id: league1.');
|
||||
});
|
||||
|
||||
it('should return teams in the league', async () => {
|
||||
const team1 = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team 1',
|
||||
tag: 'TT1',
|
||||
description: 'A test team 1',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
const team2 = Team.create({
|
||||
id: 'team2',
|
||||
name: 'Test Team 2',
|
||||
tag: 'TT2',
|
||||
description: 'A test team 2',
|
||||
ownerId: 'owner2',
|
||||
leagues: ['league1', 'league2'],
|
||||
});
|
||||
await repository.create(team1);
|
||||
await repository.create(team2);
|
||||
|
||||
const result = await repository.findByLeagueId('league1');
|
||||
expect(result).toEqual([team1, team2]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding teams by league id: league1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 teams for league id: league1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new team', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
|
||||
const result = await repository.create(team);
|
||||
expect(result).toEqual(team);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Creating team: team1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Team team1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if team already exists', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
await repository.create(team);
|
||||
|
||||
await expect(repository.create(team)).rejects.toThrow('Team with ID team1 already exists');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Team with ID team1 already exists.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing team', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
await repository.create(team);
|
||||
|
||||
const updatedTeam = team.update({ name: 'Updated Team' });
|
||||
const result = await repository.update(updatedTeam);
|
||||
expect(result).toEqual(updatedTeam);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Updating team: team1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Team team1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if team does not exist', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
|
||||
await expect(repository.update(team)).rejects.toThrow('Team with ID team1 not found');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Team with ID team1 not found for update.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing team', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
await repository.create(team);
|
||||
|
||||
await repository.delete('team1');
|
||||
expect(await repository.exists('team1')).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Deleting team: team1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Team team1 deleted successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if team does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('Team with ID nonexistent not found');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Team with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if team exists', async () => {
|
||||
const team = Team.create({
|
||||
id: 'team1',
|
||||
name: 'Test Team',
|
||||
tag: 'TT',
|
||||
description: 'A test team',
|
||||
ownerId: 'owner1',
|
||||
leagues: ['league1'],
|
||||
});
|
||||
await repository.create(team);
|
||||
|
||||
const result = await repository.exists('team1');
|
||||
expect(result).toBe(true);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of team with id: team1');
|
||||
});
|
||||
|
||||
it('should return false if team does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of team with id: nonexistent');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,17 +13,10 @@ export class InMemoryTeamRepository implements ITeamRepository {
|
||||
private teams: Map<string, Team>;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Team[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryTeamRepository initialized.');
|
||||
this.teams = new Map();
|
||||
|
||||
if (seedData) {
|
||||
seedData.forEach((team) => {
|
||||
this.teams.set(team.id, team);
|
||||
this.logger.debug(`Seeded team: ${team.id}.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Team | null> {
|
||||
@@ -58,7 +51,7 @@ export class InMemoryTeamRepository implements ITeamRepository {
|
||||
this.logger.debug(`Finding teams by league id: ${leagueId}`);
|
||||
try {
|
||||
const teams = Array.from(this.teams.values()).filter((team) =>
|
||||
team.leagues.includes(leagueId),
|
||||
team.leagues.some(league => league.toString() === leagueId),
|
||||
);
|
||||
this.logger.info(`Found ${teams.length} teams for league id: ${leagueId}.`);
|
||||
return teams;
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryTrackRepository } from './InMemoryTrackRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Track } from '@core/racing/domain/entities/Track';
|
||||
|
||||
describe('InMemoryTrackRepository', () => {
|
||||
let repository: InMemoryTrackRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryTrackRepository(mockLogger);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryTrackRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if track not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding track by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Track with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the track if found', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
shortName: 'MON',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
difficulty: 'advanced',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
imageUrl: 'https://example.com/monza.jpg',
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
const result = await repository.findById('track1');
|
||||
expect(result).toEqual(track);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding track by id: track1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found track: track1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return an empty array when no tracks', async () => {
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding all tracks.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks.');
|
||||
});
|
||||
|
||||
it('should return all tracks', async () => {
|
||||
const track1 = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track2 = Track.create({
|
||||
id: 'track2',
|
||||
name: 'Silverstone',
|
||||
country: 'UK',
|
||||
category: 'road',
|
||||
lengthKm: 5.891,
|
||||
turns: 18,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track1);
|
||||
await repository.create(track2);
|
||||
|
||||
const result = await repository.findAll();
|
||||
expect(result).toEqual([track1, track2]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding all tracks.');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByGameId', () => {
|
||||
it('should return empty array if no tracks for game id', async () => {
|
||||
const result = await repository.findByGameId('iracing');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by game id: iracing');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for game id: iracing.');
|
||||
});
|
||||
|
||||
it('should return tracks for the game id', async () => {
|
||||
const track1 = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track2 = Track.create({
|
||||
id: 'track2',
|
||||
name: 'Silverstone',
|
||||
country: 'UK',
|
||||
category: 'road',
|
||||
lengthKm: 5.891,
|
||||
turns: 18,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track3 = Track.create({
|
||||
id: 'track3',
|
||||
name: 'Daytona',
|
||||
country: 'USA',
|
||||
category: 'oval',
|
||||
lengthKm: 4.0,
|
||||
turns: 4,
|
||||
gameId: 'assetto',
|
||||
});
|
||||
await repository.create(track1);
|
||||
await repository.create(track2);
|
||||
await repository.create(track3);
|
||||
|
||||
const result = await repository.findByGameId('iracing');
|
||||
expect(result).toEqual([track1, track2]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by game id: iracing');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks for game id: iracing.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByCategory', () => {
|
||||
it('should return empty array if no tracks in category', async () => {
|
||||
const result = await repository.findByCategory('oval');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by category: oval');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for category: oval.');
|
||||
});
|
||||
|
||||
it('should return tracks in the category', async () => {
|
||||
const track1 = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track2 = Track.create({
|
||||
id: 'track2',
|
||||
name: 'Daytona',
|
||||
country: 'USA',
|
||||
category: 'oval',
|
||||
lengthKm: 4.0,
|
||||
turns: 4,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track1);
|
||||
await repository.create(track2);
|
||||
|
||||
const result = await repository.findByCategory('road');
|
||||
expect(result).toEqual([track1]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by category: road');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tracks for category: road.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByCountry', () => {
|
||||
it('should return empty array if no tracks in country', async () => {
|
||||
const result = await repository.findByCountry('France');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by country: France');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks for country: France.');
|
||||
});
|
||||
|
||||
it('should return tracks in the country', async () => {
|
||||
const track1 = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track2 = Track.create({
|
||||
id: 'track2',
|
||||
name: 'Imola',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 4.909,
|
||||
turns: 17,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track3 = Track.create({
|
||||
id: 'track3',
|
||||
name: 'Silverstone',
|
||||
country: 'UK',
|
||||
category: 'road',
|
||||
lengthKm: 5.891,
|
||||
turns: 18,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track1);
|
||||
await repository.create(track2);
|
||||
await repository.create(track3);
|
||||
|
||||
const result = await repository.findByCountry('Italy');
|
||||
expect(result).toEqual([track2, track1]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding tracks by country: Italy');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks for country: Italy.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByName', () => {
|
||||
it('should return empty array if no matches', async () => {
|
||||
const result = await repository.searchByName('nonexistent');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: nonexistent');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 0 tracks matching search query: nonexistent.');
|
||||
});
|
||||
|
||||
it('should return tracks matching the query in name', async () => {
|
||||
const track1 = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
shortName: 'MON',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const track2 = Track.create({
|
||||
id: 'track2',
|
||||
name: 'Silverstone Circuit',
|
||||
shortName: 'SIL',
|
||||
country: 'UK',
|
||||
category: 'road',
|
||||
lengthKm: 5.891,
|
||||
turns: 18,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track1);
|
||||
await repository.create(track2);
|
||||
|
||||
const result = await repository.searchByName('Circuit');
|
||||
expect(result).toEqual([track1, track2]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: Circuit');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 tracks matching search query: Circuit.');
|
||||
});
|
||||
|
||||
it('should return tracks matching the query in short name', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
shortName: 'MON',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
const result = await repository.searchByName('mon');
|
||||
expect(result).toEqual([track]);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Searching tracks by name query: mon');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tracks matching search query: mon.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new track', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
|
||||
const result = await repository.create(track);
|
||||
expect(result).toEqual(track);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Creating track: track1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Track track1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if track already exists', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
await expect(repository.create(track)).rejects.toThrow('Track with ID track1 already exists');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID track1 already exists.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing track', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
const updatedTrack = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Grand Prix Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
const result = await repository.update(updatedTrack);
|
||||
expect(result).toEqual(updatedTrack);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Updating track: track1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Track track1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if track does not exist', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
|
||||
await expect(repository.update(track)).rejects.toThrow('Track with ID track1 not found');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID track1 not found for update.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing track', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
await repository.delete('track1');
|
||||
expect(await repository.exists('track1')).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Deleting track: track1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Track track1 deleted successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if track does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('Track with ID nonexistent not found');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Track with ID nonexistent not found for deletion.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if track exists', async () => {
|
||||
const track = Track.create({
|
||||
id: 'track1',
|
||||
name: 'Monza Circuit',
|
||||
country: 'Italy',
|
||||
category: 'road',
|
||||
lengthKm: 5.793,
|
||||
turns: 11,
|
||||
gameId: 'iracing',
|
||||
});
|
||||
await repository.create(track);
|
||||
|
||||
const result = await repository.exists('track1');
|
||||
expect(result).toBe(true);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of track with id: track1');
|
||||
});
|
||||
|
||||
it('should return false if track does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Checking existence of track with id: nonexistent');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,17 +14,10 @@ export class InMemoryTrackRepository implements ITrackRepository {
|
||||
private tracks: Map<string, Track>;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Track[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryTrackRepository initialized.');
|
||||
this.tracks = new Map();
|
||||
|
||||
if (seedData) {
|
||||
seedData.forEach(track => {
|
||||
this.tracks.set(track.id, track);
|
||||
this.logger.debug(`Seeded track: ${track.id}.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Track | null> {
|
||||
@@ -59,8 +52,8 @@ export class InMemoryTrackRepository implements ITrackRepository {
|
||||
this.logger.debug(`Finding tracks by game id: ${gameId}`);
|
||||
try {
|
||||
const tracks = Array.from(this.tracks.values())
|
||||
.filter(track => track.gameId === gameId)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(track => track.gameId.props === gameId)
|
||||
.sort((a, b) => a.name.props.localeCompare(b.name.props));
|
||||
this.logger.info(`Found ${tracks.length} tracks for game id: ${gameId}.`);
|
||||
return tracks;
|
||||
} catch (error) {
|
||||
@@ -74,7 +67,7 @@ export class InMemoryTrackRepository implements ITrackRepository {
|
||||
try {
|
||||
const tracks = Array.from(this.tracks.values())
|
||||
.filter(track => track.category === category)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.sort((a, b) => a.name.props.localeCompare(b.name.props));
|
||||
this.logger.info(`Found ${tracks.length} tracks for category: ${category}.`);
|
||||
return tracks;
|
||||
} catch (error) {
|
||||
@@ -87,8 +80,8 @@ export class InMemoryTrackRepository implements ITrackRepository {
|
||||
this.logger.debug(`Finding tracks by country: ${country}`);
|
||||
try {
|
||||
const tracks = Array.from(this.tracks.values())
|
||||
.filter(track => track.country.toLowerCase() === country.toLowerCase())
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.filter(track => track.country.props.toLowerCase() === country.toLowerCase())
|
||||
.sort((a, b) => a.name.props.localeCompare(b.name.props));
|
||||
this.logger.info(`Found ${tracks.length} tracks for country: ${country}.`);
|
||||
return tracks;
|
||||
} catch (error) {
|
||||
@@ -103,10 +96,10 @@ export class InMemoryTrackRepository implements ITrackRepository {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const tracks = Array.from(this.tracks.values())
|
||||
.filter(track =>
|
||||
track.name.toLowerCase().includes(lowerQuery) ||
|
||||
track.shortName.toLowerCase().includes(lowerQuery)
|
||||
track.name.props.toLowerCase().includes(lowerQuery) ||
|
||||
track.shortName.props.toLowerCase().includes(lowerQuery)
|
||||
)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.sort((a, b) => a.name.props.localeCompare(b.name.props));
|
||||
this.logger.info(`Found ${tracks.length} tracks matching search query: ${query}.`);
|
||||
return tracks;
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { InMemoryTransactionRepository } from './InMemoryTransactionRepository';
|
||||
import { Transaction, TransactionType } from '@core/racing/domain/entities/league-wallet/Transaction';
|
||||
import { TransactionId } from '@core/racing/domain/entities/league-wallet/TransactionId';
|
||||
import { LeagueWalletId } from '@core/racing/domain/entities/league-wallet/LeagueWalletId';
|
||||
import { Money } from '@core/racing/domain/value-objects/Money';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
describe('InMemoryTransactionRepository', () => {
|
||||
let repository: InMemoryTransactionRepository;
|
||||
let mockLogger: Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
repository = new InMemoryTransactionRepository(mockLogger);
|
||||
});
|
||||
|
||||
const createTestTransaction = (id: string, walletId: string, type: TransactionType, amount: number) => {
|
||||
return Transaction.create({
|
||||
id: TransactionId.create(id),
|
||||
walletId: LeagueWalletId.create(walletId),
|
||||
type,
|
||||
amount: Money.create(amount),
|
||||
completedAt: undefined,
|
||||
description: undefined,
|
||||
metadata: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with a logger', () => {
|
||||
expect(repository).toBeDefined();
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('InMemoryTransactionRepository initialized.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should return null if transaction not found', async () => {
|
||||
const result = await repository.findById('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Finding transaction by id: nonexistent');
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Transaction with id nonexistent not found.');
|
||||
});
|
||||
|
||||
it('should return the transaction if found', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await repository.create(transaction);
|
||||
|
||||
const result = await repository.findById('1');
|
||||
expect(result).toEqual(transaction);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found transaction: 1.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByWalletId', () => {
|
||||
it('should return transactions filtered by wallet ID', async () => {
|
||||
const transaction1 = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
const transaction2 = createTestTransaction('2', 'wallet2', 'prize_payout', 200);
|
||||
await repository.create(transaction1);
|
||||
await repository.create(transaction2);
|
||||
|
||||
const result = await repository.findByWalletId('wallet1');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(transaction1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByType', () => {
|
||||
it('should return transactions filtered by type', async () => {
|
||||
const transaction1 = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
const transaction2 = createTestTransaction('2', 'wallet1', 'prize_payout', 200);
|
||||
await repository.create(transaction1);
|
||||
await repository.create(transaction2);
|
||||
|
||||
const result = await repository.findByType('sponsorship_payment');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(transaction1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new transaction', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
const result = await repository.create(transaction);
|
||||
expect(result).toEqual(transaction);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Transaction 1 created successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if transaction already exists', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await repository.create(transaction);
|
||||
await expect(repository.create(transaction)).rejects.toThrow('Transaction with this ID already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update an existing transaction', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await repository.create(transaction);
|
||||
|
||||
const updatedTransaction = Transaction.create({
|
||||
id: TransactionId.create('1'),
|
||||
walletId: LeagueWalletId.create('wallet1'),
|
||||
type: 'prize_payout',
|
||||
amount: Money.create(150),
|
||||
completedAt: undefined,
|
||||
description: undefined,
|
||||
metadata: undefined,
|
||||
});
|
||||
const result = await repository.update(updatedTransaction);
|
||||
expect(result).toEqual(updatedTransaction);
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Transaction 1 updated successfully.');
|
||||
});
|
||||
|
||||
it('should throw error if transaction does not exist', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await expect(repository.update(transaction)).rejects.toThrow('Transaction not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete an existing transaction', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await repository.create(transaction);
|
||||
|
||||
await repository.delete('1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Transaction 1 deleted successfully.');
|
||||
const found = await repository.findById('1');
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error if transaction does not exist', async () => {
|
||||
await expect(repository.delete('nonexistent')).rejects.toThrow('Transaction with ID nonexistent not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
it('should return true if transaction exists', async () => {
|
||||
const transaction = createTestTransaction('1', 'wallet1', 'sponsorship_payment', 100);
|
||||
await repository.create(transaction);
|
||||
|
||||
const result = await repository.exists('1');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if transaction does not exist', async () => {
|
||||
const result = await repository.exists('nonexistent');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,21 +4,17 @@
|
||||
* Mock repository for testing and development
|
||||
*/
|
||||
|
||||
import type { Transaction, TransactionType } from '../../domain/entities/Transaction';
|
||||
import type { ITransactionRepository } from '../../domain/repositories/ITransactionRepository';
|
||||
import type { Transaction, TransactionType } from '@core/racing/domain/entities/league-wallet/Transaction';
|
||||
import type { ITransactionRepository } from '@core/racing/domain/repositories/ITransactionRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
export class InMemoryTransactionRepository implements ITransactionRepository {
|
||||
private transactions: Map<string, Transaction> = new Map();
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger, seedData?: Transaction[]) {
|
||||
constructor(logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.logger.info('InMemoryTransactionRepository initialized.');
|
||||
if (seedData) {
|
||||
seedData.forEach(t => this.transactions.set(t.id, t));
|
||||
this.logger.debug(`Seeded ${seedData.length} transactions.`);
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Transaction | null> {
|
||||
@@ -40,7 +36,7 @@ export class InMemoryTransactionRepository implements ITransactionRepository {
|
||||
async findByWalletId(walletId: string): Promise<Transaction[]> {
|
||||
this.logger.debug(`Finding transactions by wallet id: ${walletId}`);
|
||||
try {
|
||||
const transactions = Array.from(this.transactions.values()).filter(t => t.walletId === walletId);
|
||||
const transactions = Array.from(this.transactions.values()).filter(t => t.walletId.toString() === walletId);
|
||||
this.logger.info(`Found ${transactions.length} transactions for wallet id: ${walletId}.`);
|
||||
return transactions;
|
||||
} catch (error) {
|
||||
@@ -64,11 +60,11 @@ export class InMemoryTransactionRepository implements ITransactionRepository {
|
||||
async create(transaction: Transaction): Promise<Transaction> {
|
||||
this.logger.debug(`Creating transaction: ${transaction.id}`);
|
||||
try {
|
||||
if (this.transactions.has(transaction.id)) {
|
||||
if (this.transactions.has(transaction.id.toString())) {
|
||||
this.logger.warn(`Transaction with ID ${transaction.id} already exists.`);
|
||||
throw new Error('Transaction with this ID already exists');
|
||||
}
|
||||
this.transactions.set(transaction.id, transaction);
|
||||
this.transactions.set(transaction.id.toString(), transaction);
|
||||
this.logger.info(`Transaction ${transaction.id} created successfully.`);
|
||||
return transaction;
|
||||
} catch (error) {
|
||||
@@ -80,11 +76,11 @@ export class InMemoryTransactionRepository implements ITransactionRepository {
|
||||
async update(transaction: Transaction): Promise<Transaction> {
|
||||
this.logger.debug(`Updating transaction: ${transaction.id}`);
|
||||
try {
|
||||
if (!this.transactions.has(transaction.id)) {
|
||||
if (!this.transactions.has(transaction.id.toString())) {
|
||||
this.logger.warn(`Transaction with ID ${transaction.id} not found for update.`);
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
this.transactions.set(transaction.id, transaction);
|
||||
this.transactions.set(transaction.id.toString(), transaction);
|
||||
this.logger.info(`Transaction ${transaction.id} updated successfully.`);
|
||||
return transaction;
|
||||
} catch (error) {
|
||||
@@ -96,11 +92,13 @@ export class InMemoryTransactionRepository implements ITransactionRepository {
|
||||
async delete(id: string): Promise<void> {
|
||||
this.logger.debug(`Deleting transaction: ${id}`);
|
||||
try {
|
||||
if (this.transactions.delete(id)) {
|
||||
this.logger.info(`Transaction ${id} deleted successfully.`);
|
||||
} else {
|
||||
if (!this.transactions.has(id)) {
|
||||
this.logger.warn(`Transaction with id ${id} not found for deletion.`);
|
||||
throw new Error(`Transaction with ID ${id} not found`);
|
||||
}
|
||||
|
||||
this.transactions.delete(id);
|
||||
this.logger.info(`Transaction ${id} deleted successfully.`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting transaction ${id}:`, error instanceof Error ? error : new Error(String(error)));
|
||||
throw error;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
import type { IEntity } from '@core/shared/domain';
|
||||
import { IRacingId } from '../value-objects/IRacingId';
|
||||
import { DriverName } from '../value-objects/DriverName';
|
||||
import { DriverName } from '../value-objects/driver/DriverName';
|
||||
import { CountryCode } from '../value-objects/CountryCode';
|
||||
import { DriverBio } from '../value-objects/DriverBio';
|
||||
import { DriverBio } from '../value-objects/driver/DriverBio';
|
||||
import { JoinedAt } from '../value-objects/JoinedAt';
|
||||
|
||||
export class Driver implements IEntity<string> {
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { IEntity } from '@core/shared/domain';
|
||||
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
|
||||
import { LiveryDecal } from '../value-objects/LiveryDecal';
|
||||
import { DecalOverride } from '../value-objects/DecalOverride';
|
||||
import { DriverId } from '../value-objects/DriverId';
|
||||
import { DriverId } from '../value-objects/driver/DriverId';
|
||||
import { GameId } from './GameId';
|
||||
import { CarId } from '../value-objects/CarId';
|
||||
import { ImageUrl } from '../value-objects/ImageUrl';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { IEntity } from '@core/shared/domain';
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
import type { ChampionshipConfig } from '../types/ChampionshipConfig';
|
||||
import { SeasonId } from './SeasonId';
|
||||
import { SeasonId } from './season/SeasonId';
|
||||
import { ScoringPresetId } from './ScoringPresetId';
|
||||
import { LeagueScoringConfigId } from './LeagueScoringConfigId';
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import { RacingDomainValidationError, RacingDomainInvariantError } from '../erro
|
||||
import type { LiveryDecal } from '../value-objects/LiveryDecal';
|
||||
import { LiveryTemplateId } from './LiveryTemplateId';
|
||||
import { LeagueId } from './LeagueId';
|
||||
import { SeasonId } from './SeasonId';
|
||||
import { CarId } from './CarId';
|
||||
import { SeasonId } from './season/SeasonId';
|
||||
import { CarId } from '../value-objects/CarId';
|
||||
import { ImageUrl } from './ImageUrl';
|
||||
import { LiveryTemplateCreatedAt } from './LiveryTemplateCreatedAt';
|
||||
import { LiveryTemplateUpdatedAt } from './LiveryTemplateUpdatedAt';
|
||||
|
||||
0
core/racing/domain/entities/Penalty.ts
Normal file
0
core/racing/domain/entities/Penalty.ts
Normal file
@@ -5,12 +5,12 @@
|
||||
* Aggregate root for managing league finances and transactions.
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
|
||||
import { RacingDomainValidationError, RacingDomainInvariantError } from '../../errors/RacingDomainError';
|
||||
import type { IEntity } from '@core/shared/domain';
|
||||
|
||||
import type { Money } from '../value-objects/Money';
|
||||
import type { Money } from '../../value-objects/Money';
|
||||
import { LeagueWalletId } from './LeagueWalletId';
|
||||
import { LeagueId } from './LeagueId';
|
||||
import { LeagueId } from '../LeagueId';
|
||||
import { TransactionId } from './TransactionId';
|
||||
|
||||
export interface LeagueWalletProps {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Defines the contract for persisting and retrieving Penalty entities.
|
||||
*/
|
||||
|
||||
import type { Penalty } from '../entities/Penalty';
|
||||
import type { Penalty } from '../entities/penalty/Penalty';
|
||||
|
||||
export interface IPenaltyRepository {
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Defines async methods using domain entities as types.
|
||||
*/
|
||||
|
||||
import type { Result } from '../entities/Result';
|
||||
import type { Result } from '../entities/result/Result';
|
||||
|
||||
export interface IResultRepository {
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Defines operations for Transaction entity persistence
|
||||
*/
|
||||
|
||||
import type { Transaction, TransactionType } from '../entities/Transaction';
|
||||
import type { Transaction, TransactionType } from '../entities/league-wallet/Transaction';
|
||||
|
||||
export interface ITransactionRepository {
|
||||
findById(id: string): Promise<Transaction | null>;
|
||||
|
||||
Reference in New Issue
Block a user