Files
AgentHub/server/__tests__/server-info.test.js
T
nntrivi2001 c7dbaf2d9f refactor: finish the AgentHub rename and drop the MCP server
Two leftovers from the rebrand, done together because they touch the same
docs and manifests.

**Data directory.** `agent-dashboard` could not simply be renamed in the
source: it names a real directory holding the live SQLite database. Startup
now moves it once, via `migrateLegacyDataDir()` in `server/lib/claude-home.js`
— a same-filesystem rename, so it is instant even at multiple gigabytes and
cannot leave two divergent copies. `getDataDir()` stays PURE and resolves to
the legacy path until the move happens; the first version of this put the
rename inside the getter, which meant `npm run test:server` relocated the
developer's real database as a side effect. The call sits behind
`require.main === module` and ahead of the router requires, because those pull
in db.js, which opens SQLite at module load. A failed rename is silent and the
old directory keeps being used: an unfashionable directory name beats a blank
database beside an unreachable one. Same treatment for the port-discovery file
(`.agent-dashboard.json` → `.agenthub.json`, old one dropped on write).

Not renamed on purpose: the Sagi localStorage keys (renaming silently resets
everyone's prefs) and the PWA manifest `id` (changing it orphans an installed
app).

**MCP server.** 4 tool calls in 46 days across 577,132 events, against 4,556
lines of TypeScript, a 2 MB committed build artifact, and a pre-commit guard
that had already blocked commits twice. Deleted: `mcp/`, the maintainer skills
and `mcp-reviewer` agent, the build check, `plugins/agenthub/.mcp.json`,
`docs/MCP.md`, 12 npm scripts, 5 Makefile targets, and every doc section
describing it.

Kept, because they are unrelated things sharing the name: the Config
Explorer's MCP tab and its API, per-lane Playwright MCP, the agenthub-config
plugin's MCP audit, and the historical `mcp__plugin_agenthub_*` events that
analytics must keep attributing.

Verified: server 1432/1432, client 459/459, tsc clean, prettier clean. Both
directions of the migration proven — a sandbox-HOME test run leaves the legacy
directory untouched, and a real boot moves it with its contents intact.
2026-09-08 09:48:40 +07:00

128 lines
4.0 KiB
JavaScript

/**
* @file Tests for multi-server discovery and hook-ingest deduplication by
* SQLite data directory (`server/lib/server-info.js`).
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { describe, it, beforeEach, afterEach } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("fs");
const path = require("path");
const os = require("os");
const STAMP = `server-info-${Date.now()}-${process.pid}`;
const TMP = path.join(os.tmpdir(), STAMP);
const CLAUDE_HOME = path.join(TMP, "home");
const DATA_DIR_A = path.join(TMP, "data-a");
const DATA_DIR_B = path.join(TMP, "data-b");
function freshModule(dataDir) {
delete require.cache[require.resolve("../lib/server-info")];
delete require.cache[require.resolve("../lib/claude-home")];
process.env.CLAUDE_HOME = CLAUDE_HOME;
process.env.DASHBOARD_DATA_DIR = dataDir;
return require("../lib/server-info");
}
function writeDiscovery(servers) {
const infoPath = path.join(CLAUDE_HOME, ".agenthub.json");
fs.mkdirSync(CLAUDE_HOME, { recursive: true });
const recent = servers.reduce((a, b) =>
Date.parse(b.startedAt) > Date.parse(a.startedAt) ? b : a
);
fs.writeFileSync(
infoPath,
JSON.stringify(
{ port: recent.port, pid: recent.pid, startedAt: recent.startedAt, servers },
null,
2
)
);
}
describe("server-info hook ingest deduplication", () => {
beforeEach(() => {
fs.rmSync(TMP, { recursive: true, force: true });
fs.mkdirSync(DATA_DIR_A, { recursive: true });
fs.mkdirSync(DATA_DIR_B, { recursive: true });
});
afterEach(() => {
delete process.env.CLAUDE_DASHBOARD_PORT;
fs.rmSync(TMP, { recursive: true, force: true });
delete require.cache[require.resolve("../lib/server-info")];
delete require.cache[require.resolve("../lib/claude-home")];
});
it("dedupes hook targets when two live servers share one dataDir", () => {
const mod = freshModule(DATA_DIR_A);
writeDiscovery([
{
port: 4820,
pid: process.pid,
startedAt: "2026-01-01T00:00:00.000Z",
dataDir: DATA_DIR_A,
},
{
port: 4821,
pid: process.pid,
startedAt: "2026-01-02T00:00:00.000Z",
dataDir: DATA_DIR_A,
},
]);
assert.deepEqual(mod.resolveHookIngestPorts(), [4820]);
assert.deepEqual(mod.resolveAllDashboardPorts(), [4820, 4821]);
});
it("fans out when live servers use different data directories", () => {
const mod = freshModule(DATA_DIR_A);
writeDiscovery([
{
port: 4820,
pid: process.pid,
startedAt: "2026-01-01T00:00:00.000Z",
dataDir: DATA_DIR_A,
},
{
port: 4900,
pid: process.pid,
startedAt: "2026-01-02T00:00:00.000Z",
dataDir: DATA_DIR_B,
},
]);
assert.deepEqual(mod.resolveHookIngestPorts(), [4820, 4900]);
});
it("treats legacy entries without dataDir as unique per port", () => {
const mod = freshModule(DATA_DIR_A);
writeDiscovery([
{ port: 4820, pid: process.pid, startedAt: "2026-01-01T00:00:00.000Z" },
{ port: 4821, pid: process.pid, startedAt: "2026-01-02T00:00:00.000Z" },
]);
assert.deepEqual(mod.resolveHookIngestPorts(), [4820, 4821]);
});
it("records dataDir when writing server info", () => {
const mod = freshModule(DATA_DIR_A);
mod.writeServerInfo(4999);
const raw = JSON.parse(fs.readFileSync(mod.getServerInfoPath(), "utf8"));
const entry = raw.servers.find((s) => s.port === 4999);
assert.ok(entry);
assert.equal(mod.normalizeDataDir(entry.dataDir), mod.normalizeDataDir(DATA_DIR_A));
});
it("honors CLAUDE_DASHBOARD_PORT for hook ingest", () => {
const mod = freshModule(DATA_DIR_A);
process.env.CLAUDE_DASHBOARD_PORT = "7777";
writeDiscovery([
{
port: 4820,
pid: process.pid,
startedAt: "2026-01-01T00:00:00.000Z",
dataDir: DATA_DIR_A,
},
]);
assert.deepEqual(mod.resolveHookIngestPorts(), [7777]);
});
});