/** * @file Tests the status-area icon's keeper (server/lib/tray.js): on by * default, never started once the user hid it, one icon at a time, a stale * heartbeat never trusted (its pid may be another program's by now), an icon * from another install root replaced only at server start, and no retry loop * for an icon that keeps dying. No real icon is spawned. * @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 { EventEmitter } = require("node:events"); const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-tray-")); process.env.DASHBOARD_DATA_DIR = TMP; process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db"); process.env.DISPLAY = process.env.DISPLAY || ":99"; // guiEnv() on Linux const tray = require("../lib/tray"); after(() => fs.rmSync(TMP, { recursive: true, force: true })); function fakeSpawn() { const calls = []; const spawn = (cmd, args, opts) => { calls.push({ cmd, args, opts }); const child = new EventEmitter(); child.unref = () => {}; return child; }; return { spawn, calls }; } describe("tray keeper", () => { beforeEach(() => { fs.rmSync(path.join(TMP, "runtime"), { recursive: true, force: true }); }); it("is on by default and starts scripts/tray.js detached", () => { assert.equal(tray.isEnabled(), true); const { spawn, calls } = fakeSpawn(); assert.equal(tray.ensureTray({ spawn }), "started"); assert.equal(calls.length, 1); assert.match(calls[0].args[0], /scripts[\\/]tray\.js$/); assert.equal(calls[0].opts.detached, true); }); it("starts nothing once the user hid it", () => { fs.mkdirSync(path.dirname(tray.offPath()), { recursive: true }); fs.writeFileSync(tray.offPath(), "x"); const { spawn, calls } = fakeSpawn(); assert.equal(tray.ensureTray({ spawn }), "skipped"); assert.equal(calls.length, 0); }); it("leaves a live icon alone, and replaces one from another root only when asked", () => { tray.writePid(process.pid, "/some/other/root"); const { spawn, calls } = fakeSpawn(); assert.equal(tray.ensureTray({ spawn }), "running"); assert.equal(calls.length, 0); tray.writePid(process.pid); // this root: nothing to replace assert.equal(tray.ensureTray({ spawn, replace: true }), "running"); assert.equal(calls.length, 0); }); it("does not trust a heartbeat that went stale", () => { tray.writePid(process.pid); const old = new Date(Date.now() - 10 * tray.HEARTBEAT_MS); fs.utimesSync(tray.pidPath(), old, old); assert.equal(tray.liveTray(), null); }); it("writes and clears the off flag", () => { assert.equal(tray.setEnabled(false).enabled, false); assert.equal(fs.existsSync(tray.offPath()), true); tray.writePid(999_999_999); // no such process: nothing to signal, nothing to trust const { spawn, calls } = fakeSpawn(); assert.equal(tray.setEnabled(true, { spawn }).enabled, true); assert.equal(fs.existsSync(tray.offPath()), false); assert.equal(calls.length, 1, "turning it on shows the icon at once"); }); it("gives up on an icon that keeps dying at once", () => { const spawned = []; const spawn = () => { const child = new EventEmitter(); child.unref = () => {}; spawned.push(child); return child; }; for (let i = 0; i < 5; i++) { if (tray.ensureTray({ spawn }) === "started") spawned.at(-1).emit("exit", 1); } assert.ok(spawned.length <= 4, `retried ${spawned.length} times`); assert.equal(tray.ensureTray({ spawn }), "skipped"); }); });