import assert from "node:assert/strict"; import test from "node:test"; import { createRecoverySupervisor } from "../src/bridge/recovery-supervisor.js"; test("restarts after bounded exponential delays and stops after exhaustion", async () => { const delays = []; const states = []; let starts = 0; const supervisor = createRecoverySupervisor({ start: async () => ({ attempt: ++starts }), maxAttempts: 2, delayForAttempt: (attempt) => attempt * 100, sleep: async (delay) => delays.push(delay), onState: (state) => states.push(state), }); assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 1 }); assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 2 }); assert.equal(await supervisor.handleUnexpectedExit(), undefined); assert.deepEqual(delays, [100, 200]); assert.deepEqual( states.map(({ state, attempt }) => ({ state, attempt })), [ { state: "recovering", attempt: 1 }, { state: "healthy", attempt: 1 }, { state: "recovering", attempt: 2 }, { state: "healthy", attempt: 2 }, { state: "failed", attempt: 2 }, ], ); }); test("restarts failed launches within the same bounded recovery budget", async () => { let starts = 0; const supervisor = createRecoverySupervisor({ start: async () => { starts += 1; if (starts === 1) throw new Error("temporary spawn failure"); return { attempt: starts }; }, maxAttempts: 2, delayForAttempt: () => 0, sleep: async () => {}, }); assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 2 }); assert.equal(starts, 2); }); test("cancels scheduled recovery when stopped", async () => { let resolveSleep; const supervisor = createRecoverySupervisor({ start: async () => assert.fail("must not restart after stop"), delayForAttempt: () => 1, sleep: () => new Promise((resolve) => { resolveSleep = resolve; }), }); const recovering = supervisor.handleUnexpectedExit(); supervisor.stop(); resolveSleep(); assert.equal(await recovering, undefined); });