wip
This commit is contained in:
@@ -367,6 +367,7 @@ export function configureDIContainer(): void {
|
||||
|
||||
const penalty = Penalty.create({
|
||||
id: `penalty-${race.id}-${i}`,
|
||||
leagueId: race.leagueId,
|
||||
raceId: race.id,
|
||||
driverId: accusedResult.driverId,
|
||||
type: penaltyType,
|
||||
@@ -390,6 +391,7 @@ export function configureDIContainer(): void {
|
||||
if (penalizedResult) {
|
||||
const penalty = Penalty.create({
|
||||
id: `penalty-direct-${race.id}`,
|
||||
leagueId: race.leagueId,
|
||||
raceId: race.id,
|
||||
driverId: penalizedResult.driverId,
|
||||
type: 'points_deduction',
|
||||
@@ -410,6 +412,7 @@ export function configureDIContainer(): void {
|
||||
if (penalizedResult) {
|
||||
const penalty = Penalty.create({
|
||||
id: `penalty-direct-2-${race.id}`,
|
||||
leagueId: race.leagueId,
|
||||
raceId: race.id,
|
||||
driverId: penalizedResult.driverId,
|
||||
type: 'points_deduction',
|
||||
|
||||
@@ -438,7 +438,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
private static readonly PAUSE_CHECK_INTERVAL = 300;
|
||||
|
||||
/** Checkout confirmation callback - called before clicking checkout button */
|
||||
private checkoutConfirmationCallback?: (price: CheckoutPrice, state: CheckoutState) => Promise<CheckoutConfirmation>;
|
||||
private checkoutConfirmationCallback: ((price: CheckoutPrice, state: CheckoutState) => Promise<CheckoutConfirmation>) | undefined;
|
||||
|
||||
/** Page state validator instance */
|
||||
private pageStateValidator: PageStateValidator;
|
||||
|
||||
@@ -508,7 +508,7 @@ export class IRacingDomInteractor {
|
||||
|
||||
for (const sel of cands) {
|
||||
try {
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLElement[];
|
||||
if (els.length === 0) continue;
|
||||
for (const el of els) {
|
||||
try {
|
||||
@@ -516,10 +516,10 @@ export class IRacingDomInteractor {
|
||||
(el as HTMLInputElement).checked = Boolean(should);
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else {
|
||||
(el as HTMLElement).setAttribute('aria-checked', String(Boolean(should)));
|
||||
el.setAttribute('aria-checked', String(Boolean(should)));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try {
|
||||
(el as HTMLElement).click();
|
||||
el.click();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -615,7 +615,7 @@ export class IRacingDomInteractor {
|
||||
const applied = await page.evaluate(
|
||||
({ sel, val }) => {
|
||||
try {
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLElement[];
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
|
||||
if (els.length === 0) return false;
|
||||
for (const el of els) {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,7 @@ export class ApplyPenaltyUseCase
|
||||
// Create the penalty
|
||||
const penalty = Penalty.create({
|
||||
id: randomUUID(),
|
||||
leagueId: race.leagueId,
|
||||
raceId: command.raceId,
|
||||
driverId: command.driverId,
|
||||
type: command.type,
|
||||
|
||||
@@ -23,6 +23,7 @@ export type PenaltyStatus = 'pending' | 'applied' | 'appealed' | 'overturned';
|
||||
|
||||
export interface PenaltyProps {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
raceId: string;
|
||||
/** The driver receiving the penalty */
|
||||
driverId: string;
|
||||
@@ -51,6 +52,7 @@ export class Penalty implements IEntity<string> {
|
||||
|
||||
static create(props: PenaltyProps): Penalty {
|
||||
if (!props.id) throw new RacingDomainValidationError('Penalty ID is required');
|
||||
if (!props.leagueId) throw new RacingDomainValidationError('League ID is required');
|
||||
if (!props.raceId) throw new RacingDomainValidationError('Race ID is required');
|
||||
if (!props.driverId) throw new RacingDomainValidationError('Driver ID is required');
|
||||
if (!props.type) throw new RacingDomainValidationError('Penalty type is required');
|
||||
@@ -72,6 +74,7 @@ export class Penalty implements IEntity<string> {
|
||||
}
|
||||
|
||||
get id(): string { return this.props.id; }
|
||||
get leagueId(): string { return this.props.leagueId; }
|
||||
get raceId(): string { return this.props.raceId; }
|
||||
get driverId(): string { return this.props.driverId; }
|
||||
get type(): PenaltyType { return this.props.type; }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { OverlayAction } from '../../../../packages/automation/application/ports/IOverlaySyncPort'
|
||||
import { OverlayAction } from '../../../../packages/automation/application/ports/OverlaySyncPort'
|
||||
import { IAutomationLifecycleEmitter, LifecycleCallback } from '../../../../packages/automation/infrastructure/adapters/IAutomationLifecycleEmitter'
|
||||
import { OverlaySyncService } from '../../../../packages/automation/application/services/OverlaySyncService'
|
||||
|
||||
@@ -11,7 +11,7 @@ class MockLifecycleEmitter implements IAutomationLifecycleEmitter {
|
||||
offLifecycle(cb: LifecycleCallback): void {
|
||||
this.callbacks.delete(cb)
|
||||
}
|
||||
async emit(event: { type: string; actionId: string; timestamp: number }) {
|
||||
async emit(event: { type: 'panel-attached'|'modal-opened'|'action-started'|'action-complete'|'action-failed'|'panel-missing'; actionId: string; timestamp: number }) {
|
||||
for (const cb of Array.from(this.callbacks)) {
|
||||
cb(event)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('CompleteRaceCreationUseCase', () => {
|
||||
const state = CheckoutState.ready();
|
||||
|
||||
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
||||
Result.ok({ price: undefined, state, buttonHtml: '<a>n/a</a>' })
|
||||
Result.ok({ price: null, state, buttonHtml: '<a>n/a</a>' })
|
||||
);
|
||||
|
||||
const result = await useCase.execute('test-session-123');
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Result } from '@gridpilot/shared-result';
|
||||
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
|
||||
import type { ICheckoutService } from '@gridpilot/automation/application/ports/ICheckoutService';
|
||||
import type { ICheckoutConfirmationPort } from '@gridpilot/automation/application/ports/ICheckoutConfirmationPort';
|
||||
import type { CheckoutServicePort } from '@gridpilot/automation/application/ports/CheckoutServicePort';
|
||||
import type { CheckoutConfirmationPort } from '@gridpilot/automation/application/ports/CheckoutConfirmationPort';
|
||||
|
||||
describe('ConfirmCheckoutUseCase - Enhanced with Confirmation Port', () => {
|
||||
let mockCheckoutService: ICheckoutService;
|
||||
let mockConfirmationPort: ICheckoutConfirmationPort;
|
||||
let mockCheckoutService: CheckoutServicePort;
|
||||
let mockConfirmationPort: CheckoutConfirmationPort;
|
||||
let useCase: ConfirmCheckoutUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Result } from '../../../../packages/shared/result/Result';
|
||||
import { ConfirmCheckoutUseCase } from '../../../../packages/automation/application/use-cases/ConfirmCheckoutUseCase';
|
||||
import type { CheckoutServicePort } from '../../../../packages/automation/application/ports/CheckoutServicePort';
|
||||
import type { CheckoutConfirmationPort } from '../../../../packages/automation/application/ports/CheckoutConfirmationPort';
|
||||
import type { CheckoutInfoDTODTO } from '../../../../packages/automation/application/dto/CheckoutInfoDTODTO';
|
||||
import type { CheckoutInfoDTO } from '../../../../packages/automation/application/dto/CheckoutInfoDTO';
|
||||
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState, CheckoutStateEnum } from '@gridpilot/automation/domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
|
||||
|
||||
@@ -8,10 +8,10 @@ import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IR
|
||||
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
|
||||
import type { IChampionshipStandingRepository } from '@gridpilot/racing/domain/repositories/IChampionshipStandingRepository';
|
||||
|
||||
import type { Season } from '@gridpilot/racing/domain/entities/Season';
|
||||
import { Season } from '@gridpilot/racing/domain/entities/Season';
|
||||
import type { LeagueScoringConfig } from '@gridpilot/racing/domain/entities/LeagueScoringConfig';
|
||||
import type { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import type { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import type { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
|
||||
import type { ChampionshipStanding } from '@gridpilot/racing/domain/entities/ChampionshipStanding';
|
||||
import type { ChampionshipConfig } from '@gridpilot/racing/domain/types/ChampionshipConfig';
|
||||
@@ -34,6 +34,30 @@ class InMemorySeasonRepository implements ISeasonRepository {
|
||||
return this.seasons.filter((s) => s.leagueId === leagueId);
|
||||
}
|
||||
|
||||
async create(season: Season): Promise<Season> {
|
||||
this.seasons.push(season);
|
||||
return season;
|
||||
}
|
||||
|
||||
async add(season: Season): Promise<void> {
|
||||
this.seasons.push(season);
|
||||
}
|
||||
|
||||
async update(season: Season): Promise<void> {
|
||||
const index = this.seasons.findIndex((s) => s.id === season.id);
|
||||
if (index >= 0) {
|
||||
this.seasons[index] = season;
|
||||
}
|
||||
}
|
||||
|
||||
async listByLeague(leagueId: string): Promise<Season[]> {
|
||||
return this.seasons.filter((s) => s.leagueId === leagueId);
|
||||
}
|
||||
|
||||
async listActiveByLeague(leagueId: string): Promise<Season[]> {
|
||||
return this.seasons.filter((s) => s.leagueId === leagueId && s.status === 'active');
|
||||
}
|
||||
|
||||
seedSeason(season: Season): void {
|
||||
this.seasons.push(season);
|
||||
}
|
||||
@@ -46,6 +70,16 @@ class InMemoryLeagueScoringConfigRepository implements ILeagueScoringConfigRepos
|
||||
return this.configs.find((c) => c.seasonId === seasonId) || null;
|
||||
}
|
||||
|
||||
async save(config: LeagueScoringConfig): Promise<LeagueScoringConfig> {
|
||||
const index = this.configs.findIndex((c) => c.id === config.id);
|
||||
if (index >= 0) {
|
||||
this.configs[index] = config;
|
||||
} else {
|
||||
this.configs.push(config);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
seedConfig(config: LeagueScoringConfig): void {
|
||||
this.configs.push(config);
|
||||
}
|
||||
@@ -113,10 +147,60 @@ class InMemoryRaceRepository implements IRaceRepository {
|
||||
class InMemoryResultRepository implements IResultRepository {
|
||||
private results: Result[] = [];
|
||||
|
||||
async findById(id: string): Promise<Result | null> {
|
||||
return this.results.find((r) => r.id === id) || null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Result[]> {
|
||||
return [...this.results];
|
||||
}
|
||||
|
||||
async findByRaceId(raceId: string): Promise<Result[]> {
|
||||
return this.results.filter((r) => r.raceId === raceId);
|
||||
}
|
||||
|
||||
async findByDriverId(driverId: string): Promise<Result[]> {
|
||||
return this.results.filter((r) => r.driverId === driverId);
|
||||
}
|
||||
|
||||
async findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Result[]> {
|
||||
return this.results.filter((r) => r.driverId === driverId && r.raceId.startsWith(leagueId));
|
||||
}
|
||||
|
||||
async create(result: Result): Promise<Result> {
|
||||
this.results.push(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
async createMany(results: Result[]): Promise<Result[]> {
|
||||
this.results.push(...results);
|
||||
return results;
|
||||
}
|
||||
|
||||
async update(result: Result): Promise<Result> {
|
||||
const index = this.results.findIndex((r) => r.id === result.id);
|
||||
if (index >= 0) {
|
||||
this.results[index] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
this.results = this.results.filter((r) => r.id !== id);
|
||||
}
|
||||
|
||||
async deleteByRaceId(raceId: string): Promise<void> {
|
||||
this.results = this.results.filter((r) => r.raceId !== raceId);
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
return this.results.some((r) => r.id === id);
|
||||
}
|
||||
|
||||
async existsByRaceId(raceId: string): Promise<boolean> {
|
||||
return this.results.some((r) => r.raceId === raceId);
|
||||
}
|
||||
|
||||
seedResult(result: Result): void {
|
||||
this.results.push(result);
|
||||
}
|
||||
@@ -146,6 +230,41 @@ class InMemoryPenaltyRepository implements IPenaltyRepository {
|
||||
return [...this.penalties];
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Penalty | null> {
|
||||
return this.penalties.find((p) => p.id === id) || null;
|
||||
}
|
||||
|
||||
async findByDriverId(driverId: string): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.driverId === driverId);
|
||||
}
|
||||
|
||||
async findByProtestId(protestId: string): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.protestId === protestId);
|
||||
}
|
||||
|
||||
async findPending(): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.status === 'pending');
|
||||
}
|
||||
|
||||
async findIssuedBy(stewardId: string): Promise<Penalty[]> {
|
||||
return this.penalties.filter((p) => p.issuedBy === stewardId);
|
||||
}
|
||||
|
||||
async create(penalty: Penalty): Promise<void> {
|
||||
this.penalties.push(penalty);
|
||||
}
|
||||
|
||||
async update(penalty: Penalty): Promise<void> {
|
||||
const index = this.penalties.findIndex((p) => p.id === penalty.id);
|
||||
if (index >= 0) {
|
||||
this.penalties[index] = penalty;
|
||||
}
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
return this.penalties.some((p) => p.id === id);
|
||||
}
|
||||
|
||||
seedPenalty(penalty: Penalty): void {
|
||||
this.penalties.push(penalty);
|
||||
}
|
||||
@@ -267,7 +386,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
championshipAggregator,
|
||||
);
|
||||
|
||||
const season: Season = {
|
||||
const season = Season.create({
|
||||
id: seasonId,
|
||||
leagueId,
|
||||
gameId: 'iracing',
|
||||
@@ -277,7 +396,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
order: 1,
|
||||
startDate: new Date('2025-01-01'),
|
||||
endDate: new Date('2025-12-31'),
|
||||
};
|
||||
});
|
||||
|
||||
seasonRepository.seedSeason(season);
|
||||
|
||||
@@ -292,7 +411,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
leagueScoringConfigRepository.seedConfig(leagueScoringConfig);
|
||||
|
||||
const races: Race[] = [
|
||||
{
|
||||
Race.create({
|
||||
id: 'race-1-sprint',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-02-01'),
|
||||
@@ -300,8 +419,8 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-1-main',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-02-01'),
|
||||
@@ -309,8 +428,8 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-2-sprint',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-03-01'),
|
||||
@@ -318,8 +437,8 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-2-main',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-03-01'),
|
||||
@@ -327,8 +446,8 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-3-sprint',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-04-01'),
|
||||
@@ -336,8 +455,8 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-3-main',
|
||||
leagueId,
|
||||
scheduledAt: new Date('2025-04-01'),
|
||||
@@ -345,7 +464,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
car: 'Car A',
|
||||
sessionType: 'race',
|
||||
status: 'completed',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
races.forEach((race) => raceRepository.seedRace(race));
|
||||
@@ -392,7 +511,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
let resultIdCounter = 1;
|
||||
for (const raceData of resultsData) {
|
||||
raceData.finishingOrder.forEach((driverId, index) => {
|
||||
const result: Result = {
|
||||
const result = Result.create({
|
||||
id: `result-${resultIdCounter++}`,
|
||||
raceId: raceData.raceId,
|
||||
driverId,
|
||||
@@ -400,7 +519,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
fastestLap: driverId === raceData.fastestLapDriverId ? 90000 : 91000 + index * 100,
|
||||
incidents: 0,
|
||||
startPosition: index + 1,
|
||||
};
|
||||
});
|
||||
resultRepository.seedResult(result);
|
||||
});
|
||||
}
|
||||
@@ -423,7 +542,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
|
||||
sorted.map((r) => r.participant.id),
|
||||
);
|
||||
|
||||
const leader = rows[0];
|
||||
const leader = rows[0]!;
|
||||
expect(leader.resultsCounted).toBeLessThanOrEqual(6);
|
||||
expect(leader.resultsDropped).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||
import { StartAutomationSessionUseCase } from '../../../../packages/automation/application/use-cases/StartAutomationSessionUseCase';
|
||||
import { IAutomationEngine } from '../../../../packages/automation/application/ports/IAutomationEngine';
|
||||
import { IScreenAutomation } from '../../../../packages/automation/application/ports/IScreenAutomation';
|
||||
import { ISessionRepository } from '../../../../packages/automation/application/ports/ISessionRepository';
|
||||
import { AutomationEnginePort as IAutomationEngine } from '../../../../packages/automation/application/ports/AutomationEnginePort';
|
||||
import { IBrowserAutomation as IScreenAutomation } from '../../../../packages/automation/application/ports/ScreenAutomationPort';
|
||||
import { SessionRepositoryPort as ISessionRepository } from '../../../../packages/automation/application/ports/SessionRepositoryPort';
|
||||
import { AutomationSession } from '@gridpilot/automation/domain/entities/AutomationSession';
|
||||
|
||||
describe('StartAutomationSessionUseCase', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { VerifyAuthenticatedPageUseCase } from '../../../../packages/automation/application/use-cases/VerifyAuthenticatedPageUseCase';
|
||||
import { IAuthenticationService } from '../../../../packages/automation/application/ports/IAuthenticationService';
|
||||
import { AuthenticationServicePort as IAuthenticationService } from '../../../../packages/automation/application/ports/AuthenticationServicePort';
|
||||
import { Result } from '../../../../packages/shared/result/Result';
|
||||
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { SessionType } from '@gridpilot/racing/domain/types/SessionType';
|
||||
import { PointsTable } from '@gridpilot/racing/domain/value-objects/PointsTable';
|
||||
import type { BonusRule } from '@gridpilot/racing/domain/types/BonusRule';
|
||||
import type { ChampionshipConfig } from '@gridpilot/racing/domain/types/ChampionshipConfig';
|
||||
import type { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import type { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
|
||||
import type { ChampionshipType } from '@gridpilot/racing/domain/value-objects/ChampionshipType';
|
||||
import type { ChampionshipType } from '@gridpilot/racing/domain/types/ChampionshipType';
|
||||
|
||||
function makeDriverRef(id: string): ParticipantRef {
|
||||
return {
|
||||
@@ -83,7 +83,7 @@ describe('EventScoringService', () => {
|
||||
});
|
||||
|
||||
const results: Result[] = [
|
||||
{
|
||||
Result.create({
|
||||
id: 'result-1',
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-1',
|
||||
@@ -91,8 +91,8 @@ describe('EventScoringService', () => {
|
||||
fastestLap: 90000,
|
||||
incidents: 0,
|
||||
startPosition: 1,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-2',
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-2',
|
||||
@@ -100,8 +100,8 @@ describe('EventScoringService', () => {
|
||||
fastestLap: 90500,
|
||||
incidents: 0,
|
||||
startPosition: 2,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-3',
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-3',
|
||||
@@ -109,8 +109,8 @@ describe('EventScoringService', () => {
|
||||
fastestLap: 91000,
|
||||
incidents: 0,
|
||||
startPosition: 3,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-4',
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-4',
|
||||
@@ -118,8 +118,8 @@ describe('EventScoringService', () => {
|
||||
fastestLap: 91500,
|
||||
incidents: 0,
|
||||
startPosition: 4,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-5',
|
||||
raceId: 'race-1',
|
||||
driverId: 'driver-5',
|
||||
@@ -127,7 +127,7 @@ describe('EventScoringService', () => {
|
||||
fastestLap: 92000,
|
||||
incidents: 0,
|
||||
startPosition: 5,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const penalties: Penalty[] = [];
|
||||
@@ -179,30 +179,30 @@ describe('EventScoringService', () => {
|
||||
} as const;
|
||||
|
||||
const resultsP11Fastest: Result[] = [
|
||||
{
|
||||
Result.create({
|
||||
id: 'result-1',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-1',
|
||||
position: 1,
|
||||
startPosition: 1,
|
||||
fastestLap: 91000,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-2',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-2',
|
||||
position: 2,
|
||||
startPosition: 2,
|
||||
fastestLap: 90500,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-3',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-3',
|
||||
position: 11,
|
||||
startPosition: 15,
|
||||
fastestLap: 90000,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const penalties: Penalty[] = [];
|
||||
@@ -220,30 +220,30 @@ describe('EventScoringService', () => {
|
||||
expect(mapNoBonus.get('driver-3')?.bonusPoints).toBe(0);
|
||||
|
||||
const resultsP8Fastest: Result[] = [
|
||||
{
|
||||
Result.create({
|
||||
id: 'result-1',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-1',
|
||||
position: 1,
|
||||
startPosition: 1,
|
||||
fastestLap: 91000,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-2',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-2',
|
||||
position: 2,
|
||||
startPosition: 2,
|
||||
fastestLap: 90500,
|
||||
},
|
||||
{
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-3',
|
||||
...baseResultTemplate,
|
||||
driverId: 'driver-3',
|
||||
position: 8,
|
||||
startPosition: 15,
|
||||
fastestLap: 90000,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const pointsWithBonus = service.scoreSession({
|
||||
|
||||
@@ -75,9 +75,9 @@ describe('ScheduleCalculator', () => {
|
||||
expect(date.getDay()).toBe(6); // Saturday
|
||||
});
|
||||
// First race should be Jan 6
|
||||
expect(result.raceDates[0].toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
expect(result.raceDates[0]!.toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
// Last race should be 7 weeks later (Feb 24)
|
||||
expect(result.raceDates[7].toISOString().split('T')[0]).toBe('2024-02-24');
|
||||
expect(result.raceDates[7]!.toISOString().split('T')[0]).toBe('2024-02-24');
|
||||
});
|
||||
|
||||
it('should schedule races on multiple weekdays', () => {
|
||||
@@ -138,13 +138,13 @@ describe('ScheduleCalculator', () => {
|
||||
// Then
|
||||
expect(result.raceDates.length).toBe(4);
|
||||
// First race Jan 6
|
||||
expect(result.raceDates[0].toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
expect(result.raceDates[0]!.toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
// Second race 2 weeks later (Jan 20)
|
||||
expect(result.raceDates[1].toISOString().split('T')[0]).toBe('2024-01-20');
|
||||
expect(result.raceDates[1]!.toISOString().split('T')[0]).toBe('2024-01-20');
|
||||
// Third race 2 weeks later (Feb 3)
|
||||
expect(result.raceDates[2].toISOString().split('T')[0]).toBe('2024-02-03');
|
||||
expect(result.raceDates[2]!.toISOString().split('T')[0]).toBe('2024-02-03');
|
||||
// Fourth race 2 weeks later (Feb 17)
|
||||
expect(result.raceDates[3].toISOString().split('T')[0]).toBe('2024-02-17');
|
||||
expect(result.raceDates[3]!.toISOString().split('T')[0]).toBe('2024-02-17');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ describe('ScheduleCalculator', () => {
|
||||
// Then
|
||||
expect(result.raceDates.length).toBe(8);
|
||||
// First race should be at or near start
|
||||
expect(result.raceDates[0].toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
expect(result.raceDates[0]!.toISOString().split('T')[0]).toBe('2024-01-06');
|
||||
// Races should be spread across the range, not consecutive weeks
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('CheckoutConfirmation Value Object', () => {
|
||||
});
|
||||
|
||||
it('should throw error for invalid decision', () => {
|
||||
expect(() => CheckoutConfirmation.create('invalid')).toThrow(
|
||||
expect(() => CheckoutConfirmation.create('invalid' as any)).toThrow(
|
||||
'Invalid checkout confirmation decision',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -44,11 +44,11 @@ describe('SessionState Value Object', () => {
|
||||
});
|
||||
|
||||
it('should throw error for invalid state', () => {
|
||||
expect(() => SessionState.create('INVALID')).toThrow('Invalid session state');
|
||||
expect(() => SessionState.create('INVALID' as any)).toThrow('Invalid session state');
|
||||
});
|
||||
|
||||
|
||||
it('should throw error for empty string', () => {
|
||||
expect(() => SessionState.create('')).toThrow('Invalid session state');
|
||||
expect(() => SessionState.create('' as any)).toThrow('Invalid session state');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PlaywrightAuthSessionService } from '../../../../packages/automation/in
|
||||
import type { PlaywrightBrowserSession } from '../../../../packages/automation/infrastructure/adapters/automation/core/PlaywrightBrowserSession';
|
||||
import type { SessionCookieStore } from '../../../../packages/automation/infrastructure/adapters/automation/auth/SessionCookieStore';
|
||||
import type { IPlaywrightAuthFlow } from '../../../../packages/automation/infrastructure/adapters/automation/auth/PlaywrightAuthFlow';
|
||||
import type { ILogger } from '../../../../packages/automation/application/ports/ILogger';
|
||||
import type { LoggerPort as ILogger } from '../../../../packages/automation/application/ports/LoggerPort';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
import { Result } from '../../../../packages/shared/result/Result';
|
||||
|
||||
@@ -25,6 +25,9 @@ describe('PlaywrightAuthSessionService.initiateLogin browser mode behaviour', ()
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
child: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
mockPage = {
|
||||
@@ -107,7 +110,7 @@ describe('PlaywrightAuthSessionService.initiateLogin browser mode behaviour', ()
|
||||
}),
|
||||
);
|
||||
|
||||
const calledUrl = (mockPage.goto as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
||||
const calledUrl = (mockPage.goto as unknown as ReturnType<typeof vi.fn>).mock.calls[0]![0] as string;
|
||||
expect(calledUrl).not.toEqual('about:blank');
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Page, Locator } from 'playwright';
|
||||
import { PlaywrightAuthSessionService } from '../../../../packages/automation/infrastructure/adapters/automation/auth/PlaywrightAuthSessionService';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
|
||||
import type { ILogger } from '../../../../packages/automation/application/ports/ILogger';
|
||||
import type { LoggerPort as ILogger } from '../../../../packages/automation/application/ports/LoggerPort';
|
||||
import type { Result } from '../../../../packages/shared/result/Result';
|
||||
import type { PlaywrightBrowserSession } from '../../../../packages/automation/infrastructure/adapters/automation/core/PlaywrightBrowserSession';
|
||||
import type { SessionCookieStore } from '../../../../packages/automation/infrastructure/adapters/automation/auth/SessionCookieStore';
|
||||
@@ -21,6 +21,9 @@ describe('PlaywrightAuthSessionService.verifyPageAuthentication', () => {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
child: vi.fn(),
|
||||
flush: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const mockLocator: Locator = {
|
||||
|
||||
@@ -197,7 +197,7 @@ describe('SessionCookieStore - Cookie Validation', () => {
|
||||
const validCookies = cookieStore.getValidCookiesForUrl(targetUrl);
|
||||
|
||||
expect(validCookies).toHaveLength(1);
|
||||
expect(validCookies[0].name).toBe('valid_cookie');
|
||||
expect(validCookies[0]!.name).toBe('valid_cookie');
|
||||
});
|
||||
|
||||
test('should filter out cookies with mismatched domains', async () => {
|
||||
@@ -228,7 +228,7 @@ describe('SessionCookieStore - Cookie Validation', () => {
|
||||
const validCookies = cookieStore.getValidCookiesForUrl(targetUrl);
|
||||
|
||||
expect(validCookies).toHaveLength(1);
|
||||
expect(validCookies[0].name).toBe('cookie1');
|
||||
expect(validCookies[0]!.name).toBe('cookie1');
|
||||
});
|
||||
|
||||
test('should filter out cookies with invalid paths', async () => {
|
||||
@@ -259,7 +259,7 @@ describe('SessionCookieStore - Cookie Validation', () => {
|
||||
const validCookies = cookieStore.getValidCookiesForUrl(targetUrl);
|
||||
|
||||
expect(validCookies).toHaveLength(1);
|
||||
expect(validCookies[0].name).toBe('valid_path_cookie');
|
||||
expect(validCookies[0]!.name).toBe('valid_path_cookie');
|
||||
});
|
||||
|
||||
test('should return empty array when no cookies are valid', async () => {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { GetDashboardOverviewUseCase } from '@gridpilot/racing/application/use-cases/GetDashboardOverviewUseCase';
|
||||
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
||||
import { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import { League } from '@gridpilot/racing/domain/entities/League';
|
||||
import { Standing } from '@gridpilot/racing/domain/entities/Standing';
|
||||
import { LeagueMembership } from '@gridpilot/racing/domain/entities/LeagueMembership';
|
||||
import type { FeedItem } from '@gridpilot/social/domain/types/FeedItem';
|
||||
import type {
|
||||
IDashboardOverviewPresenter,
|
||||
DashboardOverviewViewModel,
|
||||
@@ -25,11 +32,17 @@ class FakeDashboardOverviewPresenter implements IDashboardOverviewPresenter {
|
||||
|
||||
interface TestImageService {
|
||||
getDriverAvatar(driverId: string): string;
|
||||
getTeamLogo(teamId: string): string;
|
||||
getLeagueCover(leagueId: string): string;
|
||||
getLeagueLogo(leagueId: string): string;
|
||||
}
|
||||
|
||||
function createTestImageService(): TestImageService {
|
||||
return {
|
||||
getDriverAvatar: (driverId: string) => `avatar-${driverId}`,
|
||||
getTeamLogo: (teamId: string) => `team-logo-${teamId}`,
|
||||
getLeagueCover: (leagueId: string) => `league-cover-${leagueId}`,
|
||||
getLeagueLogo: (leagueId: string) => `league-logo-${leagueId}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,143 +51,173 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
// Given a driver with memberships in two leagues and future races with mixed registration
|
||||
const driverId = 'driver-1';
|
||||
|
||||
const driver = { id: driverId, name: 'Alice Racer', country: 'US' };
|
||||
const driver = Driver.create({ id: driverId, iracingId: '12345', name: 'Alice Racer', country: 'US' });
|
||||
|
||||
const leagues = [
|
||||
{ id: 'league-1', name: 'Alpha League' },
|
||||
{ id: 'league-2', name: 'Beta League' },
|
||||
League.create({ id: 'league-1', name: 'Alpha League', description: 'First league', ownerId: 'owner-1' }),
|
||||
League.create({ id: 'league-2', name: 'Beta League', description: 'Second league', ownerId: 'owner-2' }),
|
||||
];
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
const races = [
|
||||
{
|
||||
Race.create({
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
track: 'Monza',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(now + 60 * 60 * 1000),
|
||||
status: 'scheduled' as const,
|
||||
},
|
||||
{
|
||||
status: 'scheduled',
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-2',
|
||||
leagueId: 'league-1',
|
||||
track: 'Spa',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date(now + 2 * 60 * 60 * 1000),
|
||||
status: 'scheduled' as const,
|
||||
},
|
||||
{
|
||||
status: 'scheduled',
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-3',
|
||||
leagueId: 'league-2',
|
||||
track: 'Silverstone',
|
||||
car: 'GT4',
|
||||
scheduledAt: new Date(now + 3 * 60 * 60 * 1000),
|
||||
status: 'scheduled' as const,
|
||||
},
|
||||
{
|
||||
status: 'scheduled',
|
||||
}),
|
||||
Race.create({
|
||||
id: 'race-4',
|
||||
leagueId: 'league-2',
|
||||
track: 'Imola',
|
||||
car: 'GT4',
|
||||
scheduledAt: new Date(now + 4 * 60 * 60 * 1000),
|
||||
status: 'scheduled' as const,
|
||||
},
|
||||
status: 'scheduled',
|
||||
}),
|
||||
];
|
||||
|
||||
const results: unknown[] = [];
|
||||
const results: Result[] = [];
|
||||
|
||||
const memberships = [
|
||||
{
|
||||
LeagueMembership.create({
|
||||
leagueId: 'league-1',
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
}),
|
||||
LeagueMembership.create({
|
||||
leagueId: 'league-2',
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const registeredRaceIds = new Set<string>(['race-1', 'race-3']);
|
||||
|
||||
const feedItems: DashboardFeedItemSummaryViewModel[] = [];
|
||||
const friends: Array<{ id: string }> = [];
|
||||
const feedItems: FeedItem[] = [];
|
||||
const friends: Driver[] = [];
|
||||
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
const driverRepository = {
|
||||
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => [],
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const raceRepository: {
|
||||
findAll: () => Promise<
|
||||
Array<{
|
||||
id: string;
|
||||
leagueId: string;
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: Date;
|
||||
status: 'scheduled';
|
||||
}>
|
||||
>;
|
||||
} = {
|
||||
findAll: async () => races,
|
||||
const raceRepository = {
|
||||
findById: async (): Promise<Race | null> => null,
|
||||
findAll: async (): Promise<Race[]> => races,
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findAll: () => Promise<unknown[]>;
|
||||
} = {
|
||||
findAll: async () => results,
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => results,
|
||||
findByRaceId: async (): Promise<Result[]> => [],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string }>>;
|
||||
} = {
|
||||
findAll: async () => leagues,
|
||||
const leagueRepository = {
|
||||
findById: async (): Promise<League | null> => null,
|
||||
findAll: async (): Promise<League[]> => leagues,
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<unknown[]>;
|
||||
} = {
|
||||
findByLeagueId: async () => [],
|
||||
const standingRepository = {
|
||||
findByLeagueId: async (): Promise<Standing[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
||||
findAll: async (): Promise<Standing[]> => [],
|
||||
save: async (): Promise<Standing> => { throw new Error('Not implemented'); },
|
||||
saveMany: async (): Promise<Standing[]> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByLeagueId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
recalculate: async (): Promise<Standing[]> => [],
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (
|
||||
leagueId: string,
|
||||
driverIdParam: string,
|
||||
) => Promise<{ leagueId: string; driverId: string; status: string } | null>;
|
||||
} = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string) => {
|
||||
const leagueMembershipRepository = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
||||
return (
|
||||
memberships.find(
|
||||
(m) => m.leagueId === leagueId && m.driverId === driverIdParam,
|
||||
) ?? null
|
||||
);
|
||||
},
|
||||
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
||||
getJoinRequests: async (): Promise<any[]> => [],
|
||||
saveMembership: async (): Promise<LeagueMembership> => { throw new Error('Not implemented'); },
|
||||
removeMembership: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
saveJoinRequest: async (): Promise<any> => { throw new Error('Not implemented'); },
|
||||
removeJoinRequest: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async (raceId: string, driverIdParam: string) => {
|
||||
const raceRegistrationRepository = {
|
||||
isRegistered: async (raceId: string, driverIdParam: string): Promise<boolean> => {
|
||||
if (driverIdParam !== driverId) return false;
|
||||
return registeredRaceIds.has(raceId);
|
||||
},
|
||||
getRegisteredDrivers: async (): Promise<string[]> => [],
|
||||
getRegistrationCount: async (): Promise<number> => 0,
|
||||
register: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
withdraw: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
getDriverRegistrations: async (): Promise<string[]> => [],
|
||||
clearRaceRegistrations: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => feedItems,
|
||||
const feedRepository = {
|
||||
getFeedForDriver: async (): Promise<FeedItem[]> => feedItems,
|
||||
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => friends,
|
||||
const socialRepository = {
|
||||
getFriends: async (): Promise<Driver[]> => friends,
|
||||
getFriendIds: async (): Promise<string[]> => [],
|
||||
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
@@ -230,138 +273,181 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
// Given completed races with results and standings
|
||||
const driverId = 'driver-2';
|
||||
|
||||
const driver = { id: driverId, name: 'Result Driver', country: 'DE' };
|
||||
const driver = Driver.create({ id: driverId, iracingId: '67890', name: 'Result Driver', country: 'DE' });
|
||||
|
||||
const leagues = [
|
||||
{ id: 'league-A', name: 'Results League A' },
|
||||
{ id: 'league-B', name: 'Results League B' },
|
||||
League.create({ id: 'league-A', name: 'Results League A', description: 'League A', ownerId: 'owner-A' }),
|
||||
League.create({ id: 'league-B', name: 'Results League B', description: 'League B', ownerId: 'owner-B' }),
|
||||
];
|
||||
|
||||
const raceOld = {
|
||||
const raceOld = Race.create({
|
||||
id: 'race-old',
|
||||
leagueId: 'league-A',
|
||||
track: 'Old Circuit',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date('2024-01-01T10:00:00Z'),
|
||||
status: 'completed' as const,
|
||||
};
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
const raceNew = {
|
||||
const raceNew = Race.create({
|
||||
id: 'race-new',
|
||||
leagueId: 'league-B',
|
||||
track: 'New Circuit',
|
||||
car: 'GT4',
|
||||
scheduledAt: new Date('2024-02-01T10:00:00Z'),
|
||||
status: 'completed' as const,
|
||||
};
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
const races = [raceOld, raceNew];
|
||||
|
||||
const results = [
|
||||
{
|
||||
Result.create({
|
||||
id: 'result-old',
|
||||
raceId: raceOld.id,
|
||||
driverId,
|
||||
position: 5,
|
||||
fastestLap: 120,
|
||||
incidents: 3,
|
||||
},
|
||||
{
|
||||
startPosition: 5,
|
||||
}),
|
||||
Result.create({
|
||||
id: 'result-new',
|
||||
raceId: raceNew.id,
|
||||
driverId,
|
||||
position: 2,
|
||||
fastestLap: 115,
|
||||
incidents: 1,
|
||||
},
|
||||
startPosition: 2,
|
||||
}),
|
||||
];
|
||||
|
||||
const memberships = [
|
||||
{
|
||||
LeagueMembership.create({
|
||||
leagueId: 'league-A',
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
}),
|
||||
LeagueMembership.create({
|
||||
leagueId: 'league-B',
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const standingsByLeague = new Map<
|
||||
string,
|
||||
Array<{ leagueId: string; driverId: string; position: number; points: number }>
|
||||
Standing[]
|
||||
>();
|
||||
standingsByLeague.set('league-A', [
|
||||
{ leagueId: 'league-A', driverId, position: 3, points: 50 },
|
||||
{ leagueId: 'league-A', driverId: 'other-1', position: 1, points: 80 },
|
||||
Standing.create({ leagueId: 'league-A', driverId, position: 3, points: 50 }),
|
||||
Standing.create({ leagueId: 'league-A', driverId: 'other-1', position: 1, points: 80 }),
|
||||
]);
|
||||
standingsByLeague.set('league-B', [
|
||||
{ leagueId: 'league-B', driverId, position: 1, points: 100 },
|
||||
{ leagueId: 'league-B', driverId: 'other-2', position: 2, points: 90 },
|
||||
Standing.create({ leagueId: 'league-B', driverId, position: 1, points: 100 }),
|
||||
Standing.create({ leagueId: 'league-B', driverId: 'other-2', position: 2, points: 90 }),
|
||||
]);
|
||||
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
const driverRepository = {
|
||||
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => [],
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const raceRepository: {
|
||||
findAll: () => Promise<typeof races>;
|
||||
} = {
|
||||
findAll: async () => races,
|
||||
const raceRepository = {
|
||||
findById: async (): Promise<Race | null> => null,
|
||||
findAll: async (): Promise<Race[]> => races,
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findAll: () => Promise<typeof results>;
|
||||
} = {
|
||||
findAll: async () => results,
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => results,
|
||||
findByRaceId: async (): Promise<Result[]> => [],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findAll: () => Promise<typeof leagues>;
|
||||
} = {
|
||||
findAll: async () => leagues,
|
||||
const leagueRepository = {
|
||||
findById: async (): Promise<League | null> => null,
|
||||
findAll: async (): Promise<League[]> => leagues,
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<Array<{ leagueId: string; driverId: string; position: number; points: number }>>;
|
||||
} = {
|
||||
findByLeagueId: async (leagueId: string) =>
|
||||
const standingRepository = {
|
||||
findByLeagueId: async (leagueId: string): Promise<Standing[]> =>
|
||||
standingsByLeague.get(leagueId) ?? [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
||||
findAll: async (): Promise<Standing[]> => [],
|
||||
save: async (): Promise<Standing> => { throw new Error('Not implemented'); },
|
||||
saveMany: async (): Promise<Standing[]> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByLeagueId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
recalculate: async (): Promise<Standing[]> => [],
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (
|
||||
leagueId: string,
|
||||
driverIdParam: string,
|
||||
) => Promise<{ leagueId: string; driverId: string; status: string } | null>;
|
||||
} = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string) => {
|
||||
const leagueMembershipRepository = {
|
||||
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
||||
return (
|
||||
memberships.find(
|
||||
(m) => m.leagueId === leagueId && m.driverId === driverIdParam,
|
||||
) ?? null
|
||||
);
|
||||
},
|
||||
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
||||
getJoinRequests: async (): Promise<any[]> => [],
|
||||
saveMembership: async (): Promise<LeagueMembership> => { throw new Error('Not implemented'); },
|
||||
removeMembership: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
saveJoinRequest: async (): Promise<any> => { throw new Error('Not implemented'); },
|
||||
removeJoinRequest: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async () => false,
|
||||
const raceRegistrationRepository = {
|
||||
isRegistered: async (): Promise<boolean> => false,
|
||||
getRegisteredDrivers: async (): Promise<string[]> => [],
|
||||
getRegistrationCount: async (): Promise<number> => 0,
|
||||
register: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
withdraw: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
getDriverRegistrations: async (): Promise<string[]> => [],
|
||||
clearRaceRegistrations: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => [],
|
||||
const feedRepository = {
|
||||
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
||||
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => [],
|
||||
const socialRepository = {
|
||||
getFriends: async (): Promise<Driver[]> => [],
|
||||
getFriendIds: async (): Promise<string[]> => [],
|
||||
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
@@ -430,54 +516,100 @@ describe('GetDashboardOverviewUseCase', () => {
|
||||
// Given a driver with no related data
|
||||
const driverId = 'driver-empty';
|
||||
|
||||
const driver = { id: driverId, name: 'New Racer', country: 'FR' };
|
||||
const driver = Driver.create({ id: driverId, iracingId: '11111', name: 'New Racer', country: 'FR' });
|
||||
|
||||
const driverRepository: {
|
||||
findById: (id: string) => Promise<{ id: string; name: string; country: string } | null>;
|
||||
} = {
|
||||
findById: async (id: string) => (id === driver.id ? driver : null),
|
||||
const driverRepository = {
|
||||
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => [],
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const raceRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
const raceRepository = {
|
||||
findById: async (): Promise<Race | null> => null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const resultRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (): Promise<Result[]> => [],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: { findAll: () => Promise<never[]> } = {
|
||||
findAll: async () => [],
|
||||
const leagueRepository = {
|
||||
findById: async (): Promise<League | null> => null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
findByLeagueId: (leagueId: string) => Promise<never[]>;
|
||||
} = {
|
||||
findByLeagueId: async () => [],
|
||||
const standingRepository = {
|
||||
findByLeagueId: async (): Promise<Standing[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
||||
findAll: async (): Promise<Standing[]> => [],
|
||||
save: async (): Promise<Standing> => { throw new Error('Not implemented'); },
|
||||
saveMany: async (): Promise<Standing[]> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByLeagueId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
recalculate: async (): Promise<Standing[]> => [],
|
||||
};
|
||||
|
||||
const leagueMembershipRepository: {
|
||||
getMembership: (leagueId: string, driverIdParam: string) => Promise<null>;
|
||||
} = {
|
||||
getMembership: async () => null,
|
||||
const leagueMembershipRepository = {
|
||||
getMembership: async (): Promise<LeagueMembership | null> => null,
|
||||
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
||||
getJoinRequests: async (): Promise<any[]> => [],
|
||||
saveMembership: async (): Promise<LeagueMembership> => { throw new Error('Not implemented'); },
|
||||
removeMembership: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
saveJoinRequest: async (): Promise<any> => { throw new Error('Not implemented'); },
|
||||
removeJoinRequest: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const raceRegistrationRepository: {
|
||||
isRegistered: (raceId: string, driverIdParam: string) => Promise<boolean>;
|
||||
} = {
|
||||
isRegistered: async () => false,
|
||||
const raceRegistrationRepository = {
|
||||
isRegistered: async (): Promise<boolean> => false,
|
||||
getRegisteredDrivers: async (): Promise<string[]> => [],
|
||||
getRegistrationCount: async (): Promise<number> => 0,
|
||||
register: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
withdraw: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
getDriverRegistrations: async (): Promise<string[]> => [],
|
||||
clearRaceRegistrations: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
};
|
||||
|
||||
const feedRepository: {
|
||||
getFeedForDriver: (driverIdParam: string) => Promise<DashboardFeedItemSummaryViewModel[]>;
|
||||
} = {
|
||||
getFeedForDriver: async () => [],
|
||||
const feedRepository = {
|
||||
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
||||
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
||||
};
|
||||
|
||||
const socialRepository: {
|
||||
getFriends: (driverIdParam: string) => Promise<Array<{ id: string }>>;
|
||||
} = {
|
||||
getFriends: async () => [],
|
||||
const socialRepository = {
|
||||
getFriends: async (): Promise<Driver[]> => [],
|
||||
getFriendIds: async (): Promise<string[]> => [],
|
||||
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
||||
};
|
||||
|
||||
const imageService = createTestImageService();
|
||||
|
||||
@@ -2,10 +2,10 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
||||
|
||||
import { JoinLeagueUseCase } from '@gridpilot/racing/application/use-cases/JoinLeagueUseCase';
|
||||
import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
|
||||
import type {
|
||||
import {
|
||||
LeagueMembership,
|
||||
MembershipRole,
|
||||
MembershipStatus,
|
||||
type MembershipRole,
|
||||
type MembershipStatus,
|
||||
} from '@gridpilot/racing/domain/entities/LeagueMembership';
|
||||
|
||||
class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository {
|
||||
@@ -109,13 +109,13 @@ describe('Membership use-cases', () => {
|
||||
const leagueId = 'league-1';
|
||||
const driverId = 'driver-1';
|
||||
|
||||
repository.seedMembership({
|
||||
repository.seedMembership(LeagueMembership.create({
|
||||
leagueId,
|
||||
driverId,
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
joinedAt: new Date('2024-01-01'),
|
||||
});
|
||||
}));
|
||||
|
||||
await expect(
|
||||
useCase.execute({ leagueId, driverId }),
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import { League } from '@gridpilot/racing/domain/entities/League';
|
||||
import { Result } from '@gridpilot/racing/domain/entities/Result';
|
||||
import { Penalty } from '@gridpilot/racing/domain/entities/Penalty';
|
||||
import { Standing } from '@gridpilot/racing/domain/entities/Standing';
|
||||
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
||||
|
||||
import { GetRaceResultsDetailUseCase } from '@gridpilot/racing/application/use-cases/GetRaceResultsDetailUseCase';
|
||||
import { ImportRaceResultsUseCase } from '@gridpilot/racing/application/use-cases/ImportRaceResultsUseCase';
|
||||
@@ -77,37 +79,64 @@ describe('ImportRaceResultsUseCase', () => {
|
||||
let existsByRaceIdCalled = false;
|
||||
const recalcCalls: string[] = [];
|
||||
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
const raceRepository = {
|
||||
findById: async (id: string): Promise<Race | null> => races.get(id) ?? null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
const leagueRepository = {
|
||||
findById: async (id: string): Promise<League | null> => leagues.get(id) ?? null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
existsByRaceId: (raceId: string) => Promise<boolean>;
|
||||
createMany: (results: Result[]) => Promise<Result[]>;
|
||||
} = {
|
||||
existsByRaceId: async (raceId: string) => {
|
||||
existsByRaceIdCalled = true;
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
},
|
||||
createMany: async (results: Result[]) => {
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (): Promise<Result[]> => [],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (results: Result[]): Promise<Result[]> => {
|
||||
storedResults.push(...results);
|
||||
return results;
|
||||
},
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (raceId: string): Promise<boolean> => {
|
||||
existsByRaceIdCalled = true;
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
},
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
recalculate: (leagueId: string) => Promise<void>;
|
||||
} = {
|
||||
recalculate: async (leagueId: string) => {
|
||||
const standingRepository = {
|
||||
findByLeagueId: async (): Promise<Standing[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
||||
findAll: async (): Promise<Standing[]> => [],
|
||||
save: async (): Promise<Standing> => { throw new Error('Not implemented'); },
|
||||
saveMany: async (): Promise<Standing[]> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByLeagueId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
recalculate: async (leagueId: string): Promise<Standing[]> => {
|
||||
recalcCalls.push(leagueId);
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -196,34 +225,60 @@ describe('ImportRaceResultsUseCase', () => {
|
||||
}),
|
||||
];
|
||||
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
const raceRepository = {
|
||||
findById: async (id: string): Promise<Race | null> => races.get(id) ?? null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
const leagueRepository = {
|
||||
findById: async (id: string): Promise<League | null> => leagues.get(id) ?? null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
existsByRaceId: (raceId: string) => Promise<boolean>;
|
||||
createMany: (results: Result[]) => Promise<Result[]>;
|
||||
} = {
|
||||
existsByRaceId: async (raceId: string) => {
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
},
|
||||
createMany: async (_results: Result[]) => {
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (): Promise<Result[]> => [],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (_results: Result[]): Promise<Result[]> => {
|
||||
throw new Error('Should not be called when results already exist');
|
||||
},
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (raceId: string): Promise<boolean> => {
|
||||
return storedResults.some((r) => r.raceId === raceId);
|
||||
},
|
||||
};
|
||||
|
||||
const standingRepository: {
|
||||
recalculate: (leagueId: string) => Promise<void>;
|
||||
} = {
|
||||
recalculate: async (_leagueId: string) => {
|
||||
const standingRepository = {
|
||||
findByLeagueId: async (): Promise<Standing[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
||||
findAll: async (): Promise<Standing[]> => [],
|
||||
save: async (): Promise<Standing> => { throw new Error('Not implemented'); },
|
||||
saveMany: async (): Promise<Standing[]> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByLeagueId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
recalculate: async (_leagueId: string): Promise<Standing[]> => {
|
||||
throw new Error('Should not be called when results already exist');
|
||||
},
|
||||
};
|
||||
@@ -315,35 +370,68 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
const results = [result1, result2];
|
||||
const drivers = [driver1, driver2];
|
||||
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
const raceRepository = {
|
||||
findById: async (id: string): Promise<Race | null> => races.get(id) ?? null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
const leagueRepository = {
|
||||
findById: async (id: string): Promise<League | null> => leagues.get(id) ?? null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (raceId: string): Promise<Result[]> =>
|
||||
results.filter((r) => r.raceId === raceId),
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => drivers,
|
||||
const driverRepository = {
|
||||
findById: async (): Promise<Driver | null> => null,
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => drivers.map(d => Driver.create({ id: d.id, iracingId: '123', name: d.name, country: d.country })),
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Penalty[],
|
||||
const penaltyRepository = {
|
||||
findById: async (): Promise<Penalty | null> => null,
|
||||
findByRaceId: async (): Promise<Penalty[]> => [] as Penalty[],
|
||||
findByDriverId: async (): Promise<Penalty[]> => [],
|
||||
findByProtestId: async (): Promise<Penalty[]> => [],
|
||||
findPending: async (): Promise<Penalty[]> => [],
|
||||
findIssuedBy: async (): Promise<Penalty[]> => [],
|
||||
create: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
@@ -354,7 +442,6 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
resultRepository,
|
||||
driverRepository,
|
||||
penaltyRepository,
|
||||
presenter,
|
||||
);
|
||||
|
||||
// When executing the query
|
||||
@@ -364,8 +451,8 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
expect(viewModel).not.toBeNull();
|
||||
|
||||
// Then points system matches the default F1-style configuration
|
||||
expect(viewModel!.pointsSystem[1]).toBe(25);
|
||||
expect(viewModel!.pointsSystem[2]).toBe(18);
|
||||
expect(viewModel!.pointsSystem?.[1]).toBe(25);
|
||||
expect(viewModel!.pointsSystem?.[2]).toBe(18);
|
||||
|
||||
// And fastest lap is identified correctly
|
||||
expect(viewModel!.fastestLapTime).toBeCloseTo(88.456, 3);
|
||||
@@ -408,6 +495,7 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
|
||||
const penalty = Penalty.create({
|
||||
id: 'pen-1',
|
||||
leagueId: league.id,
|
||||
raceId: race.id,
|
||||
driverId: driver.id,
|
||||
type: 'points_deduction',
|
||||
@@ -424,36 +512,69 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
const drivers = [driver];
|
||||
const penalties = [penalty];
|
||||
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async (id: string) => races.get(id) ?? null,
|
||||
const raceRepository = {
|
||||
findById: async (id: string): Promise<Race | null> => races.get(id) ?? null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async (id: string) => leagues.get(id) ?? null,
|
||||
const leagueRepository = {
|
||||
findById: async (id: string): Promise<League | null> => leagues.get(id) ?? null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (raceId: string): Promise<Result[]> =>
|
||||
results.filter((r) => r.raceId === raceId),
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => drivers,
|
||||
const driverRepository = {
|
||||
findById: async (): Promise<Driver | null> => null,
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => drivers.map(d => Driver.create({ id: d.id, iracingId: '123', name: d.name, country: d.country })),
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async (raceId: string) =>
|
||||
const penaltyRepository = {
|
||||
findById: async (): Promise<Penalty | null> => null,
|
||||
findByRaceId: async (raceId: string): Promise<Penalty[]> =>
|
||||
penalties.filter((p) => p.raceId === raceId),
|
||||
findByDriverId: async (): Promise<Penalty[]> => [],
|
||||
findByProtestId: async (): Promise<Penalty[]> => [],
|
||||
findPending: async (): Promise<Penalty[]> => [],
|
||||
findIssuedBy: async (): Promise<Penalty[]> => [],
|
||||
create: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
@@ -464,7 +585,6 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
resultRepository,
|
||||
driverRepository,
|
||||
penaltyRepository,
|
||||
presenter,
|
||||
);
|
||||
|
||||
// When
|
||||
@@ -491,34 +611,67 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
|
||||
it('presents an error when race does not exist', async () => {
|
||||
// Given repositories without the requested race
|
||||
const raceRepository: {
|
||||
findById: (id: string) => Promise<Race | null>;
|
||||
} = {
|
||||
findById: async () => null,
|
||||
const raceRepository = {
|
||||
findById: async (): Promise<Race | null> => null,
|
||||
findAll: async (): Promise<Race[]> => [],
|
||||
findByLeagueId: async (): Promise<Race[]> => [],
|
||||
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
||||
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
||||
findByStatus: async (): Promise<Race[]> => [],
|
||||
findByDateRange: async (): Promise<Race[]> => [],
|
||||
create: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Race> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const leagueRepository: {
|
||||
findById: (id: string) => Promise<League | null>;
|
||||
} = {
|
||||
findById: async () => null,
|
||||
const leagueRepository = {
|
||||
findById: async (): Promise<League | null> => null,
|
||||
findAll: async (): Promise<League[]> => [],
|
||||
findByOwnerId: async (): Promise<League[]> => [],
|
||||
create: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<League> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
searchByName: async (): Promise<League[]> => [],
|
||||
};
|
||||
|
||||
const resultRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Result[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Result[],
|
||||
const resultRepository = {
|
||||
findById: async (): Promise<Result | null> => null,
|
||||
findAll: async (): Promise<Result[]> => [],
|
||||
findByRaceId: async (): Promise<Result[]> => [] as Result[],
|
||||
findByDriverId: async (): Promise<Result[]> => [],
|
||||
findByDriverIdAndLeagueId: async (): Promise<Result[]> => [],
|
||||
create: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
createMany: async (): Promise<Result[]> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Result> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
deleteByRaceId: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByRaceId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const driverRepository: {
|
||||
findAll: () => Promise<Array<{ id: string; name: string; country: string }>>;
|
||||
} = {
|
||||
findAll: async () => [],
|
||||
const driverRepository = {
|
||||
findById: async (): Promise<Driver | null> => null,
|
||||
findByIRacingId: async (): Promise<Driver | null> => null,
|
||||
findAll: async (): Promise<Driver[]> => [],
|
||||
create: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<Driver> => { throw new Error('Not implemented'); },
|
||||
delete: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
existsByIRacingId: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const penaltyRepository: {
|
||||
findByRaceId: (raceId: string) => Promise<Penalty[]>;
|
||||
} = {
|
||||
findByRaceId: async () => [] as Penalty[],
|
||||
const penaltyRepository = {
|
||||
findById: async (): Promise<Penalty | null> => null,
|
||||
findByRaceId: async (): Promise<Penalty[]> => [] as Penalty[],
|
||||
findByDriverId: async (): Promise<Penalty[]> => [],
|
||||
findByProtestId: async (): Promise<Penalty[]> => [],
|
||||
findPending: async (): Promise<Penalty[]> => [],
|
||||
findIssuedBy: async (): Promise<Penalty[]> => [],
|
||||
create: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
update: async (): Promise<void> => { throw new Error('Not implemented'); },
|
||||
exists: async (): Promise<boolean> => false,
|
||||
};
|
||||
|
||||
const presenter = new FakeRaceResultsDetailPresenter();
|
||||
@@ -529,7 +682,6 @@ describe('GetRaceResultsDetailUseCase', () => {
|
||||
resultRepository,
|
||||
driverRepository,
|
||||
penaltyRepository,
|
||||
presenter,
|
||||
);
|
||||
|
||||
// When
|
||||
|
||||
@@ -9,13 +9,14 @@ import {
|
||||
LeagueMembership,
|
||||
type MembershipStatus,
|
||||
} from '@gridpilot/racing/domain/entities/LeagueMembership';
|
||||
import { Team } from '@gridpilot/racing/domain/entities/Team';
|
||||
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
||||
import type {
|
||||
Team,
|
||||
TeamMembership,
|
||||
TeamMembershipStatus,
|
||||
TeamRole,
|
||||
TeamJoinRequest,
|
||||
} from '@gridpilot/racing/domain/entities/Team';
|
||||
} from '@gridpilot/racing/domain/types/TeamMembership';
|
||||
|
||||
import { RegisterForRaceUseCase } from '@gridpilot/racing/application/use-cases/RegisterForRaceUseCase';
|
||||
import { WithdrawFromRaceUseCase } from '@gridpilot/racing/application/use-cases/WithdrawFromRaceUseCase';
|
||||
@@ -169,10 +170,23 @@ class TestDriverRegistrationStatusPresenter implements IDriverRegistrationStatus
|
||||
raceId: string | null = null;
|
||||
driverId: string | null = null;
|
||||
|
||||
present(isRegistered: boolean, raceId: string, driverId: string): void {
|
||||
present(isRegistered: boolean, raceId: string, driverId: string) {
|
||||
this.isRegistered = isRegistered;
|
||||
this.raceId = raceId;
|
||||
this.driverId = driverId;
|
||||
return {
|
||||
isRegistered,
|
||||
raceId,
|
||||
driverId,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel() {
|
||||
return {
|
||||
isRegistered: this.isRegistered!,
|
||||
raceId: this.raceId!,
|
||||
driverId: this.driverId!,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +199,20 @@ class TestRaceRegistrationsPresenter implements IRaceRegistrationsPresenter {
|
||||
this.driverIds = [];
|
||||
}
|
||||
|
||||
present(input: RaceRegistrationsResultDTO): void {
|
||||
present(input: RaceRegistrationsResultDTO) {
|
||||
this.driverIds = input.registeredDriverIds;
|
||||
this.raceId = null;
|
||||
return {
|
||||
registeredDriverIds: input.registeredDriverIds,
|
||||
count: input.registeredDriverIds.length,
|
||||
};
|
||||
}
|
||||
|
||||
getViewModel() {
|
||||
return {
|
||||
registeredDriverIds: this.driverIds,
|
||||
count: this.driverIds.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,8 +436,43 @@ describe('Racing application use-cases - teams', () => {
|
||||
let getDriverTeamUseCase: GetDriverTeamUseCase;
|
||||
|
||||
class FakeDriverRepository {
|
||||
async findById(driverId: string): Promise<{ id: string; name: string } | null> {
|
||||
return { id: driverId, name: `Driver ${driverId}` };
|
||||
async findById(driverId: string): Promise<Driver | null> {
|
||||
return Driver.create({ id: driverId, iracingId: '123', name: `Driver ${driverId}`, country: 'US' });
|
||||
}
|
||||
|
||||
async findByIRacingId(id: string): Promise<Driver | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async findAll(): Promise<Driver[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async create(driver: Driver): Promise<Driver> {
|
||||
return driver;
|
||||
}
|
||||
|
||||
async update(driver: Driver): Promise<Driver> {
|
||||
return driver;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
}
|
||||
|
||||
async exists(id: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async existsByIRacingId(iracingId: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async findByLeagueId(leagueId: string): Promise<Driver[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async findByTeamId(teamId: string): Promise<Driver[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +480,18 @@ describe('Racing application use-cases - teams', () => {
|
||||
getDriverAvatar(driverId: string): string {
|
||||
return `https://example.com/avatar/${driverId}.png`;
|
||||
}
|
||||
|
||||
getTeamLogo(teamId: string): string {
|
||||
return `https://example.com/logo/${teamId}.png`;
|
||||
}
|
||||
|
||||
getLeagueCover(leagueId: string): string {
|
||||
return `https://example.com/cover/${leagueId}.png`;
|
||||
}
|
||||
|
||||
getLeagueLogo(leagueId: string): string {
|
||||
return `https://example.com/logo/${leagueId}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
class TestAllTeamsPresenter implements IAllTeamsPresenter {
|
||||
@@ -438,9 +510,9 @@ describe('Racing application use-cases - teams', () => {
|
||||
description: team.description,
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
specialization: (team as any).specialization,
|
||||
region: (team as any).region,
|
||||
languages: (team as any).languages,
|
||||
})),
|
||||
totalCount: input.teams.length,
|
||||
};
|
||||
@@ -487,7 +559,7 @@ describe('Racing application use-cases - teams', () => {
|
||||
return {
|
||||
driverId,
|
||||
driverName,
|
||||
role: membership.role,
|
||||
role: ((membership.role as any) === 'owner' ? 'owner' : (membership.role as any) === 'member' ? 'member' : (membership.role as any) === 'manager' ? 'manager' : (membership.role as any) === 'driver' ? 'member' : 'member') as "owner" | "member" | "manager",
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isActive: membership.status === 'active',
|
||||
avatarUrl,
|
||||
@@ -496,7 +568,7 @@ describe('Racing application use-cases - teams', () => {
|
||||
|
||||
const ownerCount = members.filter((m) => m.role === 'owner').length;
|
||||
const managerCount = members.filter((m) => m.role === 'manager').length;
|
||||
const memberCount = members.filter((m) => m.role === 'member').length;
|
||||
const memberCount = members.filter((m) => (m.role as any) === 'member').length;
|
||||
|
||||
this.viewModel = {
|
||||
members,
|
||||
@@ -534,7 +606,7 @@ describe('Racing application use-cases - teams', () => {
|
||||
driverId,
|
||||
driverName,
|
||||
teamId: request.teamId,
|
||||
status: 'pending',
|
||||
status: 'pending' as const,
|
||||
requestedAt: request.requestedAt.toISOString(),
|
||||
avatarUrl,
|
||||
};
|
||||
@@ -559,7 +631,7 @@ describe('Racing application use-cases - teams', () => {
|
||||
}
|
||||
|
||||
class TestDriverTeamPresenter implements IDriverTeamPresenter {
|
||||
private viewModel: DriverTeamViewModel | null = null;
|
||||
viewModel: DriverTeamViewModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.viewModel = null;
|
||||
@@ -579,12 +651,9 @@ describe('Racing application use-cases - teams', () => {
|
||||
description: team.description,
|
||||
ownerId: team.ownerId,
|
||||
leagues: team.leagues,
|
||||
specialization: team.specialization,
|
||||
region: team.region,
|
||||
languages: team.languages,
|
||||
},
|
||||
membership: {
|
||||
role: membership.role,
|
||||
role: (membership.role === 'owner' || membership.role === 'manager') ? membership.role : 'member' as "owner" | "member" | "manager",
|
||||
joinedAt: membership.joinedAt.toISOString(),
|
||||
isActive: membership.status === 'active',
|
||||
},
|
||||
@@ -619,19 +688,17 @@ describe('Racing application use-cases - teams', () => {
|
||||
getAllTeamsUseCase = new GetAllTeamsUseCase(
|
||||
teamRepo,
|
||||
membershipRepo,
|
||||
allTeamsPresenter,
|
||||
);
|
||||
|
||||
teamDetailsPresenter = new TestTeamDetailsPresenter();
|
||||
getTeamDetailsUseCase = new GetTeamDetailsUseCase(
|
||||
teamRepo,
|
||||
membershipRepo,
|
||||
teamDetailsPresenter,
|
||||
);
|
||||
|
||||
const driverRepository = new FakeDriverRepository();
|
||||
const imageService = new FakeImageService();
|
||||
|
||||
|
||||
teamMembersPresenter = new TestTeamMembersPresenter();
|
||||
getTeamMembersUseCase = new GetTeamMembersUseCase(
|
||||
membershipRepo,
|
||||
|
||||
@@ -75,13 +75,13 @@ function extractImportModule(line: string): string | null {
|
||||
// Handle: import ... from 'x';
|
||||
const fromMatch = trimmed.match(/from\s+['"](.*)['"]/);
|
||||
if (fromMatch) {
|
||||
return fromMatch[1];
|
||||
return fromMatch[1] || null;
|
||||
}
|
||||
|
||||
// Handle: import 'x';
|
||||
const sideEffectMatch = trimmed.match(/^import\s+['"](.*)['"]\s*;?$/);
|
||||
if (sideEffectMatch) {
|
||||
return sideEffectMatch[1];
|
||||
return sideEffectMatch[1] || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -45,8 +45,8 @@ vi.mock('../../../apps/website/lib/auth/AuthContext', () => {
|
||||
refreshSession: async () => {},
|
||||
});
|
||||
|
||||
const AuthProvider = ({ value, children }: { value: any; children: React.ReactNode }) => (
|
||||
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||
const AuthProvider = ({ initialSession, children }: { initialSession?: any; children: React.ReactNode }) => (
|
||||
<AuthContext.Provider value={{ session: initialSession, loading: false, login: () => {}, logout: async () => {}, refreshSession: async () => {} }}>{children}</AuthContext.Provider>
|
||||
);
|
||||
|
||||
const useAuth = () => React.useContext(AuthContext);
|
||||
@@ -65,13 +65,7 @@ describe('AlphaNav', () => {
|
||||
it('hides Dashboard link and uses Home when unauthenticated', () => {
|
||||
render(
|
||||
<AuthProvider
|
||||
value={{
|
||||
session: null,
|
||||
loading: false,
|
||||
login: () => {},
|
||||
logout: async () => {},
|
||||
refreshSession: async () => {},
|
||||
}}
|
||||
initialSession={null}
|
||||
>
|
||||
<AlphaNav />
|
||||
</AuthProvider>,
|
||||
@@ -87,14 +81,11 @@ describe('AlphaNav', () => {
|
||||
it('shows Dashboard link and hides Home when authenticated', () => {
|
||||
render(
|
||||
<AuthProvider
|
||||
value={{
|
||||
session: {
|
||||
user: { id: 'user-1' },
|
||||
},
|
||||
loading: false,
|
||||
login: () => {},
|
||||
logout: async () => {},
|
||||
refreshSession: async () => {},
|
||||
initialSession={{
|
||||
user: { id: 'user-1', displayName: 'Test User' },
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: Date.now() + 3600000,
|
||||
token: 'fake-token',
|
||||
}}
|
||||
>
|
||||
<AlphaNav />
|
||||
|
||||
@@ -35,7 +35,9 @@ describe('iRacing auth route handlers', () => {
|
||||
expect(location).toMatch(/state=/);
|
||||
|
||||
expect(cookieStore.set).toHaveBeenCalled();
|
||||
const [name] = cookieStore.set.mock.calls[0];
|
||||
const call = cookieStore.set.mock.calls[0];
|
||||
expect(call).toBeDefined();
|
||||
const [name] = call as [string, string];
|
||||
expect(name).toBe('gp_demo_auth_state');
|
||||
});
|
||||
|
||||
@@ -58,7 +60,9 @@ describe('iRacing auth route handlers', () => {
|
||||
expect(location).toBe('http://localhost/dashboard');
|
||||
|
||||
expect(cookieStore.set).toHaveBeenCalled();
|
||||
const [sessionName, sessionValue] = cookieStore.set.mock.calls[0];
|
||||
const call = cookieStore.set.mock.calls[0];
|
||||
expect(call).toBeDefined();
|
||||
const [sessionName, sessionValue] = call as [string, string];
|
||||
expect(sessionName).toBe('gp_demo_session');
|
||||
expect(typeof sessionValue).toBe('string');
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('getAppMode', () => {
|
||||
it('falls back to "pre-launch" and logs when NEXT_PUBLIC_GRIDPILOT_MODE is invalid in production', () => {
|
||||
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode';
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode' as any;
|
||||
|
||||
const mode = getAppMode();
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('getAppMode', () => {
|
||||
|
||||
it('throws in development when NEXT_PUBLIC_GRIDPILOT_MODE is invalid', () => {
|
||||
(process.env as any).NODE_ENV = 'development';
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode';
|
||||
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'invalid-mode' as any;
|
||||
|
||||
expect(() => getAppMode()).toThrowError(/Invalid NEXT_PUBLIC_GRIDPILOT_MODE/);
|
||||
});
|
||||
|
||||
@@ -103,7 +103,9 @@ describe('CreateLeaguePage - URL-bound wizard steps', () => {
|
||||
fireEvent.click(backButton);
|
||||
|
||||
expect(routerInstance.push).toHaveBeenCalledTimes(1);
|
||||
const callArg = routerInstance.push.mock.calls[0][0] as string;
|
||||
const call = routerInstance.push.mock.calls[0];
|
||||
expect(call).toBeDefined();
|
||||
const callArg = (call as [string])[0];
|
||||
expect(callArg).toContain('/leagues/create');
|
||||
expect(callArg).toContain('step=structure');
|
||||
});
|
||||
|
||||
@@ -6,12 +6,12 @@ type RateLimitResult = {
|
||||
resetAt: number;
|
||||
};
|
||||
|
||||
const mockCheckRateLimit = vi.fn<[], Promise<RateLimitResult>>();
|
||||
const mockGetClientIp = vi.fn<[], string>();
|
||||
const mockCheckRateLimit = vi.fn(() => Promise.resolve({ allowed: true, remaining: 4, resetAt: 0 }));
|
||||
const mockGetClientIp = vi.fn(() => '127.0.0.1');
|
||||
|
||||
vi.mock('../../../apps/website/lib/rate-limit', () => ({
|
||||
checkRateLimit: (...args: unknown[]) => mockCheckRateLimit(...(args as [])),
|
||||
getClientIp: (..._args: unknown[]) => mockGetClientIp(),
|
||||
checkRateLimit: mockCheckRateLimit,
|
||||
getClientIp: mockGetClientIp,
|
||||
}));
|
||||
|
||||
async function getPostHandler() {
|
||||
|
||||
Reference in New Issue
Block a user