mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 15:39:41 +02:00
test: repair route harness error rendering + stop AI-checker spawning real processes in tests (#97)
* fix(test): share route error handler with test harness + fix stale assertions
The route test harness built a bare Fastify instance without the production
global error handler (server.ts), so structured errors thrown by route helpers
(findSessionOrFail → 404, parseBody → 400) fell through to Fastify's default
handler — yielding a `{statusCode,error,message}` body instead of the
`{success:false,...}` shape, and the tests asserted the old implicit-200
behavior. 51 route tests across 7 files were red.
- Extract the handler into src/web/route-error-handler.ts; server.ts and the
test harness now install the identical handler (single source of truth).
- Correct stale assertions across route test files: throw-based error paths
now assert 404 (unknown session) / 400 (invalid body); genuine in-handler
`return createErrorResponse(...)` paths (200 + success:false) left untouched.
- Reformat a few test files prettier flagged (pre-existing non-compliance).
Route suite: 307/307 passing (was 256/307). No production behavior change.
* test(respawn): mock child_process so AI checker never spawns real processes
respawn-controller.test.ts drives the AI idle checker (ai-checker-base), whose
runCheck() spawns a real `tmux new-session` running `claude -p`. The AI-enabled
tests only assert the ai_checking state transition (then cancel/stop), so the
spawn produced stray real tmux sessions and claude processes on every run — the
reason `npm test` (full suite) was unsafe to run inside a managed session.
Mock node:child_process here (mirroring ai-idle-checker.test.ts), spreading the
real module so `exec` stays intact for transitively-imported modules
(tmux-manager calls promisify(exec) at load). With this, the full non-mobile
suite runs without spawning any real tmux/claude.
---------
Co-authored-by: Teigen <teigen@TeigendeMac-mini.local>
This commit is contained in:
+149
-141
@@ -1,4 +1,18 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
|
||||
// The respawn controller drives the AI idle checker, which spawns real `tmux`/`claude`
|
||||
// via child_process. Neutralize those spawns here (mirrors ai-idle-checker.test.ts) so
|
||||
// tests never launch real processes. Spread the real module to keep `exec` etc. intact —
|
||||
// transitively-imported modules (e.g. tmux-manager) call `promisify(exec)` at load time.
|
||||
vi.mock('node:child_process', async (orig) => {
|
||||
const actual = await orig<typeof import('node:child_process')>();
|
||||
return {
|
||||
...actual,
|
||||
execSync: vi.fn(),
|
||||
spawn: vi.fn(() => ({ unref: vi.fn(), pid: 12345, on: vi.fn() })),
|
||||
};
|
||||
});
|
||||
|
||||
import { RespawnController, RespawnState, RespawnConfig } from '../src/respawn-controller.js';
|
||||
import { Session } from '../src/session.js';
|
||||
import { MockSession } from './mocks/index.js';
|
||||
@@ -38,7 +52,9 @@ describe('RespawnController', () => {
|
||||
it('should have default configuration', () => {
|
||||
const config = controller.getConfig();
|
||||
expect(config.enabled).toBe(true);
|
||||
expect(config.updatePrompt).toBe('write a brief progress summary to CLAUDE.md noting what you accomplished, then continue working.');
|
||||
expect(config.updatePrompt).toBe(
|
||||
'write a brief progress summary to CLAUDE.md noting what you accomplished, then continue working.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow custom configuration', () => {
|
||||
@@ -95,9 +111,9 @@ describe('RespawnController', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for log
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const hasCompletionLog = logMessages.some(msg => msg.includes('Completion message detected'));
|
||||
const hasCompletionLog = logMessages.some((msg) => msg.includes('Completion message detected'));
|
||||
expect(hasCompletionLog).toBe(true);
|
||||
});
|
||||
|
||||
@@ -139,7 +155,7 @@ describe('RespawnController', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirmation (completionConfirmMs=50) + processing
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(cycleStarted).toBe(true);
|
||||
expect(controller.currentCycle).toBe(1);
|
||||
@@ -155,7 +171,7 @@ describe('RespawnController', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirmation + step delay
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
expect(stepSent).toBe('update');
|
||||
expect(session.writeBuffer.length).toBeGreaterThan(0);
|
||||
@@ -170,7 +186,7 @@ describe('RespawnController', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for state transitions
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should have transitioned through multiple states (watching -> confirming_idle -> sending_update)
|
||||
expect(states).toContain('watching');
|
||||
@@ -239,7 +255,7 @@ describe('RespawnController', () => {
|
||||
controller.start();
|
||||
|
||||
// Wait a bit
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const status = controller.getStatus();
|
||||
expect(status.timeSinceActivity).toBeGreaterThan(0);
|
||||
@@ -299,7 +315,7 @@ describe('RespawnController', () => {
|
||||
});
|
||||
|
||||
controller.start();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
expect(events[0].state).toBe('watching');
|
||||
@@ -313,7 +329,7 @@ describe('RespawnController', () => {
|
||||
controller.start();
|
||||
|
||||
expect(logs.length).toBeGreaterThan(0);
|
||||
expect(logs.some(l => l.includes('Starting'))).toBe(true);
|
||||
expect(logs.some((l) => l.includes('Starting'))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -346,13 +362,13 @@ describe('RespawnController Integration', () => {
|
||||
|
||||
// Alternate between working and idle
|
||||
session.simulatePrompt();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
session.simulateWorking();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
session.simulatePrompt();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Should handle transitions gracefully
|
||||
expect(controller.isRunning).toBe(true);
|
||||
@@ -467,7 +483,7 @@ describe('RespawnController Integration', () => {
|
||||
controller.start();
|
||||
session.simulatePrompt();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(cycleStarted).toBe(false);
|
||||
controller.stop();
|
||||
@@ -560,7 +576,7 @@ describe('RespawnController Configuration', () => {
|
||||
|
||||
it('should clamp completionConfirmMs to noOutputTimeoutMs', () => {
|
||||
const controller = new RespawnController(session as unknown as Session, {
|
||||
completionConfirmMs: 60000, // Greater than noOutputTimeoutMs
|
||||
completionConfirmMs: 60000, // Greater than noOutputTimeoutMs
|
||||
noOutputTimeoutMs: 30000,
|
||||
});
|
||||
// completionConfirmMs should be clamped to noOutputTimeoutMs
|
||||
@@ -646,7 +662,7 @@ describe('RespawnController State Transitions', () => {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(stateHistory).toContain('watching');
|
||||
expect(stateHistory.length).toBeGreaterThan(1);
|
||||
@@ -657,7 +673,7 @@ describe('RespawnController State Transitions', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait a bit then stop during potential transition
|
||||
await new Promise(resolve => setTimeout(resolve, 30));
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
controller.stop();
|
||||
|
||||
expect(controller.state).toBe('stopped');
|
||||
@@ -672,7 +688,7 @@ describe('RespawnController State Transitions', () => {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// May or may not complete depending on timing
|
||||
expect(controller.isRunning).toBe(true);
|
||||
@@ -692,7 +708,7 @@ describe('RespawnController State Transitions', () => {
|
||||
session.simulateCompletionMessage();
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Should detect completion messages
|
||||
expect(completionCount).toBeGreaterThan(0);
|
||||
@@ -708,10 +724,10 @@ describe('RespawnController State Transitions', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Before confirmation timer fires, start working
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
session.simulateWorking();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Cycle should not have started due to working state canceling confirmation
|
||||
expect(controller.getStatus().workingDetected).toBe(true);
|
||||
@@ -820,7 +836,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
// Update config mid-run
|
||||
controller.updateConfig({ updatePrompt: 'updated' });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(controller.getConfig().updatePrompt).toBe('updated');
|
||||
controller.stop();
|
||||
@@ -840,7 +856,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(controller.currentCycle).toBeGreaterThan(0);
|
||||
controller.stop();
|
||||
@@ -853,7 +869,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
|
||||
controller.start();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const status = controller.getStatus();
|
||||
// Allow for slight timing variance (timers may fire 1-2ms early)
|
||||
@@ -868,7 +884,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
|
||||
controller.start();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
session.simulateTerminalOutput('new data');
|
||||
|
||||
@@ -908,7 +924,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
session.simulateTerminalOutput('Would you like to proceed?\n❯ 1. Yes\n 2. No\n');
|
||||
|
||||
// Wait for autoAcceptDelayMs to expire
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(true);
|
||||
expect(session.writeBuffer).toContain('\r');
|
||||
@@ -935,7 +951,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for autoAcceptDelayMs
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
autoAcceptController.stop();
|
||||
@@ -958,7 +974,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
autoAcceptController.start();
|
||||
session.simulateTerminalOutput('Plan: Waiting for approval...');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
autoAcceptController.stop();
|
||||
@@ -981,7 +997,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
autoAcceptController.start();
|
||||
|
||||
// Don't simulate any output - just wait
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
autoAcceptController.stop();
|
||||
@@ -1006,15 +1022,15 @@ describe('RespawnController Edge Cases', () => {
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
|
||||
// Wait 100ms (less than 150ms delay), then send more output
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
session.simulateTerminalOutput('More output');
|
||||
|
||||
// Wait another 100ms - total 200ms from start but only 100ms from last output
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
|
||||
// Wait the remaining time
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
expect(autoAcceptFired).toBe(true);
|
||||
autoAcceptController.stop();
|
||||
});
|
||||
@@ -1038,16 +1054,16 @@ describe('RespawnController Edge Cases', () => {
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
|
||||
// Wait for first auto-accept
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
expect(autoAcceptCount).toBe(1);
|
||||
|
||||
// Wait more - should NOT fire again (hasReceivedOutput is false)
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
expect(autoAcceptCount).toBe(1);
|
||||
|
||||
// New output comes in (plan mode again), then silence again - should fire again
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
expect(autoAcceptCount).toBe(2);
|
||||
|
||||
autoAcceptController.stop();
|
||||
@@ -1072,14 +1088,14 @@ describe('RespawnController Edge Cases', () => {
|
||||
|
||||
// Trigger a respawn cycle via completion message
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Now in sending_update or waiting_update state
|
||||
expect(autoAcceptController.state).not.toBe('watching');
|
||||
|
||||
// Simulate output in the waiting state, then silence
|
||||
session.simulateTerminalOutput('Processing update...');
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Auto-accept should NOT fire because we're not in watching state
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
@@ -1107,7 +1123,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
autoAcceptController.signalElicitation();
|
||||
|
||||
// Wait for autoAcceptDelayMs to expire
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Auto-accept should NOT fire because elicitation was signaled
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
@@ -1141,7 +1157,7 @@ describe('RespawnController Edge Cases', () => {
|
||||
// New silence after work - plan mode approval with plan mode UI
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Auto-accept should fire now (elicitation cleared by working pattern)
|
||||
expect(autoAcceptFired).toBe(true);
|
||||
@@ -1210,7 +1226,7 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm timer to fire and AI check to start
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should have transitioned to ai_checking
|
||||
expect(states).toContain('ai_checking');
|
||||
@@ -1230,13 +1246,13 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for AI check to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Simulate working patterns during AI check
|
||||
session.simulateWorking();
|
||||
|
||||
// Should be back to watching
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
expect(controller.state).toBe('watching');
|
||||
|
||||
controller.stop();
|
||||
@@ -1254,13 +1270,13 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for AI check to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Simulate substantial output during AI check
|
||||
session.simulateTerminalOutput('Some meaningful output that is more than 2 chars');
|
||||
|
||||
// Should be back to watching
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
expect(controller.state).toBe('watching');
|
||||
|
||||
controller.stop();
|
||||
@@ -1282,7 +1298,7 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm and direct idle
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(cycleStarted).toBe(true);
|
||||
controller.stop();
|
||||
@@ -1304,7 +1320,7 @@ describe('RespawnController AI Idle Check', () => {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
expect(aiCheckStarted).toBe(true);
|
||||
controller.stop();
|
||||
@@ -1347,7 +1363,7 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm timer + AI check start
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Should have triggered ai_checking via completion path
|
||||
expect(states).toContain('ai_checking');
|
||||
@@ -1370,7 +1386,7 @@ describe('RespawnController AI Idle Check', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for AI check to start and timeout
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
// Should return to watching after timeout (with cooldown)
|
||||
expect(controller.state).toBe('watching');
|
||||
@@ -1416,7 +1432,7 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
// Output without plan mode patterns (no numbered list, no selector)
|
||||
session.simulateTerminalOutput('Claude is just thinking about something...\nSome regular output here.');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Pre-filter should block - no plan mode patterns found
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
@@ -1442,13 +1458,10 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
|
||||
// Output WITH plan mode patterns
|
||||
session.simulateTerminalOutput(
|
||||
'Would you like to proceed with this plan?\n' +
|
||||
'❯ 1. Yes\n' +
|
||||
' 2. No\n' +
|
||||
' 3. Type your own\n'
|
||||
'Would you like to proceed with this plan?\n' + '❯ 1. Yes\n' + ' 2. No\n' + ' 3. Type your own\n'
|
||||
);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Pre-filter should pass and send Enter (AI disabled)
|
||||
expect(autoAcceptFired).toBe(true);
|
||||
@@ -1473,15 +1486,11 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Plan mode patterns BUT also has working patterns (spinner) in the tail
|
||||
session.simulateTerminalOutput(
|
||||
'❯ 1. Yes\n' +
|
||||
' 2. No\n' +
|
||||
'Thinking ⠋\n'
|
||||
);
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n' + ' 2. No\n' + 'Thinking ⠋\n');
|
||||
|
||||
// Wait for autoAcceptDelay - but working pattern resets the timer
|
||||
// so we need to wait longer and check after working pattern was consumed
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should NOT fire because working patterns detected resets timer
|
||||
// (the working pattern in handleTerminalData clears timers)
|
||||
@@ -1508,13 +1517,9 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Output with plan mode patterns to pass pre-filter
|
||||
session.simulateTerminalOutput(
|
||||
'Would you like to proceed?\n' +
|
||||
'❯ 1. Yes\n' +
|
||||
' 2. No\n'
|
||||
);
|
||||
session.simulateTerminalOutput('Would you like to proceed?\n' + '❯ 1. Yes\n' + ' 2. No\n');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Plan check should have been started (pre-filter passed, AI enabled)
|
||||
expect(planCheckStarted).toBe(true);
|
||||
@@ -1544,17 +1549,14 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Trigger plan check
|
||||
session.simulateTerminalOutput(
|
||||
'❯ 1. Yes\n' +
|
||||
' 2. No\n'
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n' + ' 2. No\n');
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
expect(planCheckStarted).toBe(true);
|
||||
|
||||
// New output arrives - should cancel plan check (stale)
|
||||
session.simulateTerminalOutput('New output from Claude...');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Auto-accept should NOT have fired (check was cancelled)
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
@@ -1580,16 +1582,14 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Plan mode patterns to trigger check
|
||||
session.simulateTerminalOutput(
|
||||
'❯ 1. Yes\n 2. No\n'
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Output arrives during check - result should be discarded
|
||||
session.simulateTerminalOutput('Claude started working again');
|
||||
|
||||
// Wait for any pending check to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
controller.stop();
|
||||
@@ -1617,11 +1617,9 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Plan mode patterns
|
||||
session.simulateTerminalOutput(
|
||||
'❯ 1. Yes\n 2. No\n'
|
||||
);
|
||||
session.simulateTerminalOutput('❯ 1. Yes\n 2. No\n');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should send Enter directly (no AI check)
|
||||
expect(planCheckStarted).toBe(false);
|
||||
@@ -1663,7 +1661,7 @@ describe('RespawnController AI Plan Mode Check', () => {
|
||||
controller.start();
|
||||
|
||||
// Don't send any output - hasReceivedOutput should guard
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(autoAcceptFired).toBe(false);
|
||||
controller.stop();
|
||||
@@ -1861,7 +1859,7 @@ describe('RespawnController Resume Behavior', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm timer to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should start cycle based on completion message alone
|
||||
expect(cycleStarted).toBe(true);
|
||||
@@ -1886,7 +1884,7 @@ describe('RespawnController Resume Behavior', () => {
|
||||
session.simulateTerminalOutput('Some text output');
|
||||
|
||||
// Wait for noOutput fallback
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
// Should eventually trigger via fallback
|
||||
expect(cycleStarted).toBe(true);
|
||||
@@ -1915,7 +1913,7 @@ describe('RespawnController Resume Behavior', () => {
|
||||
|
||||
// Verify cycle can still start after resume
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(states).toContain('confirming_idle');
|
||||
controller.stop();
|
||||
@@ -1933,7 +1931,7 @@ describe('RespawnController Resume Behavior', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for cycle to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Pause during cycle
|
||||
controller.pause();
|
||||
@@ -1975,7 +1973,7 @@ describe('RespawnController Step Confirmation', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for full cycle
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
// Should have gone through: watching -> confirming_idle -> sending_update -> waiting_update -> watching
|
||||
expect(states).toContain('watching');
|
||||
@@ -1996,12 +1994,12 @@ describe('RespawnController Step Confirmation', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for update to be sent
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
// Simulate continuous output while waiting for update completion
|
||||
for (let i = 0; i < 5; i++) {
|
||||
session.simulateTerminalOutput(`Processing step ${i}...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
|
||||
// Controller should still be functional
|
||||
@@ -2021,7 +2019,7 @@ describe('RespawnController Step Confirmation', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for cycle to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Continuously emit output to prevent completion detection
|
||||
const outputInterval = setInterval(() => {
|
||||
@@ -2029,7 +2027,7 @@ describe('RespawnController Step Confirmation', () => {
|
||||
}, 50);
|
||||
|
||||
// Wait a reasonable time
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
clearInterval(outputInterval);
|
||||
|
||||
@@ -2057,7 +2055,7 @@ describe('RespawnController Step Confirmation', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for step to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// After update step completes (via timeout), should emit stepCompleted
|
||||
// Note: with sendClear/sendInit false, cycle completes after update
|
||||
@@ -2086,7 +2084,7 @@ describe('RespawnController AI Check Cooldown Behavior', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for AI check to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const detection = controller.getDetectionStatus();
|
||||
// AI check should have started or be in progress
|
||||
@@ -2130,7 +2128,7 @@ describe('RespawnController AI Check Cooldown Behavior', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for AI check to timeout
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// Should have gone through ai_checking and back
|
||||
expect(states).toContain('ai_checking');
|
||||
@@ -2151,18 +2149,18 @@ describe('RespawnController AI Check Cooldown Behavior', () => {
|
||||
|
||||
// First cycle - should start AI check
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const detection1 = controller.getDetectionStatus();
|
||||
// AI check was attempted
|
||||
|
||||
// Reset by simulating working
|
||||
session.simulateWorking();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Second cycle - might be on cooldown
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Controller should still be functional
|
||||
expect(controller.isRunning).toBe(true);
|
||||
@@ -2240,7 +2238,18 @@ describe('RespawnController Working Pattern Detection', () => {
|
||||
controller.start();
|
||||
|
||||
// All spinner characters should indicate working
|
||||
const spinnerChars = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f'];
|
||||
const spinnerChars = [
|
||||
'\u280b',
|
||||
'\u2819',
|
||||
'\u2839',
|
||||
'\u2838',
|
||||
'\u283c',
|
||||
'\u2834',
|
||||
'\u2826',
|
||||
'\u2827',
|
||||
'\u2807',
|
||||
'\u280f',
|
||||
];
|
||||
for (const char of spinnerChars) {
|
||||
session.simulateTerminalOutput(char);
|
||||
}
|
||||
@@ -2266,7 +2275,7 @@ describe('RespawnController Working Pattern Detection', () => {
|
||||
|
||||
// Then completion
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Working should be cleared after a bit of silence
|
||||
status = controller.getStatus();
|
||||
@@ -2301,7 +2310,7 @@ describe('RespawnController Cycle Count Tracking', () => {
|
||||
expect(controller.currentCycle).toBe(0);
|
||||
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
expect(controller.currentCycle).toBeGreaterThan(0);
|
||||
controller.stop();
|
||||
@@ -2321,7 +2330,7 @@ describe('RespawnController Cycle Count Tracking', () => {
|
||||
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
expect(cycleNumber).toBe(1);
|
||||
controller.stop();
|
||||
@@ -2425,13 +2434,13 @@ describe('RespawnController Timer Cleanup', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Start a timer-based operation
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Stop should clean up all timers
|
||||
controller.stop();
|
||||
|
||||
// Wait to ensure no timer fires after stop
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(controller.state).toBe('stopped');
|
||||
expect(controller.isRunning).toBe(false);
|
||||
@@ -2448,13 +2457,13 @@ describe('RespawnController Timer Cleanup', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for confirming_idle
|
||||
await new Promise(resolve => setTimeout(resolve, 30));
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
|
||||
// Interrupt with working pattern
|
||||
session.simulateWorking();
|
||||
|
||||
// Should cancel completion confirm timer and return to watching
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
expect(controller.state).toBe('watching');
|
||||
|
||||
controller.stop();
|
||||
@@ -2469,14 +2478,13 @@ describe('RespawnController Timer Cleanup', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
// Should end in stopped state without errors
|
||||
expect(controller.state).toBe('stopped');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// ========== Hook-Based Detection Tests (Phase 1) ==========
|
||||
@@ -2542,7 +2550,7 @@ describe('RespawnController Hook-Based Idle Detection', () => {
|
||||
testController.signalStopHook();
|
||||
|
||||
// Wait for hook confirmation timer (3s default)
|
||||
await new Promise(resolve => setTimeout(resolve, 3100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 3100));
|
||||
|
||||
expect(cycleStarted).toHaveBeenCalled();
|
||||
testController.stop();
|
||||
@@ -2562,7 +2570,7 @@ describe('RespawnController Hook-Based Idle Detection', () => {
|
||||
testController.signalIdlePrompt();
|
||||
|
||||
// idle_prompt skips confirmation and goes directly to idle
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
expect(cycleStarted).toHaveBeenCalled();
|
||||
testController.stop();
|
||||
@@ -2582,11 +2590,11 @@ describe('RespawnController Hook-Based Idle Detection', () => {
|
||||
testController.signalStopHook();
|
||||
|
||||
// Simulate working patterns IMMEDIATELY after hook (before confirmation)
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
session.simulateWorking();
|
||||
|
||||
// Wait longer than hook confirmation delay (3s)
|
||||
await new Promise(resolve => setTimeout(resolve, 3500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 3500));
|
||||
|
||||
// Cycle should NOT have started because working was detected
|
||||
expect(cycleStarted).not.toHaveBeenCalled();
|
||||
@@ -2608,7 +2616,7 @@ describe('RespawnController Hook-Based Idle Detection', () => {
|
||||
testController.start();
|
||||
testController.signalIdlePrompt(); // Start a cycle
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Now in sending_update state - Stop hook should be ignored
|
||||
testController.signalStopHook();
|
||||
@@ -2658,7 +2666,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion detection to trigger timer
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Should have started at least one timer (completion-confirm or no-output-fallback)
|
||||
expect(timerEvents.length).toBeGreaterThan(0);
|
||||
@@ -2690,9 +2698,9 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
// Send output to trigger no-output timer reset
|
||||
session.simulateTerminalOutput('some output');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const noOutputTimer = timerEvents.find(e => e.name === 'no-output-fallback');
|
||||
const noOutputTimer = timerEvents.find((e) => e.name === 'no-output-fallback');
|
||||
if (noOutputTimer) {
|
||||
expect(noOutputTimer.durationMs).toBe(noOutputTimeoutMs);
|
||||
}
|
||||
@@ -2716,7 +2724,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm timer to fire (50ms + processing)
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
// At least one timer should have completed (completion-confirm)
|
||||
expect(completedTimers.length).toBeGreaterThan(0);
|
||||
@@ -2741,11 +2749,11 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for timer to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Simulate working to cancel the completion confirm timer
|
||||
session.simulateWorking();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Working patterns should have cancelled at least one timer
|
||||
const hasCancel = cancelledTimers.length > 0;
|
||||
@@ -2770,13 +2778,13 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
// Trigger Stop hook to start hook-confirm timer
|
||||
controller.signalStopHook();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Then working patterns should cancel it with a reason
|
||||
session.simulateWorking();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const hookCancel = cancelledTimers.find(e => e.name === 'hook-confirm');
|
||||
const hookCancel = cancelledTimers.find((e) => e.name === 'hook-confirm');
|
||||
if (hookCancel) {
|
||||
expect(hookCancel.reason).toBeTruthy();
|
||||
}
|
||||
@@ -2797,7 +2805,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for timers to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Should have active timers
|
||||
const timersBefore = controller.getActiveTimers();
|
||||
@@ -2822,7 +2830,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for timers to start
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
controller.stop();
|
||||
|
||||
@@ -2835,7 +2843,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
});
|
||||
|
||||
// Wait longer than any timer duration
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
|
||||
expect(timerFiredAfterStop).toBe(false);
|
||||
});
|
||||
@@ -2852,11 +2860,11 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
// First cycle: start, trigger completion-related timers, stop
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Should have completion-confirm timer active
|
||||
const firstCycleTimers = controller.getActiveTimers();
|
||||
const hasCompletionConfirm = firstCycleTimers.some(t => t.name === 'completion-confirm');
|
||||
const hasCompletionConfirm = firstCycleTimers.some((t) => t.name === 'completion-confirm');
|
||||
expect(hasCompletionConfirm).toBe(true);
|
||||
|
||||
controller.stop();
|
||||
@@ -2866,14 +2874,14 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
controller.start();
|
||||
expect(controller.state).toBe('watching');
|
||||
const restartTimers = controller.getActiveTimers();
|
||||
const staleCompletionConfirm = restartTimers.some(t => t.name === 'completion-confirm');
|
||||
const staleCompletionConfirm = restartTimers.some((t) => t.name === 'completion-confirm');
|
||||
expect(staleCompletionConfirm).toBe(false);
|
||||
|
||||
// Can still trigger new timers
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
const newTimers = controller.getActiveTimers();
|
||||
const hasNewCompletionConfirm = newTimers.some(t => t.name === 'completion-confirm');
|
||||
const hasNewCompletionConfirm = newTimers.some((t) => t.name === 'completion-confirm');
|
||||
expect(hasNewCompletionConfirm).toBe(true);
|
||||
|
||||
controller.stop();
|
||||
@@ -2895,13 +2903,13 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
// Wait to check no stale timers fire
|
||||
const countBefore = completedTimers.length;
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
const countAfter = completedTimers.length;
|
||||
|
||||
// No new timer completions should happen after final stop
|
||||
@@ -2920,7 +2928,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const activeTimers = controller.getActiveTimers();
|
||||
expect(activeTimers.length).toBeGreaterThan(0);
|
||||
@@ -2946,7 +2954,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const detectionStatus = controller.getDetectionStatus();
|
||||
expect(Array.isArray(detectionStatus.activeTimers)).toBe(true);
|
||||
@@ -2966,14 +2974,14 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
session.simulateCompletionMessage();
|
||||
|
||||
// Wait for completion confirm timer to be set up
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
const timersBefore = controller.getActiveTimers();
|
||||
const hasCompletionConfirm = timersBefore.some(t => t.name === 'completion-confirm');
|
||||
const hasCompletionConfirm = timersBefore.some((t) => t.name === 'completion-confirm');
|
||||
|
||||
// Wait for the timer to fire
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
const timersAfter = controller.getActiveTimers();
|
||||
const stillHasCompletionConfirm = timersAfter.some(t => t.name === 'completion-confirm');
|
||||
const stillHasCompletionConfirm = timersAfter.some((t) => t.name === 'completion-confirm');
|
||||
|
||||
// If we caught the timer before it fired, it should be gone now
|
||||
if (hasCompletionConfirm) {
|
||||
@@ -2992,7 +3000,7 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
controller.start();
|
||||
session.simulateCompletionMessage();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Should have timers
|
||||
const timersBefore = controller.getActiveTimers();
|
||||
@@ -3000,11 +3008,11 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
// Cancel via working
|
||||
session.simulateWorking();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// completion-confirm should be removed
|
||||
const timersAfter = controller.getActiveTimers();
|
||||
const hasCompletionConfirm = timersAfter.some(t => t.name === 'completion-confirm');
|
||||
const hasCompletionConfirm = timersAfter.some((t) => t.name === 'completion-confirm');
|
||||
expect(hasCompletionConfirm).toBe(false);
|
||||
|
||||
controller.stop();
|
||||
@@ -3019,10 +3027,10 @@ describe('RespawnController CleanupManager Timer Tracking', () => {
|
||||
|
||||
controller.start();
|
||||
controller.signalStopHook();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const activeTimers = controller.getActiveTimers();
|
||||
const hookTimer = activeTimers.find(t => t.name === 'hook-confirm');
|
||||
const hookTimer = activeTimers.find((t) => t.name === 'hook-confirm');
|
||||
expect(hookTimer).toBeDefined();
|
||||
if (hookTimer) {
|
||||
expect(hookTimer.totalMs).toBeGreaterThan(0);
|
||||
|
||||
Reference in New Issue
Block a user