diff --git a/src/test/e2e/edge-cases.test.ts b/src/test/e2e/edge-cases.test.ts index dcd0c68..e35ed85 100644 --- a/src/test/e2e/edge-cases.test.ts +++ b/src/test/e2e/edge-cases.test.ts @@ -50,7 +50,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set error scenario BEFORE dispatch harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -62,7 +62,7 @@ describe('E2E Edge Cases', () => { harness.clearEvents(); await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:spawned event emitted const spawnedEvents = harness.getEventsByType('agent:spawned'); @@ -89,7 +89,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set error scenario harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -99,7 +99,7 @@ describe('E2E Edge Cases', () => { await harness.dispatchManager.queue(taskAId); await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Task status should be 'in_progress' (not 'completed') const task = await harness.taskRepository.findById(taskAId); @@ -117,7 +117,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set error scenario harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -127,7 +127,7 @@ describe('E2E Edge Cases', () => { await harness.dispatchManager.queue(taskAId); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Get agent result - should have error const agentResult = await harness.agentManager.getResult(dispatchResult.agentId!); @@ -149,7 +149,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -161,7 +161,7 @@ describe('E2E Edge Cases', () => { harness.clearEvents(); await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:waiting event emitted const waitingEvents = harness.getEventsByType('agent:waiting'); @@ -182,7 +182,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -194,7 +194,7 @@ describe('E2E Edge Cases', () => { harness.clearEvents(); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify agent is in waiting_for_input status const agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -205,7 +205,7 @@ describe('E2E Edge Cases', () => { // Resume agent with answers map await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:resumed event emitted const resumedEvents = harness.getEventsByType('agent:resumed'); @@ -232,7 +232,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario harness.setAgentScenario(`agent-${taskAId.slice(0, 6)}`, { @@ -247,7 +247,7 @@ describe('E2E Edge Cases', () => { let agent = await harness.agentManager.get(dispatchResult.agentId!); expect(agent?.status).toBe('running'); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // After scenario completes: waiting_for_input agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -260,7 +260,7 @@ describe('E2E Edge Cases', () => { agent = await harness.agentManager.get(dispatchResult.agentId!); expect(agent?.status).toBe('running'); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // After completion: idle agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -311,7 +311,7 @@ describe('E2E Edge Cases', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Queue Task A and block it await harness.dispatchManager.queue(taskAId); diff --git a/src/test/e2e/extended-scenarios.test.ts b/src/test/e2e/extended-scenarios.test.ts index 76d3bc7..e2c538c 100644 --- a/src/test/e2e/extended-scenarios.test.ts +++ b/src/test/e2e/extended-scenarios.test.ts @@ -304,7 +304,7 @@ describe('E2E Extended Scenarios', () => { taskId: 'placeholder-3', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue all 4 tasks @@ -327,8 +327,8 @@ describe('E2E Extended Scenarios', () => { const dispatchedIds = [result1.agentId, result2.agentId, result3.agentId]; expect(new Set(dispatchedIds).size).toBe(3); - // Use vi.runAllTimersAsync() to complete all 3 agents - await vi.runAllTimersAsync(); + // Advance timers to complete all 3 agents + await harness.advanceTimers(); // Verify: 3 agent:stopped events const stoppedEvents = harness.getEventsByType('agent:stopped'); @@ -343,7 +343,7 @@ describe('E2E Extended Scenarios', () => { const result4 = await harness.dispatchManager.dispatchNext(); expect(result4.success).toBe(true); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); await harness.dispatchManager.completeTask(result4.taskId!); // Verify: all 4 tasks completed in database @@ -483,7 +483,7 @@ describe('E2E Extended Scenarios', () => { taskId: 'placeholder-2', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set Task X to succeed, Task Y to crash harness.setAgentDone(`agent-${taskXId.slice(0, 6)}`, 'Task X completed'); @@ -503,7 +503,7 @@ describe('E2E Extended Scenarios', () => { expect(result2.success).toBe(true); // Run timers to complete agents - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: one agent:stopped, one agent:crashed const stoppedEvents = harness.getEventsByType('agent:stopped'); diff --git a/src/test/e2e/happy-path.test.ts b/src/test/e2e/happy-path.test.ts index 85d88a6..4ba36d6 100644 --- a/src/test/e2e/happy-path.test.ts +++ b/src/test/e2e/happy-path.test.ts @@ -42,7 +42,7 @@ describe('E2E Happy Path', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Step 1: Queue task @@ -69,7 +69,7 @@ describe('E2E Happy Path', () => { expect(spawnedEvents.length).toBe(1); // Step 3: Wait for agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify agent:stopped event const stoppedEvents = harness.getEventsByType('agent:stopped'); @@ -102,7 +102,7 @@ describe('E2E Happy Path', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue all three tasks @@ -130,7 +130,7 @@ describe('E2E Happy Path', () => { expect(dispatchResult.taskId).toBe(taskAId); // Wait for agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Complete Task A await harness.dispatchManager.completeTask(taskAId); @@ -172,7 +172,7 @@ describe('E2E Happy Path', () => { taskId: 'placeholder-2', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue all 4 tasks @@ -220,7 +220,7 @@ describe('E2E Happy Path', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue and dispatch task @@ -229,7 +229,7 @@ describe('E2E Happy Path', () => { expect(dispatchResult.success).toBe(true); // Wait for agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Complete task await harness.dispatchManager.completeTask(taskAId); @@ -289,7 +289,7 @@ describe('E2E Happy Path', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue all 5 tasks @@ -318,7 +318,7 @@ describe('E2E Happy Path', () => { expect(result1.taskId).toBe(task1AId); // Wait for agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Complete Task 1A await harness.dispatchManager.completeTask(task1AId); @@ -335,7 +335,7 @@ describe('E2E Happy Path', () => { // Task 1B (high priority among remaining) const result2 = await harness.dispatchManager.dispatchNext(); expect(result2.success).toBe(true); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); await harness.dispatchManager.completeTask(result2.taskId!); // 3 tasks remain @@ -345,17 +345,17 @@ describe('E2E Happy Path', () => { // Continue dispatching remaining tasks const result3 = await harness.dispatchManager.dispatchNext(); expect(result3.success).toBe(true); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); await harness.dispatchManager.completeTask(result3.taskId!); const result4 = await harness.dispatchManager.dispatchNext(); expect(result4.success).toBe(true); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); await harness.dispatchManager.completeTask(result4.taskId!); const result5 = await harness.dispatchManager.dispatchNext(); expect(result5.success).toBe(true); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); await harness.dispatchManager.completeTask(result5.taskId!); // All tasks completed diff --git a/src/test/e2e/recovery-scenarios.test.ts b/src/test/e2e/recovery-scenarios.test.ts index 2f8f46e..03c2834 100644 --- a/src/test/e2e/recovery-scenarios.test.ts +++ b/src/test/e2e/recovery-scenarios.test.ts @@ -82,7 +82,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set crash scenario harness.setAgentError(`agent-${taskAId.slice(0, 6)}`, 'Token limit exceeded'); @@ -90,7 +90,7 @@ describe('E2E Recovery Scenarios', () => { // Queue and dispatch await harness.dispatchManager.queue(taskAId); await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify task status is 'in_progress' (not completed, not lost) let task = await harness.taskRepository.findById(taskAId); @@ -104,7 +104,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Re-queue the task (it's still in_progress but we can retry) await harness.dispatchManager.queue(taskAId); @@ -115,7 +115,7 @@ describe('E2E Recovery Scenarios', () => { // Clear events and dispatch again harness.clearEvents(); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent completed successfully expect(dispatchResult.agentId).toBeDefined(); @@ -217,7 +217,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario with options harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [ @@ -236,7 +236,7 @@ describe('E2E Recovery Scenarios', () => { harness.clearEvents(); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:waiting event emitted const waitingEvents = harness.getEventsByType('agent:waiting'); @@ -248,7 +248,7 @@ describe('E2E Recovery Scenarios', () => { // Clear and resume with answers map harness.clearEvents(); await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: resumed and stopped events const resumedEvents = harness.getEventsByType('agent:resumed'); @@ -273,7 +273,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario with options harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [ @@ -291,7 +291,7 @@ describe('E2E Recovery Scenarios', () => { // Queue and dispatch await harness.dispatchManager.queue(taskAId); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:waiting event has questions const waitingEvents = harness.getEventsByType('agent:waiting'); @@ -326,7 +326,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [ @@ -336,7 +336,7 @@ describe('E2E Recovery Scenarios', () => { // Queue and dispatch await harness.dispatchManager.queue(taskAId); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify agent is waiting const agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -344,7 +344,7 @@ describe('E2E Recovery Scenarios', () => { // Resume with answers map await harness.agentManager.resume(dispatchResult.agentId!, { q1: 'PostgreSQL' }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent completed successfully const agentResult = await harness.agentManager.getResult(dispatchResult.agentId!); @@ -368,7 +368,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Set questions scenario harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [ @@ -383,7 +383,7 @@ describe('E2E Recovery Scenarios', () => { let agent = await harness.agentManager.get(dispatchResult.agentId!); expect(agent?.status).toBe('running'); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Phase 2: After scenario completes, waiting_for_input agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -400,7 +400,7 @@ describe('E2E Recovery Scenarios', () => { agent = await harness.agentManager.get(dispatchResult.agentId!); expect(agent?.status).toBe('running'); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Phase 4: After completion, idle agent = await harness.agentManager.get(dispatchResult.agentId!); @@ -422,7 +422,7 @@ describe('E2E Recovery Scenarios', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Setup: agent asks two questions harness.setAgentQuestions(`agent-${taskAId.slice(0, 6)}`, [ @@ -443,7 +443,7 @@ describe('E2E Recovery Scenarios', () => { harness.clearEvents(); const dispatchResult = await harness.dispatchManager.dispatchNext(); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:waiting event emitted const waitingEvents = harness.getEventsByType('agent:waiting'); @@ -465,7 +465,7 @@ describe('E2E Recovery Scenarios', () => { q1: 'SQLite', q2: 'Yes', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify: agent:resumed event emitted const resumedEvents = harness.getEventsByType('agent:resumed'); diff --git a/src/test/harness.test.ts b/src/test/harness.test.ts index a76f7ea..3a64edf 100644 --- a/src/test/harness.test.ts +++ b/src/test/harness.test.ts @@ -68,7 +68,7 @@ describe('TestHarness', () => { prompt: 'test', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Verify questions array format const pending = await harness.getPendingQuestions(agent.id); @@ -207,7 +207,7 @@ describe('TestHarness', () => { prompt: 'placeholder', }); // Wait for agent to complete and become idle - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Queue the task await harness.dispatchManager.queue(taskAId); @@ -219,7 +219,7 @@ describe('TestHarness', () => { const result = await harness.dispatchManager.dispatchNext(); // Advance timers to trigger mock agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); expect(result.success).toBe(true); expect(result.taskId).toBe(taskAId); @@ -264,7 +264,7 @@ describe('TestHarness', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Queue and dispatch @@ -277,7 +277,7 @@ describe('TestHarness', () => { expect(spawnedEvents.length).toBe(1); // Advance timers to trigger completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Should have agent:stopped const stoppedEvents = harness.getEventsByType('agent:stopped'); @@ -295,7 +295,7 @@ describe('TestHarness', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Set error scenario for the agent that will be spawned @@ -311,7 +311,7 @@ describe('TestHarness', () => { await harness.dispatchManager.dispatchNext(); // Advance timers - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Should have agent:crashed const crashedEvents = harness.getEventsByType('agent:crashed'); @@ -331,7 +331,7 @@ describe('TestHarness', () => { taskId: 'placeholder', prompt: 'placeholder', }); - await vi.runAllTimersAsync(); + await harness.advanceTimers(); harness.clearEvents(); // Step 1: Queue task @@ -342,7 +342,7 @@ describe('TestHarness', () => { expect(dispatchResult.success).toBe(true); // Advance timers for agent completion - await vi.runAllTimersAsync(); + await harness.advanceTimers(); // Clear events for cleaner verification harness.clearEvents(); diff --git a/src/test/harness.ts b/src/test/harness.ts index 06c3ee9..245ed78 100644 --- a/src/test/harness.ts +++ b/src/test/harness.ts @@ -280,6 +280,13 @@ export interface TestHarness { */ advanceTimers(): Promise; + /** + * Run a test body with fake timers enabled. + * Activates fake timers before the callback and restores real timers after, + * even if the callback throws. + */ + withFakeTimers(fn: () => Promise): Promise; + // ========================================================================== // Architect Mode Helpers // ========================================================================== @@ -513,6 +520,15 @@ export function createTestHarness(): TestHarness { // Timer helper - requires vi.useFakeTimers() to be active advanceTimers: () => vi.runAllTimersAsync(), + withFakeTimers: async (fn: () => Promise) => { + vi.useFakeTimers(); + try { + await fn(); + } finally { + vi.useRealTimers(); + } + }, + // ======================================================================== // Architect Mode Helpers // ========================================================================