feat(companion): implement hosted session automation POC with TDD approach
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
36
src/infrastructure/repositories/InMemorySessionRepository.ts
Normal file
36
src/infrastructure/repositories/InMemorySessionRepository.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user