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, ); });