feat(bridge): establish local pi status bridge
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user