// The service manager's verbs over a fake runner (novox/hq ADR 0177, to-be 41 WP4): which manager // a call reaches and how, acts on the system manager escalated, failures named rather than read as // empty answers, and whether the mesh declares a unit. import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { MESH_UNIT_HEADER, ServiceManager, escalated, sessionEnv, unitArg, type Ran, type Runner } from "../client.ts"; interface Call { cmd: string; args: string[]; env?: NodeJS.ProcessEnv; } function fake(answer: (c: Call) => Partial, calls: Call[] = []): Runner { return async (cmd, args, env) => { const c = { cmd, args, env }; calls.push(c); return { stdout: "", stderr: "", status: 0, ...answer(c) }; }; } const LIST = "sshd.service loaded active running OpenSSH Daemon\nbroken.service loaded failed failed A broken thing\n"; const SHOW_MESH = "LoadState=loaded\nActiveState=active\nSubState=running\nUnitFileState=enabled\nMainPID=42\nExecMainStatus=0\nDescription=showcase, a mesh daemon\nFragmentPath=/etc/systemd/system/showcase.service\n"; const SHOW_PACKAGE = "LoadState=loaded\nActiveState=active\nSubState=running\nUnitFileState=enabled\nMainPID=7\nExecMainStatus=0\nDescription=OpenSSH Daemon\nFragmentPath=/usr/lib/systemd/system/sshd.service\n"; const files: Record = { "/etc/systemd/system/showcase.service": `${MESH_UNIT_HEADER} Do not edit — this file is replaced whenever the\n[Unit]\n`, "/usr/lib/systemd/system/sshd.service": "[Unit]\nDescription=OpenSSH Daemon\n", }; const read = async (p: string) => { if (p in files) return files[p]; throw new Error("ENOENT"); }; function manager(run: Runner, o: { uid?: number; user?: string } = {}): ServiceManager { return new ServiceManager({ account: "operator", uid: o.uid ?? 1000, user: o.user ?? "operator", run, read }); } test("an act on the system manager goes through sudo without a prompt unless this is root; reads never do", () => { assert.deepEqual(escalated("systemctl", ["restart", "sshd.service"], "system", 1000), ["sudo", ["-n", "systemctl", "restart", "sshd.service"]]); for (const verb of ["start", "stop", "enable", "disable"]) { assert.equal(escalated("systemctl", [verb, "x.service"], "system", 1000)[0], "sudo"); } assert.deepEqual(escalated("systemctl", ["restart", "sshd.service"], "system", 0), ["systemctl", ["restart", "sshd.service"]]); assert.deepEqual(escalated("systemctl", ["show", "sshd.service"], "system", 1000), ["systemctl", ["show", "sshd.service"]]); assert.deepEqual(escalated("systemctl", ["list-units", "restart"], "system", 1000)[0], "systemctl"); assert.deepEqual(escalated("systemctl", ["--user", "restart", "x.service"], "user", 1000)[0], "systemctl"); assert.deepEqual(escalated("journalctl", ["-u", "x"], "system", 1000)[0], "journalctl"); }); test("the user scope is plain --user, with the account's runtime directory and bus named", async () => { const calls: Call[] = []; const m = manager(fake(() => ({ stdout: LIST }), calls), { uid: 1234 }); await m.units("user"); assert.equal(calls[0].cmd, "systemctl"); assert.equal(calls[0].args[0], "--user"); assert.ok(!calls[0].args.some((a) => a.startsWith("--machine"))); assert.equal(calls[0].env?.XDG_RUNTIME_DIR, "/run/user/1234"); assert.equal(calls[0].env?.DBUS_SESSION_BUS_ADDRESS, "unix:path=/run/user/1234/bus"); await m.journal("user", "watcher.service", 10); assert.deepEqual(calls[1].args.slice(0, 1), ["--user"]); assert.equal(calls[1].cmd, "journalctl"); assert.equal(calls[1].env?.XDG_RUNTIME_DIR, "/run/user/1234"); assert.deepEqual(sessionEnv(5, { HOME: "/h" }), { HOME: "/h", XDG_RUNTIME_DIR: "/run/user/5", DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/5/bus" }); }); test("the system scope is given no session words", async () => { const calls: Call[] = []; await manager(fake(() => ({ stdout: LIST }), calls)).units("system"); assert.equal(calls[0].env, undefined); assert.ok(!calls[0].args.includes("--user")); }); test("the user scope from a process that is not the account is refused, not answered from the wrong manager", async () => { const m = manager(fake(() => ({ stdout: LIST })), { user: "root", uid: 0 }); await assert.rejects(() => m.units("user"), /operator's service manager, and this runs as root/); }); test("a system act escalates, and answers with the state after", async () => { const calls: Call[] = []; const m = manager(fake((c) => (c.args.includes("show") ? { stdout: SHOW_PACKAGE } : {}), calls)); const r = await m.act("system", "restart", "sshd.service"); assert.deepEqual([calls[0].cmd, ...calls[0].args], ["sudo", "-n", "systemctl", "restart", "sshd.service"]); assert.equal(r.ok, true); assert.equal(r.active, "active"); assert.equal(r.mesh_declared, false); assert.equal(r.note, undefined, "no restore note on a unit the mesh did not write"); }); test("the restore note is attached only to a unit the mesh declares", async () => { const m = manager(fake((c) => (c.args.includes("show") ? { stdout: SHOW_MESH } : {}))); const r = await m.act("system", "stop", "showcase.service"); assert.equal(r.mesh_declared, true); assert.match(String(r.note), /host restores its declared state/); }); test("status says whether the mesh declares the unit, from the unit file systemd loaded", async () => { const mesh = await manager(fake(() => ({ stdout: SHOW_MESH }))).status("system", "showcase.service"); assert.equal(mesh.mesh_declared, true); assert.equal(mesh.MainPID, "42"); const pkg = await manager(fake(() => ({ stdout: SHOW_PACKAGE }))).status("system", "sshd.service"); assert.equal(pkg.mesh_declared, false); const none = await manager(fake(() => ({ stdout: "LoadState=not-found\nFragmentPath=\n" }))).status("system", "nope.service"); assert.equal(none.mesh_declared, false); }); test("the header recognised is the one the host writes", () => { // mesh-host internal/apply/process.go, unitFor: the first line of every unit the host writes. assert.equal(MESH_UNIT_HEADER, "# Generated by the mesh."); }); test("sudo refusing is named as a refusal; sudo missing is named as missing", async () => { const refused = manager(fake(() => ({ status: 1, stderr: "sudo: a password is required\n" }))); await assert.rejects(() => refused.act("system", "start", "x.service"), /needs root for this and the runtime's account may not run it without a prompt: sudo: a password is required/); const missing = manager(fake(() => ({ status: 127, error: "ENOENT" }))); await assert.rejects(() => missing.act("system", "start", "x.service"), /sudo is not installed here/); }); test("polkit refusing is named", async () => { const m = manager(fake(() => ({ status: 1, stderr: "Failed to stop x.service: Access denied as the requested operation requires interactive authentication.\n" })), { uid: 0, user: "root" }); await assert.rejects(() => m.act("system", "stop", "x.service"), /the service manager refused the runtime's account/); }); test("a failed systemctl is an error, not an empty list", async () => { const m = manager(fake(() => ({ status: 1, stderr: "Failed to list units: Connection timed out\n" }))); await assert.rejects(() => m.units("system"), /systemctl failed \(1\): Failed to list units: Connection timed out/); }); test("an unreachable user manager is said, even when systemctl exits 0", async () => { const said = "Failed to connect to user scope bus via local transport: No such file or directory\n"; const m = manager(fake(() => ({ status: 0, stderr: said })), { uid: 1000 }); await assert.rejects(() => m.units("user"), /operator's own service manager does not answer at \/run\/user\/1000/); const nonzero = manager(fake(() => ({ status: 1, stderr: said }))); await assert.rejects(() => nonzero.status("user", "x.service"), /does not answer/); }); test("failed reports each manager's failed units, and a manager that does not answer by its error", async () => { const m = manager(fake((c) => c.args[0] === "--user" ? { status: 1, stderr: "Failed to connect to user scope bus via local transport: No such file or directory\n" } : { stdout: LIST })); const r = await m.failed(); assert.deepEqual(r.system, [{ unit: "broken.service", load: "loaded", active: "failed", sub: "failed", description: "A broken thing" }]); assert.ok(!Array.isArray(r.user)); assert.match((r.user as { error: string }).error, /does not answer/); }); test("a unit's name is never an option", async () => { assert.throws(() => unitArg("--host=elsewhere"), /is not a unit's name/); assert.throws(() => unitArg("a b"), /is not a unit's name/); assert.equal(unitArg("sshd.service"), "sshd.service"); const calls: Call[] = []; const m = manager(fake(() => ({ stdout: LIST }), calls)); await assert.rejects(() => m.act("system", "stop", "-H"), /is not a unit's name/); assert.equal(calls.length, 0, "nothing ran"); await m.units("system", "-x*"); assert.deepEqual(calls[0].args.slice(-2), ["--", "-x*"]); }); test("the manifest declares no package — the service manager is always there, and networkd declares it too", () => { const m = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8")); assert.ok(!(m.resources ?? []).some((r: { type: string }) => r.type === "package")); assert.ok(!m.capabilities.includes("package-manager")); assert.deepEqual(m.claims[0].serves, ["units", "status", "start", "stop", "restart", "enable", "disable", "journal"]); });