b7fea83ed6
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
66 lines
2.0 KiB
JavaScript
66 lines
2.0 KiB
JavaScript
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);
|
|
});
|