wip
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user