This commit is contained in:
2025-12-17 12:05:00 +01:00
parent 4d890863d3
commit 07dfefebe4
65 changed files with 6034 additions and 778 deletions

View 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);
}

View 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,
};
}

View File

@@ -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);
});
});
});

View File

@@ -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) {

View File

@@ -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);
});
});
});

View File

@@ -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.`);

View File

@@ -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.');
});
});
});

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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);
});
});
});

View File

@@ -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;
}
}
}

View File

@@ -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);
});
});
});

View File

@@ -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);
}
}

View File

@@ -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');
});
});
});

View File

@@ -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 });
}
}

View File

@@ -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.');
});
});
});

View File

@@ -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[]> {

View File

@@ -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);
});
});
});

View File

@@ -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) {

View File

@@ -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.');
});
});
});
});

View File

@@ -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;
}
}

View File

@@ -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);
});
});
});

View File

@@ -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 {

View File

@@ -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);
});
});
});

View File

@@ -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);
}

View File

@@ -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);
});
});
});

View File

@@ -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> {

View File

@@ -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.');
});
});
});

View File

@@ -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);
}
}

View File

@@ -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);
});
});
});

View File

@@ -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> {

View File

@@ -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);
});
});
});

View File

@@ -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

View File

@@ -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]);
});
});
});
});

View File

@@ -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,
};
}

View File

@@ -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);
});
});
});

View File

@@ -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> {

View File

@@ -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);
});
});
});

View File

@@ -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 {

View File

@@ -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);
});
});
});

View File

@@ -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 {

View File

@@ -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);
});
});
});

View File

@@ -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.`);

View File

@@ -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([]);
});
});
});

View File

@@ -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.');
}
}

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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;
}
}

View File

@@ -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.');
});
});
});

View File

@@ -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;
}
}

View File

@@ -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');
});
});
});

View File

@@ -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;

View File

@@ -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');
});
});
});

View File

@@ -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) {

View File

@@ -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);
});
});
});

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

View 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 {

View File

@@ -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 {
/**

View File

@@ -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 {
/**

View File

@@ -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>;