184 lines
7.2 KiB
TypeScript
184 lines
7.2 KiB
TypeScript
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<string, unknown> {
|
|
const baseConfig: Record<string, unknown> = {
|
|
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<ValidationResult> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
public stopAutomation(): void {
|
|
this.isRunning = false;
|
|
}
|
|
} |