test(08.1-02): update tests to use schema-aligned scenarios

- Update MockAgentManager tests to use status-based scenarios
- Change outcome:'crash' to status:'unrecoverable_error' with error field
- Change outcome:'waiting_for_input' to status:'question' with question field
- Change outcome:'success' to status:'done' with result field
- Add tests for structured question data (options, multiSelect)
- Add tests for getPendingQuestion and resume clearing pending question
- Update E2E edge-cases tests with new scenario format
- Update harness tests with new scenario format
This commit is contained in:
Lukas May
2026-01-31 15:31:56 +01:00
parent 8e38bd298b
commit 565f9fdae9
3 changed files with 122 additions and 41 deletions

View File

@@ -129,9 +129,9 @@ describe('MockAgentManager', () => {
describe('spawn with configured delay', () => { describe('spawn with configured delay', () => {
it('should not complete before delay expires', async () => { it('should not complete before delay expires', async () => {
manager.setScenario('delayed-agent', { manager.setScenario('delayed-agent', {
outcome: 'success', status: 'done',
delay: 100, delay: 100,
message: 'Delayed completion', result: 'Delayed completion',
}); });
const agent = await manager.spawn({ const agent = await manager.spawn({
@@ -149,9 +149,9 @@ describe('MockAgentManager', () => {
it('should complete after delay expires', async () => { it('should complete after delay expires', async () => {
manager.setScenario('delayed-agent', { manager.setScenario('delayed-agent', {
outcome: 'success', status: 'done',
delay: 100, delay: 100,
message: 'Delayed completion', result: 'Delayed completion',
}); });
const agent = await manager.spawn({ const agent = await manager.spawn({
@@ -175,12 +175,12 @@ describe('MockAgentManager', () => {
// spawn() with crash scenario // spawn() with crash scenario
// =========================================================================== // ===========================================================================
describe('spawn with crash scenario', () => { describe('spawn with unrecoverable_error scenario', () => {
it('should emit agent:crashed and set result.success=false', async () => { it('should emit agent:crashed and set result.success=false', async () => {
manager.setScenario('crash-agent', { manager.setScenario('crash-agent', {
outcome: 'crash', status: 'unrecoverable_error',
delay: 0, delay: 0,
message: 'Something went terribly wrong', error: 'Something went terribly wrong',
}); });
const agent = await manager.spawn({ const agent = await manager.spawn({
@@ -208,13 +208,13 @@ describe('MockAgentManager', () => {
}); });
// =========================================================================== // ===========================================================================
// spawn() with waiting_for_input scenario // spawn() with question scenario
// =========================================================================== // ===========================================================================
describe('spawn with waiting_for_input scenario', () => { describe('spawn with question scenario', () => {
it('should emit agent:waiting and set status to waiting_for_input', async () => { it('should emit agent:waiting and set status to waiting_for_input', async () => {
manager.setScenario('waiting-agent', { manager.setScenario('waiting-agent', {
outcome: 'waiting_for_input', status: 'question',
delay: 0, delay: 0,
question: 'Should I continue?', question: 'Should I continue?',
}); });
@@ -242,10 +242,10 @@ describe('MockAgentManager', () => {
// resume() after waiting_for_input // resume() after waiting_for_input
// =========================================================================== // ===========================================================================
describe('resume after waiting_for_input', () => { describe('resume after question', () => {
it('should emit agent:resumed and continue with scenario', async () => { it('should emit agent:resumed and continue with scenario', async () => {
manager.setScenario('resume-agent', { manager.setScenario('resume-agent', {
outcome: 'waiting_for_input', status: 'question',
delay: 0, delay: 0,
question: 'Need your input', question: 'Need your input',
}); });
@@ -311,9 +311,9 @@ describe('MockAgentManager', () => {
describe('stop', () => { describe('stop', () => {
it('should cancel scheduled completion and emit agent:stopped', async () => { it('should cancel scheduled completion and emit agent:stopped', async () => {
manager.setScenario('stoppable-agent', { manager.setScenario('stoppable-agent', {
outcome: 'success', status: 'done',
delay: 1000, delay: 1000,
message: 'Should not see this', result: 'Should not see this',
}); });
const agent = await manager.spawn({ const agent = await manager.spawn({
@@ -411,11 +411,11 @@ describe('MockAgentManager', () => {
describe('setScenario overrides', () => { describe('setScenario overrides', () => {
it('should use scenario override for specific agent name', async () => { it('should use scenario override for specific agent name', async () => {
// Set crash scenario for one agent // Set unrecoverable_error scenario for one agent
manager.setScenario('crasher', { manager.setScenario('crasher', {
outcome: 'crash', status: 'unrecoverable_error',
delay: 0, delay: 0,
message: 'Intentional crash', error: 'Intentional crash',
}); });
// Spawn two agents - one with override, one with default // Spawn two agents - one with override, one with default
@@ -443,8 +443,9 @@ describe('MockAgentManager', () => {
it('should allow clearing scenario override', async () => { it('should allow clearing scenario override', async () => {
manager.setScenario('flip-flop', { manager.setScenario('flip-flop', {
outcome: 'crash', status: 'unrecoverable_error',
delay: 0, delay: 0,
error: 'Crash for test',
}); });
// First spawn crashes // First spawn crashes
@@ -489,7 +490,7 @@ describe('MockAgentManager', () => {
}); });
it('should emit spawned before crashed', async () => { it('should emit spawned before crashed', async () => {
manager.setScenario('crash-order', { outcome: 'crash', delay: 0 }); manager.setScenario('crash-order', { status: 'unrecoverable_error', delay: 0, error: 'Crash' });
await manager.spawn({ name: 'crash-order', taskId: 't1', prompt: 'p1' }); await manager.spawn({ name: 'crash-order', taskId: 't1', prompt: 'p1' });
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
@@ -503,8 +504,9 @@ describe('MockAgentManager', () => {
it('should emit spawned before waiting', async () => { it('should emit spawned before waiting', async () => {
manager.setScenario('wait-order', { manager.setScenario('wait-order', {
outcome: 'waiting_for_input', status: 'question',
delay: 0, delay: 0,
question: 'Test question',
}); });
await manager.spawn({ name: 'wait-order', taskId: 't1', prompt: 'p1' }); await manager.spawn({ name: 'wait-order', taskId: 't1', prompt: 'p1' });
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
@@ -551,9 +553,9 @@ describe('MockAgentManager', () => {
it('should use provided default scenario', async () => { it('should use provided default scenario', async () => {
const customDefault: MockAgentScenario = { const customDefault: MockAgentScenario = {
outcome: 'crash', status: 'unrecoverable_error',
delay: 0, delay: 0,
message: 'Default crash', error: 'Default crash',
}; };
const customManager = new MockAgentManager({ const customManager = new MockAgentManager({
@@ -580,7 +582,7 @@ describe('MockAgentManager', () => {
describe('clear', () => { describe('clear', () => {
it('should remove all agents and cancel pending timers', async () => { it('should remove all agents and cancel pending timers', async () => {
manager.setScenario('pending', { outcome: 'success', delay: 1000 }); manager.setScenario('pending', { status: 'done', delay: 1000 });
await manager.spawn({ name: 'pending', taskId: 't1', prompt: 'p1' }); await manager.spawn({ name: 'pending', taskId: 't1', prompt: 'p1' });
await manager.spawn({ name: 'another', taskId: 't2', prompt: 'p2' }); await manager.spawn({ name: 'another', taskId: 't2', prompt: 'p2' });
@@ -592,4 +594,83 @@ describe('MockAgentManager', () => {
expect((await manager.list()).length).toBe(0); expect((await manager.list()).length).toBe(0);
}); });
}); });
// ===========================================================================
// Structured question data (new schema tests)
// ===========================================================================
describe('structured question data', () => {
it('emits agent:waiting with structured question data', async () => {
manager.setScenario('test-agent', {
status: 'question',
question: 'Which database?',
options: [
{ label: 'PostgreSQL', description: 'Full-featured' },
{ label: 'SQLite', description: 'Lightweight' },
],
multiSelect: false,
});
await manager.spawn({ name: 'test-agent', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
const events = eventBus.emittedEvents.filter((e) => e.type === 'agent:waiting');
expect(events.length).toBe(1);
expect((events[0] as any).payload.options).toHaveLength(2);
expect((events[0] as any).payload.options[0].label).toBe('PostgreSQL');
expect((events[0] as any).payload.multiSelect).toBe(false);
});
it('stores pending question for retrieval', async () => {
manager.setScenario('test-agent', {
status: 'question',
question: 'Which database?',
options: [{ label: 'PostgreSQL' }],
});
const agent = await manager.spawn({ name: 'test-agent', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
const pending = await manager.getPendingQuestion(agent.id);
expect(pending?.question).toBe('Which database?');
expect(pending?.options).toHaveLength(1);
expect(pending?.options?.[0].label).toBe('PostgreSQL');
});
it('clears pending question after resume', async () => {
manager.setScenario('resume-test', {
status: 'question',
question: 'Need your input',
options: [{ label: 'Option A' }, { label: 'Option B' }],
});
const agent = await manager.spawn({ name: 'resume-test', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
// Verify question is pending
const pendingBefore = await manager.getPendingQuestion(agent.id);
expect(pendingBefore).not.toBeNull();
expect(pendingBefore?.question).toBe('Need your input');
// Resume the agent
await manager.resume(agent.id, 'Option A');
// Pending question should be cleared
const pendingAfter = await manager.getPendingQuestion(agent.id);
expect(pendingAfter).toBeNull();
});
it('returns null for non-existent agent pending question', async () => {
const pending = await manager.getPendingQuestion('non-existent-id');
expect(pending).toBeNull();
});
it('returns null for agent not in waiting state', async () => {
const agent = await manager.spawn({ name: 'running-agent', taskId: 'task-1', prompt: 'test' });
// Agent is running, not waiting
const pending = await manager.getPendingQuestion(agent.id);
expect(pending).toBeNull();
});
});
}); });

View File

@@ -52,10 +52,10 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set crash scenario BEFORE dispatch // Set unrecoverable_error scenario BEFORE dispatch
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'crash', status: 'unrecoverable_error',
message: 'Token limit exceeded', error: 'Token limit exceeded',
}); });
await harness.dispatchManager.queue(taskAId); await harness.dispatchManager.queue(taskAId);
@@ -91,10 +91,10 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set crash scenario // Set unrecoverable_error scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'crash', status: 'unrecoverable_error',
message: 'Token limit exceeded', error: 'Token limit exceeded',
}); });
await harness.dispatchManager.queue(taskAId); await harness.dispatchManager.queue(taskAId);
@@ -119,10 +119,10 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set crash scenario // Set unrecoverable_error scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'crash', status: 'unrecoverable_error',
message: 'Out of memory', error: 'Out of memory',
}); });
await harness.dispatchManager.queue(taskAId); await harness.dispatchManager.queue(taskAId);
@@ -151,9 +151,9 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set waiting scenario // Set question scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'waiting_for_input', status: 'question',
question: 'Which database should I use?', question: 'Which database should I use?',
}); });
@@ -184,9 +184,9 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set waiting scenario // Set question scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'waiting_for_input', status: 'question',
question: 'Which database should I use?', question: 'Which database should I use?',
}); });
@@ -234,9 +234,9 @@ describe('E2E Edge Cases', () => {
}); });
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
// Set waiting scenario // Set question scenario
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'waiting_for_input', status: 'question',
question: 'Which database should I use?', question: 'Which database should I use?',
}); });

View File

@@ -267,11 +267,11 @@ describe('TestHarness', () => {
await vi.runAllTimersAsync(); await vi.runAllTimersAsync();
harness.clearEvents(); harness.clearEvents();
// Set crash scenario for the agent that will be spawned // Set unrecoverable_error scenario for the agent that will be spawned
harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, {
outcome: 'crash', status: 'unrecoverable_error',
delay: 0, delay: 0,
message: 'Test crash', error: 'Test crash',
}); });
// Queue and dispatch // Queue and dispatch