feat(companion): implement hosted session automation POC with TDD approach

This commit is contained in:
2025-11-21 16:27:15 +01:00
parent 7a3562a844
commit 098bfc2c11
26 changed files with 6469 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
import { StepId } from '../../../packages/domain/value-objects/StepId';
import { HostedSessionConfig } from '../../../packages/domain/entities/HostedSessionConfig';
import { IBrowserAutomation } from '../../../packages/application/ports/IBrowserAutomation';
interface MockConfig {
simulateFailures?: boolean;
failureRate?: number;
}
interface StepExecutionResult {
success: boolean;
stepId: number;
wasModalStep?: boolean;
shouldStop?: boolean;
executionTime: number;
metrics: {
totalDelay: number;
operationCount: number;
};
}
interface NavigationResult {
success: boolean;
url: string;
simulatedDelay: number;
}
interface FormFillResult {
success: boolean;
fieldName: string;
value: string;
simulatedDelay: number;
}
interface ClickResult {
success: boolean;
selector: string;
simulatedDelay: number;
}
interface WaitResult {
success: boolean;
selector: string;
simulatedDelay: number;
}
interface ModalResult {
success: boolean;
stepId: number;
action: string;
simulatedDelay: number;
}
export class MockBrowserAutomationAdapter implements IBrowserAutomation {
private config: MockConfig;
constructor(config: MockConfig = {}) {
this.config = {
simulateFailures: config.simulateFailures ?? false,
failureRate: config.failureRate ?? 0.1,
};
}
async navigateToPage(url: string): Promise<NavigationResult> {
const delay = this.randomDelay(200, 800);
await this.sleep(delay);
return {
success: true,
url,
simulatedDelay: delay,
};
}
async fillFormField(fieldName: string, value: string): Promise<FormFillResult> {
const delay = this.randomDelay(100, 500);
await this.sleep(delay);
return {
success: true,
fieldName,
value,
simulatedDelay: delay,
};
}
async clickElement(selector: string): Promise<ClickResult> {
const delay = this.randomDelay(50, 300);
await this.sleep(delay);
return {
success: true,
selector,
simulatedDelay: delay,
};
}
async waitForElement(selector: string, maxWaitMs: number = 5000): Promise<WaitResult> {
const delay = this.randomDelay(100, 1000);
await this.sleep(delay);
return {
success: true,
selector,
simulatedDelay: delay,
};
}
async handleModal(stepId: StepId, action: string): Promise<ModalResult> {
if (!stepId.isModalStep()) {
throw new Error(`Step ${stepId.value} is not a modal step`);
}
const delay = this.randomDelay(200, 600);
await this.sleep(delay);
return {
success: true,
stepId: stepId.value,
action,
simulatedDelay: delay,
};
}
async executeStep(stepId: StepId, config: HostedSessionConfig): Promise<StepExecutionResult> {
if (this.shouldSimulateFailure()) {
throw new Error(`Simulated failure at step ${stepId.value}`);
}
const startTime = Date.now();
let totalDelay = 0;
let operationCount = 0;
const navigationDelay = this.randomDelay(200, 500);
await this.sleep(navigationDelay);
totalDelay += navigationDelay;
operationCount++;
if (stepId.isModalStep()) {
const modalDelay = this.randomDelay(200, 400);
await this.sleep(modalDelay);
totalDelay += modalDelay;
operationCount++;
}
const executionTime = Date.now() - startTime;
return {
success: true,
stepId: stepId.value,
wasModalStep: stepId.isModalStep(),
shouldStop: stepId.isFinalStep(),
executionTime,
metrics: {
totalDelay,
operationCount,
},
};
}
private randomDelay(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
private async sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private shouldSimulateFailure(): boolean {
if (!this.config.simulateFailures) {
return false;
}
return Math.random() < (this.config.failureRate || 0.1);
}
}

View File

@@ -0,0 +1,36 @@
import { AutomationSession } from '../../packages/domain/entities/AutomationSession';
import { SessionStateValue } from '../../packages/domain/value-objects/SessionState';
import { ISessionRepository } from '../../packages/application/ports/ISessionRepository';
export class InMemorySessionRepository implements ISessionRepository {
private sessions: Map<string, AutomationSession> = new Map();
async save(session: AutomationSession): Promise<void> {
this.sessions.set(session.id, session);
}
async findById(id: string): Promise<AutomationSession | null> {
return this.sessions.get(id) || null;
}
async update(session: AutomationSession): Promise<void> {
if (!this.sessions.has(session.id)) {
throw new Error('Session not found');
}
this.sessions.set(session.id, session);
}
async delete(id: string): Promise<void> {
this.sessions.delete(id);
}
async findAll(): Promise<AutomationSession[]> {
return Array.from(this.sessions.values());
}
async findByState(state: SessionStateValue): Promise<AutomationSession[]> {
return Array.from(this.sessions.values()).filter(
session => session.state.value === state
);
}
}