/** * @file hub-boundary.test.js * @description `server/hub/` (the remote-clients hub, server mode only) is * loaded only behind `isServerMode()`: no file outside it may require it, * except `server/index.js` on lines that test the mode themselves. And a * local-mode app really leaves it out — no module under `server/hub/` is in * the require cache after `createApp()` in local mode. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); const path = require("node:path"); const fs = require("node:fs"); const os = require("node:os"); const ROOT = path.join(__dirname, "..", ".."); const HUB = path.join(ROOT, "server", "hub"); const SCAN = ["server", "bin", "scripts", "mcp", "statusline"]; const REQUIRE_HUB = /require\(\s*["'`][^"'`]*\bhub(?:\/[^"'`]*)?["'`]\s*\)/; function jsFiles(dir) { const out = []; for (const e of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, e.name); if (e.isDirectory()) { if (["node_modules", "__tests__", "dist"].includes(e.name) || full === HUB) continue; out.push(...jsFiles(full)); } else if (/\.(c|m)?js$/.test(e.name)) { out.push(full); } } return out; } describe("server/hub boundary", () => { it("nothing outside server/hub requires it, except index.js behind isServerMode()", () => { const offenders = []; for (const top of SCAN) { const dir = path.join(ROOT, top); if (!fs.existsSync(dir)) continue; for (const file of jsFiles(dir)) { const lines = fs.readFileSync(file, "utf8").split("\n"); lines.forEach((line, i) => { if (!REQUIRE_HUB.test(line)) return; const rel = path.relative(ROOT, file); if (rel === path.join("server", "index.js") && /isServerMode\(\)/.test(line)) return; offenders.push(`${rel}:${i + 1}: ${line.trim()}`); }); } } assert.deepEqual(offenders, []); }); it("a local-mode app loads no module of server/hub", () => { const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "hub-boundary-")); process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db"); process.env.CLAUDE_HOME = path.join(TMP, ".claude"); process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data"); delete process.env.AGENTHUB_MODE; try { require("../index").createApp(); const loaded = Object.keys(require.cache).filter((k) => k.startsWith(HUB + path.sep)); assert.deepEqual(loaded, []); } finally { fs.rmSync(TMP, { recursive: true, force: true }); } }); });