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,106 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
import {
|
||||
BridgeAlreadyRunningError,
|
||||
BridgeLockError,
|
||||
acquireBridgeLock,
|
||||
lockMode,
|
||||
} from "../src/bridge/instance-lock.js";
|
||||
|
||||
async function paths() {
|
||||
const directory = await mkdtemp(join(tmpdir(), "pi-status-bridge-lock-"));
|
||||
return {
|
||||
lockPath: join(directory, "bridge.lock"),
|
||||
socketPath: join(directory, "bridge.sock"),
|
||||
};
|
||||
}
|
||||
|
||||
test("acquires an owner-only lock and releases only its own token", async () => {
|
||||
const { lockPath, socketPath } = await paths();
|
||||
const lock = await acquireBridgeLock({
|
||||
lockPath,
|
||||
socketPath,
|
||||
pid: 101,
|
||||
now: () => new Date("2026-01-02T03:04:05.000Z"),
|
||||
});
|
||||
const lockContents = await readFile(lockPath, "utf8");
|
||||
let metadata;
|
||||
try {
|
||||
metadata = JSON.parse(lockContents);
|
||||
} catch (error) {
|
||||
assert.fail(
|
||||
error instanceof Error ? error.message : "lock file was not JSON",
|
||||
);
|
||||
}
|
||||
|
||||
const lockStats = await stat(lockPath);
|
||||
assert.equal(metadata.pid, 101);
|
||||
assert.equal(metadata.socketPath, socketPath);
|
||||
assert.equal(lockStats.mode & 0o777, 0o600);
|
||||
assert.equal(lockMode, 0o600);
|
||||
assert.equal(await lock.release(), true);
|
||||
assert.equal(await lock.release(), false);
|
||||
});
|
||||
|
||||
test("refuses to replace a live owner", async () => {
|
||||
const { lockPath, socketPath } = await paths();
|
||||
await writeFile(
|
||||
lockPath,
|
||||
JSON.stringify({
|
||||
pid: 202,
|
||||
socketPath,
|
||||
startedAt: "2026-01-02T03:04:05.000Z",
|
||||
token: "live-owner",
|
||||
}),
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
acquireBridgeLock({
|
||||
lockPath,
|
||||
socketPath,
|
||||
pid: 303,
|
||||
processAlive: (pid) => pid === 202,
|
||||
}),
|
||||
BridgeAlreadyRunningError,
|
||||
);
|
||||
});
|
||||
|
||||
test("recovers only a proven-stale lock", async () => {
|
||||
const { lockPath, socketPath } = await paths();
|
||||
await writeFile(
|
||||
lockPath,
|
||||
JSON.stringify({
|
||||
pid: 404,
|
||||
socketPath,
|
||||
startedAt: "2026-01-02T03:04:05.000Z",
|
||||
token: "stale-owner",
|
||||
}),
|
||||
);
|
||||
|
||||
const lock = await acquireBridgeLock({
|
||||
lockPath,
|
||||
socketPath,
|
||||
pid: 505,
|
||||
processAlive: () => false,
|
||||
});
|
||||
assert.equal(lock.metadata.pid, 505);
|
||||
await lock.release();
|
||||
});
|
||||
|
||||
test("does not reclaim a malformed lock", async () => {
|
||||
const { lockPath, socketPath } = await paths();
|
||||
await writeFile(lockPath, "broken");
|
||||
|
||||
await assert.rejects(
|
||||
acquireBridgeLock({
|
||||
lockPath,
|
||||
socketPath,
|
||||
pid: 606,
|
||||
processAlive: () => false,
|
||||
}),
|
||||
BridgeLockError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user