import type { AutomationEnginePort } from '../../../../application/ports/AutomationEnginePort'; import type { IBrowserAutomation } from '../../../../application/ports/ScreenAutomationPort'; import type { SessionRepositoryPort } from '../../../../application/ports/SessionRepositoryPort'; import { StepTransitionValidator } from '../../../../domain/services/StepTransitionValidator'; import type { HostedSessionConfig } from '../../../../domain/types/HostedSessionConfig'; import { StepId } from '../../../../domain/value-objects/StepId'; type ValidationResult = { isValid: boolean; error?: string; }; export class MockAutomationEngineAdapter implements AutomationEnginePort { private isRunning = false; constructor( public readonly browserAutomation: IBrowserAutomation, private readonly sessionRepository: SessionRepositoryPort ) {} private toStepConfig(config: HostedSessionConfig): Record { const baseConfig: Record = { sessionName: config.sessionName, trackId: config.trackId, carIds: [...config.carIds], }; if (config.serverName !== undefined) baseConfig.serverName = config.serverName; if (config.password !== undefined) baseConfig.password = config.password; if (config.adminPassword !== undefined) baseConfig.adminPassword = config.adminPassword; if (config.maxDrivers !== undefined) baseConfig.maxDrivers = config.maxDrivers; if (config.carSearch !== undefined) baseConfig.carSearch = config.carSearch; if (config.trackSearch !== undefined) baseConfig.trackSearch = config.trackSearch; if (config.weatherType !== undefined) baseConfig.weatherType = config.weatherType; if (config.timeOfDay !== undefined) baseConfig.timeOfDay = config.timeOfDay; if (config.sessionDuration !== undefined) baseConfig.sessionDuration = config.sessionDuration; if (config.practiceLength !== undefined) baseConfig.practiceLength = config.practiceLength; if (config.qualifyingLength !== undefined) baseConfig.qualifyingLength = config.qualifyingLength; if (config.warmupLength !== undefined) baseConfig.warmupLength = config.warmupLength; if (config.raceLength !== undefined) baseConfig.raceLength = config.raceLength; if (config.startType !== undefined) baseConfig.startType = config.startType; if (config.restarts !== undefined) baseConfig.restarts = config.restarts; if (config.damageModel !== undefined) baseConfig.damageModel = config.damageModel; if (config.trackState !== undefined) baseConfig.trackState = config.trackState; return baseConfig; } async validateConfiguration(config: HostedSessionConfig): Promise { if (!config.sessionName || config.sessionName.trim() === '') { return { isValid: false, error: 'Session name is required' }; } if (!config.trackId || config.trackId.trim() === '') { return { isValid: false, error: 'Track ID is required' }; } if (!config.carIds || config.carIds.length === 0) { return { isValid: false, error: 'At least one car must be selected' }; } return { isValid: true }; } async executeStep(stepId: StepId, config: HostedSessionConfig): Promise { const sessions = await this.sessionRepository.findAll(); const session = sessions[0]; if (!session) { throw new Error('No active session found'); } // Start session if it's at step 1 and pending if (session.state.isPending() && stepId.value === 1) { session.start(); await this.sessionRepository.update(session); // Start automated progression this.startAutomation(config); } } private startAutomation(config: HostedSessionConfig): void { if (this.isRunning) { return; } this.isRunning = true; } private async runAutomationLoop(config: HostedSessionConfig): Promise { while (this.isRunning) { try { const sessions = await this.sessionRepository.findAll(); const session = sessions[0]; if (!session || !session.state.isInProgress()) { this.isRunning = false; return; } const currentStep = session.currentStep; // Execute current step using the browser automation if (this.browserAutomation.executeStep) { const result = await this.browserAutomation.executeStep( currentStep, this.toStepConfig(config), ); if (!result.success) { const stepDescription = StepTransitionValidator.getStepDescription(currentStep); const errorMessage = `Step ${currentStep.value} (${stepDescription}) failed: ${result.error}`; console.error(errorMessage); // Stop automation and mark session as failed this.isRunning = false; session.fail(errorMessage); await this.sessionRepository.update(session); return; } } else { // Fallback for adapters without executeStep (e.g., MockBrowserAutomationAdapter) await this.browserAutomation.navigateToPage(`step-${currentStep.value}`); } // Transition to next step if (!currentStep.isFinalStep()) { session.transitionToStep(currentStep.next()); await this.sessionRepository.update(session); // If we just transitioned to the final step, execute it before stopping const nextStep = session.currentStep; if (nextStep.isFinalStep()) { // Execute final step handler if (this.browserAutomation.executeStep) { const result = await this.browserAutomation.executeStep( nextStep, this.toStepConfig(config), ); if (!result.success) { const stepDescription = StepTransitionValidator.getStepDescription(nextStep); const errorMessage = `Step ${nextStep.value} (${stepDescription}) failed: ${result.error}`; console.error(errorMessage); // Don't try to fail terminal session - just log the error // Session is already in STOPPED_AT_STEP_18 state after transitionToStep() } } // Stop after final step this.isRunning = false; return; } } else { // Current step is already final - stop this.isRunning = false; return; } // Wait before next iteration await this.delay(500); } catch (error) { console.error('Automation error:', error); this.isRunning = false; try { const sessions = await this.sessionRepository.findAll(); const session = sessions[0]; if (session && !session.state.isTerminal()) { const message = error instanceof Error ? error.message : String(error); session.fail(`Automation error: ${message}`); await this.sessionRepository.update(session); } } catch { } return; } } } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } public stopAutomation(): void { this.isRunning = false; } }