docs: enterprise refactor plan with ralph specs
This commit is contained in:
196
tests/core/ExplorationEngine.test.ts
Normal file
196
tests/core/ExplorationEngine.test.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { ExplorationEngine } from '../../src/core/ExplorationEngine';
|
||||
import { StateGraph } from '../../src/core/StateGraph';
|
||||
import { NullLogger } from '../../src/core/Logger';
|
||||
import { IState, IAction, IObservation } from '../../src/core/interfaces';
|
||||
import { IInteractionAgent } from '../../src/plugins/interfaces';
|
||||
|
||||
function makeState(id: string, url: string): IState {
|
||||
return { id, url, title: `Page ${id}`, timestamp: 1000, domSnapshot: '<body></body>', visitCount: 0 };
|
||||
}
|
||||
|
||||
function makeAction(id: string, stateId: string): IAction {
|
||||
return { id, type: 'click', selector: '#btn', timestamp: 2000, seed: 42, stateId };
|
||||
}
|
||||
|
||||
function makeObservation(id: string, actionId: string, newStateId: string): IObservation {
|
||||
return {
|
||||
id,
|
||||
actionId,
|
||||
newStateId,
|
||||
httpResponses: [],
|
||||
consoleErrors: [],
|
||||
jsExceptions: [],
|
||||
timestamp: 3000,
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates a minimal mock IInteractionAgent */
|
||||
function createMockAgent(states: IState[], actions: IAction[], observations: IObservation[]): IInteractionAgent {
|
||||
let callCount = 0;
|
||||
return {
|
||||
launch: jest.fn().mockResolvedValue(undefined),
|
||||
close: jest.fn().mockResolvedValue(undefined),
|
||||
captureState: jest.fn().mockImplementation(() => {
|
||||
const state = states[Math.min(callCount, states.length - 1)];
|
||||
callCount++;
|
||||
return Promise.resolve(state);
|
||||
}),
|
||||
discoverActions: jest.fn().mockResolvedValue(actions),
|
||||
executeAction: jest.fn().mockImplementation((_action: IAction) => {
|
||||
return Promise.resolve(observations[0]);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
describe('ExplorationEngine', () => {
|
||||
it('launches and closes the agent', async () => {
|
||||
const graph = new StateGraph();
|
||||
const state = makeState('s1', 'http://localhost/');
|
||||
const obs = makeObservation('o1', 'a1', 's1');
|
||||
const agent = createMockAgent([state], [], [obs]);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 0,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
await engine.run();
|
||||
expect(agent.launch).toHaveBeenCalledWith('http://localhost/');
|
||||
expect(agent.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('captures initial state and adds it to the graph', async () => {
|
||||
const graph = new StateGraph();
|
||||
const state = makeState('s1', 'http://localhost/');
|
||||
const agent = createMockAgent([state], [], []);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 0,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
await engine.run();
|
||||
expect(graph.hasState('s1')).toBe(true);
|
||||
});
|
||||
|
||||
it('executes actions and records transitions', async () => {
|
||||
const graph = new StateGraph();
|
||||
const s1 = makeState('s1', '/');
|
||||
const s2 = makeState('s2', '/about');
|
||||
const action = makeAction('a1', 's1');
|
||||
const obs = makeObservation('o1', 'a1', 's2');
|
||||
const agent = createMockAgent([s1, s2], [action], [obs]);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 1,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
const result = await engine.run();
|
||||
expect(result.statesVisited).toBeGreaterThanOrEqual(1);
|
||||
expect(graph.getTransitions()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('terminates when maxSteps is reached', async () => {
|
||||
const graph = new StateGraph();
|
||||
const state = makeState('s1', '/');
|
||||
const action = makeAction('a1', 's1');
|
||||
const obs = makeObservation('o1', 'a1', 's1');
|
||||
const agent = createMockAgent([state], [action], [obs]);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 3,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
const result = await engine.run();
|
||||
expect((agent.executeAction as jest.Mock).mock.calls.length).toBeLessThanOrEqual(3);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it('terminates when no more states to explore', async () => {
|
||||
const graph = new StateGraph();
|
||||
const state = makeState('s1', '/');
|
||||
// No actions to discover → loop exits immediately
|
||||
const agent = createMockAgent([state], [], []);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 100,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
const result = await engine.run();
|
||||
expect(result.anomaliesFound).toBe(0);
|
||||
});
|
||||
|
||||
it('detects anomalies from HTTP errors', async () => {
|
||||
const graph = new StateGraph();
|
||||
const s1 = makeState('s1', '/');
|
||||
const action = makeAction('a1', 's1');
|
||||
const obs: IObservation = {
|
||||
id: 'o1',
|
||||
actionId: 'a1',
|
||||
newStateId: 's1',
|
||||
httpResponses: [{ url: '/api', status: 500, method: 'POST', durationMs: 100 }],
|
||||
consoleErrors: [],
|
||||
jsExceptions: [],
|
||||
timestamp: 3000,
|
||||
};
|
||||
const agent = createMockAgent([s1], [action], [obs]);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 1,
|
||||
logger: new NullLogger(),
|
||||
});
|
||||
|
||||
const result = await engine.run();
|
||||
expect(result.anomaliesFound).toBe(1);
|
||||
expect(result.anomalies[0].type).toBe('http_error');
|
||||
});
|
||||
|
||||
it('logs all key events via the logger', async () => {
|
||||
const graph = new StateGraph();
|
||||
const state = makeState('s1', '/');
|
||||
const logger = new NullLogger();
|
||||
const agent = createMockAgent([state], [], []);
|
||||
|
||||
const engine = new ExplorationEngine({
|
||||
graph,
|
||||
agent,
|
||||
seed: 42,
|
||||
url: 'http://localhost/',
|
||||
maxSteps: 0,
|
||||
logger,
|
||||
});
|
||||
|
||||
await engine.run();
|
||||
const eventTypes = logger.events.map((e) => e.event);
|
||||
expect(eventTypes).toContain('session_start');
|
||||
expect(eventTypes).toContain('state_discovered');
|
||||
expect(eventTypes).toContain('session_end');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user