/** * @file Tests scripts/launchd-service.js, the macOS LaunchAgent twin of the * systemd unit: the plist it writes (KeepAlive, AbandonProcessGroup so lane * panes survive a restart, the plugin root as WorkingDirectory), that writing * starts nothing, that a restart re-reads the plist (bootout + bootstrap, not * kickstart), and that a foreign plist is never touched. `launchctl` is * stubbed and the platform faked, so this runs on any OS. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, beforeEach, after } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const TMP_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-launchd-")); const prevHome = process.env.HOME; process.env.HOME = TMP_HOME; const realPlatform = Object.getOwnPropertyDescriptor(process, "platform"); Object.defineProperty(process, "platform", { value: "darwin" }); const svc = require("../../scripts/launchd-service"); const DOMAIN = `gui/${process.getuid()}`; const JOB = `${DOMAIN}/io.smartgift.agenthub`; let calls = []; let loaded = false; let running = false; let failBootstrap = false; let noGui = false; beforeEach(() => { calls = []; loaded = running = failBootstrap = noGui = false; fs.rmSync(svc.unitPath("agenthub"), { force: true }); svc.__setExecImpl((args) => { if (args[0] === "print" && args[1] === DOMAIN) return { status: noGui ? 113 : 0 }; calls.push(args.join(" ")); if (args[0] === "print") { return { status: loaded ? 0 : 113, stdout: running ? "\tstate = running\n" : "\tstate = not running\n", }; } if (args[0] === "bootstrap") return { status: failBootstrap ? 5 : 0 }; return { status: 0 }; }); }); after(() => { svc.__reset(); Object.defineProperty(process, "platform", realPlatform); if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; fs.rmSync(TMP_HOME, { recursive: true, force: true }); }); const UNIT = { name: "agenthub", description: "AgentHub dashboard server (Claude Code plugin)", workingDir: "/plugin/root/new & improved", execStart: "/usr/local/bin/node /plugin/root/new/server/index.js", argv: ["/usr/local/bin/node", "/plugin/root/new & improved/server/index.js"], env: { NODE_ENV: "production", PATH: "/usr/bin:/bin" }, }; const plist = () => fs.readFileSync(svc.unitPath("agenthub"), "utf8"); describe("launchd-service — writeUserService", () => { it("writes a LaunchAgent that restarts forever and spares the lane panes, and starts nothing", () => { assert.equal(svc.writeUserService(UNIT), "written"); const body = plist(); assert.ok(svc.unitPath("agenthub").startsWith(path.join(TMP_HOME, "Library", "LaunchAgents"))); assert.match(body, /Label<\/key>\s*io\.smartgift\.agenthub<\/string>/); assert.match(body, /KeepAlive<\/key>\s*/); assert.match(body, /RunAtLoad<\/key>\s*/); assert.match(body, /AbandonProcessGroup<\/key>\s*/); // argv stays split, so a path with a space is one argument; XML escaped. assert.match(body, /\/plugin\/root\/new & improved\/server\/index\.js<\/string>/); assert.match(body, /PATH<\/key>\s*\/usr\/bin:\/bin<\/string>/); assert.deepEqual(calls, [`enable ${JOB}`], "writing must not load or start the job"); }); it("leaves an identical plist alone", () => { svc.writeUserService(UNIT); calls = []; assert.equal(svc.writeUserService(UNIT), "unchanged"); assert.deepEqual(calls, []); }); it("never overwrites a plist it did not write", () => { fs.mkdirSync(path.dirname(svc.unitPath("agenthub")), { recursive: true }); fs.writeFileSync(svc.unitPath("agenthub"), "theirs"); assert.equal(svc.writeUserService(UNIT), "foreign"); assert.equal(plist(), "theirs"); assert.equal(svc.uninstallUserService("agenthub"), "foreign"); assert.ok(fs.existsSync(svc.unitPath("agenthub"))); }); it("is unavailable without a GUI login, and writes nothing", () => { noGui = true; assert.equal(svc.systemctlAvailable(), false); assert.equal(svc.writeUserService(UNIT), "unavailable"); assert.ok(!fs.existsSync(svc.unitPath("agenthub"))); }); }); describe("launchd-service — stale and root", () => { it("reads the working directory back, escaped path included", () => { svc.writeUserService(UNIT); assert.equal(svc.unitRoot("agenthub"), UNIT.workingDir); assert.equal(svc.unitIsStale("agenthub", UNIT.workingDir), false); assert.equal(svc.unitIsStale("agenthub", "/plugin/root/newer"), true); }); it("calls neither a missing nor a foreign plist stale", () => { assert.equal(svc.unitIsStale("agenthub", "/x"), false); fs.mkdirSync(path.dirname(svc.unitPath("agenthub")), { recursive: true }); fs.writeFileSync( svc.unitPath("agenthub"), "WorkingDirectory/elsewhere" ); assert.equal(svc.unitIsStale("agenthub", "/x"), false); assert.equal(svc.unitRoot("agenthub"), "/elsewhere", "the pruner still must not delete it"); }); }); describe("launchd-service — restart, install, uninstall", () => { it("restarts by bootout + bootstrap, so the new plist is what runs", () => { svc.writeUserService(UNIT); calls = []; assert.equal(svc.restartUserService("agenthub"), "restarted"); assert.deepEqual(calls, [`bootout ${JOB}`, `bootstrap ${DOMAIN} ${svc.unitPath("agenthub")}`]); }); it("reports a failed bootstrap, so the caller can fall back", () => { failBootstrap = true; assert.equal(svc.restartUserService("agenthub"), "failed"); }); it("install loads a new plist, and only kickstarts one already loaded", () => { assert.equal(svc.installUserService(UNIT), "written"); assert.ok(calls.includes(`bootstrap ${DOMAIN} ${svc.unitPath("agenthub")}`)); calls = []; loaded = true; assert.equal(svc.installUserService(UNIT), "unchanged"); assert.ok(calls.includes(`kickstart ${JOB}`)); assert.ok(!calls.some((c) => c.startsWith("bootout")), "a running dashboard is not bounced"); }); it("uninstall unloads and deletes our plist", () => { svc.writeUserService(UNIT); calls = []; assert.equal(svc.uninstallUserService("agenthub"), "removed"); assert.deepEqual(calls, [`bootout ${JOB}`]); assert.ok(!fs.existsSync(svc.unitPath("agenthub"))); assert.equal(svc.uninstallUserService("agenthub"), "absent"); }); it("is active only when launchd says the job is running", () => { assert.equal(svc.unitIsActive("agenthub"), false); loaded = true; assert.equal(svc.unitIsActive("agenthub"), false); running = true; assert.equal(svc.unitIsActive("agenthub"), true); }); }); describe("service-manager", () => { it("hands out the LaunchAgent on macOS", () => { delete require.cache[require.resolve("../../scripts/service-manager")]; assert.equal(require("../../scripts/service-manager").kind, "launchd"); }); });