/** * @file Tests the `systemd` row of scripts/plugin-doctor.js. * * The row exists for a failure nothing else in the report can see: a unit still * naming a retired plugin cache directory leaves a running, healthy-looking * dashboard that simply does not come back after the next reboot — it starts a * path Claude Code deleted during the update. Every other row would read OK. * * WARN and never FAIL: the dashboard in front of the user works, and plenty of * machines legitimately have no unit at all. * @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-doctor-systemd-")); const prevHome = process.env.HOME; // Before anything resolves a unit path: `unitPath()` reads HOME, and a path // computed against the real one makes these tests rewrite the developer's own // ~/.config/systemd/user/agenthub.service. process.env.HOME = TMP_HOME; process.env.CLAUDE_HOME = path.join(TMP_HOME, ".claude"); const svc = require("../../scripts/systemd-service"); const { diagnose } = require("../../scripts/plugin-doctor"); const UNIT = svc.unitPath("agenthub"); const PLUGIN_ROOT = path.resolve(__dirname, "..", ".."); assert.ok(UNIT.startsWith(TMP_HOME + path.sep), `refusing to touch ${UNIT}`); beforeEach(() => { fs.rmSync(UNIT, { force: true }); // Claim systemd exists and the unit is running, so each test varies exactly // one thing: the unit file on disk. svc.__setExecImpl((args) => ({ status: 0, stdout: args[0] === "is-active" ? "active\n" : "", })); }); after(() => { svc.__reset(); if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; delete process.env.CLAUDE_HOME; fs.rmSync(TMP_HOME, { recursive: true, force: true }); }); function row() { const found = diagnose().find((r) => r.label === "systemd"); assert.ok(found, "the doctor must always emit a systemd row"); return found; } function writeUnit(workingDir) { fs.mkdirSync(path.dirname(UNIT), { recursive: true }); fs.writeFileSync( UNIT, "[Unit]\n# agenthub-managed-unit — regenerated on install, do not edit by hand\n" + `[Service]\nWorkingDirectory=${workingDir}\n`, "utf8" ); } const linuxOnly = { skip: process.platform !== "linux" ? "systemd is Linux-only" : false }; describe("plugin-doctor: systemd row", linuxOnly, () => { it("warns, never fails, when the unit names a retired plugin root", () => { writeUnit("/plugins/cache/sg/agenthub/deadbeef"); const r = row(); assert.equal(r.level, "warn", "a working dashboard must not fail the doctor run"); assert.match(r.detail, /previous plugin version/i); // The consequence is the whole point — a reader who does not know it will // ignore the row. assert.match(r.detail, /reboot/i); assert.match(r.detail, /plugin-bootstrap\.js --force|new Claude Code session/); }); it("passes when the unit is current and running", () => { writeUnit(PLUGIN_ROOT); const r = row(); assert.equal(r.level, "ok", r.detail); assert.match(r.detail, /current and running/i); }); it("warns when the unit is current but stopped", () => { writeUnit(PLUGIN_ROOT); svc.__setExecImpl((args) => ({ status: 3, stdout: args[0] === "is-active" ? "inactive\n" : "", })); const r = row(); assert.equal(r.level, "warn"); assert.match(r.detail, /not running/i); }); it("reports no unit as fine, not as a problem", () => { // A plugin install without `agenthub service install` is a normal setup: // the server is started per session. const r = row(); assert.equal(r.level, "ok", r.detail); assert.match(r.detail, /no unit/i); }); it("warns about a unit it did not write, and never claims it is stale", () => { fs.mkdirSync(path.dirname(UNIT), { recursive: true }); fs.writeFileSync(UNIT, "[Service]\nExecStart=/opt/someone-elses-agenthub\n", "utf8"); const r = row(); assert.equal(r.level, "warn"); assert.match(r.detail, /not written by AgentHub|left untouched/i); }); });