Files
paseo/packages/server/scripts/supervisor.readiness.test.ts
paseo-bot[bot] e07da55f81 Show Open in editor for a password-protected desktop daemon (#5335)
* fix(daemon): report the local daemon's server id when a password is set

Since #4575, `paseo daemon status` only learned the server id from an
authenticated connection. The desktop app runs that command without the
daemon password, so a password-protected desktop daemon reported no server
id, the app treated it as remote, and hid Open in editor.

The worker now announces its server id with readiness and the supervisor
publishes it in the instance record next to the listen address, so status
reports the running daemon's identity from local state.

Closes #5300

* fix(cli): omit serverId from status until a daemon publishes one

* chore: rerun CI after macOS file observation flake

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-24 10:00:01 +02:00

123 lines
4.2 KiB
TypeScript

import { spawn } from "node:child_process";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect, test } from "vitest";
async function runWorker(worker: string, publicationDelay = 0) {
const home = await mkdtemp(path.join(tmpdir(), "paseo-readiness-"));
const workerPath = path.join(home, "worker.mjs");
const runnerPath = path.join(home, "runner.mjs");
const eventsPath = path.join(home, "events.jsonl");
const supervisor = new URL("./supervisor.ts", import.meta.url).href;
await writeFile(workerPath, worker);
await writeFile(
runnerPath,
`
import { appendFile } from "node:fs/promises";
import { runSupervisor } from ${JSON.stringify(supervisor)};
const record = (event) => appendFile(${JSON.stringify(eventsPath)}, JSON.stringify(event) + "\\n");
runSupervisor({
name: "ReadinessTest", startupMessage: "starting", restartOnCrash: true,
resolveWorkerEntry: () => ${JSON.stringify(workerPath)}, workerArgs: [], workerExecArgv: [],
onWorkerReady: async ({ listen }) => {
await new Promise(resolve => setTimeout(resolve, ${publicationDelay}));
await record(listen);
},
onWorkerExit: () => record(null),
});
`,
);
const env = Object.fromEntries(
Object.entries(process.env).filter(([key]) => !key.startsWith("PASEO_")),
);
const child = spawn(process.execPath, ["--import", "tsx", runnerPath], {
cwd: fileURLToPath(new URL("../../../", import.meta.url)),
env: { ...env, HOME: home, PASEO_HOME: home },
stdio: ["ignore", "pipe", "pipe"],
});
let output = "";
child.stdout.on("data", (chunk) => {
output += chunk;
});
child.stderr.on("data", (chunk) => {
output += chunk;
});
try {
const code = await new Promise<number | null>((resolve, reject) => {
const timer = setTimeout(() => {
child.kill("SIGKILL");
reject(new Error(output));
}, 5_000);
child.once("error", reject);
child.once("close", (exitCode) => {
clearTimeout(timer);
resolve(exitCode);
});
});
const events = await readFile(eventsPath, "utf8").catch(() => "");
return {
code,
output,
events: events
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line)),
};
} finally {
await rm(home, { recursive: true, force: true });
}
}
test("a worker that exits before ready fails the supervisor without respawning", async () => {
const result = await runWorker(`
import { existsSync, writeFileSync } from "node:fs";
const marker = process.argv[1] + ".started";
if (existsSync(marker)) process.exit(0);
writeFileSync(marker, "started");
process.exit(1);
`);
expect(result.code).toBe(1);
expect(result.output).not.toContain("Restarting worker");
});
test("a replacement worker must independently reach ready", async () => {
const result = await runWorker(`
import { existsSync, writeFileSync } from "node:fs";
const marker = process.argv[1] + ".started";
if (existsSync(marker)) process.exit(0);
writeFileSync(marker, "started");
process.on("message", message => {
if (message.type === "paseo:graceful-shutdown") process.exit(0);
});
process.send({ type: "paseo:ready", listen: "test-endpoint", serverId: "srv_test" });
process.send({ type: "paseo:restart" });
`);
expect(result.code).toBe(1);
expect(result.events, result.output).toEqual(["test-endpoint", null, null]);
});
test("exit clears publication after an in-flight ready write", async () => {
const result = await runWorker(
`
process.send({ type: "paseo:ready", listen: "test-endpoint", serverId: "srv_test" }, () => process.exit(0));
`,
100,
);
expect(result.code).toBe(0);
expect(result.events.at(-1)).toBeNull();
});
test("requested shutdown before first readiness exits successfully", async () => {
const result = await runWorker(`
process.on("message", message => {
if (message.type === "paseo:graceful-shutdown") process.exit(0);
});
process.send({ type: "paseo:shutdown", reason: "cancelled_start" });
`);
expect(result.code).toBe(0);
expect(result.events).toEqual([null]);
});