feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
/**
* @file app-config.test.ts
* @description Unit tests for the app configuration loader, which reads environment variables and constructs a configuration object for the MCP server. The tests cover default values, parsing of different transport modes, HTTP port and host parsing with validation, boolean parsing for mutation/destructive flags, timeout and retry parsing with clamping, log level parsing with fallback, dashboard URL validation to ensure it targets a local host and uses http/https, and custom server name/version parsing. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { loadConfig, type TransportMode } from "../src/config/app-config.js";
function env(overrides: Record<string, string> = {}): NodeJS.ProcessEnv {
return {
MCP_DASHBOARD_BASE_URL: "http://127.0.0.1:4820",
...overrides,
};
}
describe("loadConfig", () => {
it("returns sane defaults when no env vars set", () => {
const cfg = loadConfig(env());
assert.equal(cfg.serverName, "agent-dashboard-mcp");
assert.equal(cfg.serverVersion, "1.0.0");
assert.equal(cfg.dashboardBaseUrl.toString(), "http://127.0.0.1:4820/");
assert.equal(cfg.requestTimeoutMs, 10_000);
assert.equal(cfg.retryCount, 2);
assert.equal(cfg.retryBackoffMs, 250);
assert.equal(cfg.allowMutations, false);
assert.equal(cfg.allowDestructive, false);
assert.equal(cfg.logLevel, "info");
assert.equal(cfg.transport, "stdio");
assert.equal(cfg.httpPort, 8819);
assert.equal(cfg.httpHost, "127.0.0.1");
});
// ── Transport parsing ───────────────────────────────────────
it("parses MCP_TRANSPORT=http", () => {
const cfg = loadConfig(env({ MCP_TRANSPORT: "http" }));
assert.equal(cfg.transport, "http");
});
it("parses MCP_TRANSPORT=repl", () => {
const cfg = loadConfig(env({ MCP_TRANSPORT: "repl" }));
assert.equal(cfg.transport, "repl");
});
it("parses MCP_TRANSPORT=stdio", () => {
const cfg = loadConfig(env({ MCP_TRANSPORT: "stdio" }));
assert.equal(cfg.transport, "stdio");
});
it("defaults unknown transport to stdio", () => {
const cfg = loadConfig(env({ MCP_TRANSPORT: "grpc" }));
assert.equal(cfg.transport, "stdio");
});
it("is case-insensitive for transport", () => {
const cfg = loadConfig(env({ MCP_TRANSPORT: "HTTP" }));
assert.equal(cfg.transport, "http");
});
// ── HTTP port/host ──────────────────────────────────────────
it("parses MCP_HTTP_PORT", () => {
const cfg = loadConfig(env({ MCP_HTTP_PORT: "9999" }));
assert.equal(cfg.httpPort, 9999);
});
it("clamps MCP_HTTP_PORT to valid range", () => {
const low = loadConfig(env({ MCP_HTTP_PORT: "0" }));
assert.equal(low.httpPort, 1);
const high = loadConfig(env({ MCP_HTTP_PORT: "99999" }));
assert.equal(high.httpPort, 65535);
});
it("falls back to default on invalid MCP_HTTP_PORT", () => {
const cfg = loadConfig(env({ MCP_HTTP_PORT: "banana" }));
assert.equal(cfg.httpPort, 8819);
});
it("parses MCP_HTTP_HOST", () => {
const cfg = loadConfig(env({ MCP_HTTP_HOST: "0.0.0.0" }));
assert.equal(cfg.httpHost, "0.0.0.0");
});
// ── Boolean parsing ─────────────────────────────────────────
for (const truthy of ["1", "true", "yes", "on", "TRUE", "Yes"]) {
it(`parses allowMutations='${truthy}' as true`, () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_ALLOW_MUTATIONS: truthy }));
assert.equal(cfg.allowMutations, true);
});
}
for (const falsy of ["0", "false", "no", "off", "FALSE", "No"]) {
it(`parses allowMutations='${falsy}' as false`, () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_ALLOW_MUTATIONS: falsy }));
assert.equal(cfg.allowMutations, false);
});
}
it("parses allowDestructive", () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_ALLOW_DESTRUCTIVE: "true" }));
assert.equal(cfg.allowDestructive, true);
});
// ── Timeout / retry parsing ─────────────────────────────────
it("parses timeout with clamping", () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_TIMEOUT_MS: "200" }));
assert.equal(cfg.requestTimeoutMs, 500); // min 500
});
it("parses retry count", () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_RETRY_COUNT: "5" }));
assert.equal(cfg.retryCount, 5);
});
// ── Log level ───────────────────────────────────────────────
it("parses valid log level", () => {
const cfg = loadConfig(env({ MCP_LOG_LEVEL: "debug" }));
assert.equal(cfg.logLevel, "debug");
});
it("defaults invalid log level to info", () => {
const cfg = loadConfig(env({ MCP_LOG_LEVEL: "verbose" }));
assert.equal(cfg.logLevel, "info");
});
// ── Dashboard URL validation ────────────────────────────────
it("rejects non-local dashboard hosts", () => {
assert.throws(
() => loadConfig(env({ MCP_DASHBOARD_BASE_URL: "http://evil.com:4820" })),
/must target a local dashboard host/
);
});
it("rejects non-http protocols", () => {
assert.throws(
() => loadConfig(env({ MCP_DASHBOARD_BASE_URL: "ftp://127.0.0.1:4820" })),
/must use http or https/
);
});
it("rejects invalid URLs", () => {
assert.throws(
() => loadConfig(env({ MCP_DASHBOARD_BASE_URL: "not a url" })),
/Invalid MCP_DASHBOARD_BASE_URL/
);
});
it("accepts localhost", () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_BASE_URL: "http://localhost:4820" }));
assert.equal(cfg.dashboardBaseUrl.hostname, "localhost");
});
it("accepts host.docker.internal", () => {
const cfg = loadConfig(env({ MCP_DASHBOARD_BASE_URL: "http://host.docker.internal:4820" }));
assert.equal(cfg.dashboardBaseUrl.hostname, "host.docker.internal");
});
// ── Custom server name/version ──────────────────────────────
it("parses custom server name and version", () => {
const cfg = loadConfig(
env({
MCP_SERVER_NAME: "my-mcp",
MCP_SERVER_VERSION: "2.0.0",
})
);
assert.equal(cfg.serverName, "my-mcp");
assert.equal(cfg.serverVersion, "2.0.0");
});
});
+117
View File
@@ -0,0 +1,117 @@
/**
* @file banner.test.ts
* @description Unit tests for the banner module, which includes functions for printing the ASCII art banner, server information, ready message, and shutdown message to the console. The tests verify that the banner is printed correctly, that server information includes all expected fields, and that the ready and shutdown messages are displayed as intended. The tests use Node's built-in test framework and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { stripAnsi } from "../src/ui/colors.js";
import { printBanner, printServerInfo, printReady, printShutdown } from "../src/ui/banner.js";
function captureStdout(fn: () => void): string {
const chunks: string[] = [];
const origWrite = process.stdout.write;
process.stdout.write = ((data: string) => {
chunks.push(data);
return true;
}) as typeof process.stdout.write;
try {
fn();
} finally {
process.stdout.write = origWrite;
}
return chunks.join("");
}
describe("banner", () => {
describe("printBanner()", () => {
it("outputs ASCII art banner", () => {
const output = captureStdout(() => printBanner());
const text = stripAnsi(output);
assert.ok(text.includes("$$"), "Banner should contain $$ font characters");
// The banner spells "MCP Tools" in dollar-sign FIGlet font
assert.ok(output.length > 200, "Banner should be substantial");
});
it("outputs multiple lines", () => {
const output = captureStdout(() => printBanner());
const lines = output.split("\n").filter((l) => l.trim().length > 0);
assert.ok(lines.length >= 6, `Expected at least 6 lines, got ${lines.length}`);
});
});
describe("printServerInfo()", () => {
it("displays all provided info fields", () => {
const output = captureStdout(() =>
printServerInfo({
transport: "http",
version: "2.0.0",
dashboard: "http://localhost:4820/",
port: 8819,
mutations: true,
destructive: false,
tools: 25,
})
);
const text = stripAnsi(output);
assert.ok(text.includes("Agent Dashboard MCP Server"));
assert.ok(text.includes("2.0.0"));
assert.ok(text.includes("HTTP"));
assert.ok(text.includes("localhost:4820"));
assert.ok(text.includes("8819"));
assert.ok(text.includes("25"));
assert.ok(text.includes("ENABLED")); // mutations
assert.ok(text.includes("disabled")); // destructive
});
it("omits port when not provided", () => {
const output = captureStdout(() =>
printServerInfo({
transport: "stdio",
version: "1.0.0",
dashboard: "http://127.0.0.1:4820/",
mutations: false,
destructive: false,
tools: 25,
})
);
const text = stripAnsi(output);
assert.ok(!text.includes("HTTP Port"));
});
it("shows dashboard prerequisite hint", () => {
const output = captureStdout(() =>
printServerInfo({
transport: "repl",
version: "1.0.0",
dashboard: "http://127.0.0.1:4820/",
mutations: false,
destructive: false,
tools: 25,
})
);
const text = stripAnsi(output);
assert.ok(text.includes("Dashboard must be running"));
assert.ok(text.includes("npm run dev"));
});
});
describe("printReady()", () => {
it("outputs ready message with transport", () => {
const output = captureStdout(() => printReady("http"));
const text = stripAnsi(output);
assert.ok(text.includes("✔"));
assert.ok(text.includes("Server ready"));
assert.ok(text.includes("http"));
});
});
describe("printShutdown()", () => {
it("outputs shutdown message", () => {
const output = captureStdout(() => printShutdown());
const text = stripAnsi(output);
assert.ok(text.includes("Shutting down"));
});
});
});
+133
View File
@@ -0,0 +1,133 @@
/**
* @file colors.test.ts
* @description Unit tests for the colors module, which provides functions for styling console output with ANSI escape codes. The tests cover the stripAnsi function for removing ANSI codes from strings, the existence and functionality of various style functions (e.g., bold, red, bgBlue), and the composable styles like success, error, warn, info, muted, highlight, label, and accent. The tests also verify that the fg256 and bg256 functions return functions that correctly wrap text with the specified 256-color codes. The tests use Node's built-in test framework and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import * as c from "../src/ui/colors.js";
describe("colors", () => {
describe("stripAnsi", () => {
it("removes ANSI escape codes", () => {
const colored = "\x1b[1m\x1b[32mHello\x1b[39m\x1b[22m";
assert.equal(c.stripAnsi(colored), "Hello");
});
it("returns plain text unchanged", () => {
assert.equal(c.stripAnsi("plain text"), "plain text");
});
it("handles empty string", () => {
assert.equal(c.stripAnsi(""), "");
});
it("handles multiple escape sequences", () => {
const text = "\x1b[31mred\x1b[39m \x1b[34mblue\x1b[39m";
assert.equal(c.stripAnsi(text), "red blue");
});
});
describe("style functions exist and are callable", () => {
const styleFns = [
"bold",
"dim",
"italic",
"underline",
"strikethrough",
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"gray",
"brightRed",
"brightGreen",
"brightYellow",
"brightBlue",
"brightMagenta",
"brightCyan",
"brightWhite",
"bgRed",
"bgGreen",
"bgYellow",
"bgBlue",
"bgMagenta",
"bgCyan",
"bgWhite",
"bgGray",
] as const;
for (const name of styleFns) {
it(`${name}() returns a string`, () => {
const fn = c[name] as (t: string) => string;
assert.equal(typeof fn, "function");
const result = fn("test");
assert.equal(typeof result, "string");
assert.ok(c.stripAnsi(result).includes("test"));
});
}
});
describe("composable styles", () => {
it("success() wraps text", () => {
const r = c.success("OK");
assert.equal(c.stripAnsi(r), "OK");
});
it("error() wraps text", () => {
const r = c.error("FAIL");
assert.equal(c.stripAnsi(r), "FAIL");
});
it("warn() wraps text", () => {
const r = c.warn("CAUTION");
assert.equal(c.stripAnsi(r), "CAUTION");
});
it("info() wraps text", () => {
const r = c.info("INFO");
assert.equal(c.stripAnsi(r), "INFO");
});
it("muted() wraps text", () => {
const r = c.muted("dim");
assert.equal(c.stripAnsi(r), "dim");
});
it("highlight() wraps text", () => {
const r = c.highlight("HL");
assert.equal(c.stripAnsi(r), "HL");
});
it("label() wraps text", () => {
const r = c.label("LBL");
assert.equal(c.stripAnsi(r), "LBL");
});
it("accent() wraps text", () => {
const r = c.accent("ACC");
assert.equal(c.stripAnsi(r), "ACC");
});
});
describe("fg256 and bg256", () => {
it("fg256 returns a function that wraps text", () => {
const fn = c.fg256(196);
assert.equal(typeof fn, "function");
const result = fn("red");
assert.equal(c.stripAnsi(result), "red");
});
it("bg256 returns a function that wraps text", () => {
const fn = c.bg256(46);
assert.equal(typeof fn, "function");
const result = fn("bg");
assert.equal(c.stripAnsi(result), "bg");
});
});
});
+222
View File
@@ -0,0 +1,222 @@
/**
* @file formatter.test.ts
* @description Unit tests for the formatter module, which provides functions to format various UI components such as boxes, dividers, tables, badges, tool results, key-value pairs, section headers, and progress bars. The tests cover rendering of these components with different inputs and verify that the output contains the expected text and formatting. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { stripAnsi } from "../src/ui/colors.js";
import {
box,
divider,
table,
badge,
formatToolResult,
formatToolError,
keyValue,
sectionHeader,
progressBar,
type Column,
} from "../src/ui/formatter.js";
function plain(s: string): string {
return stripAnsi(s);
}
describe("formatter", () => {
describe("box()", () => {
it("renders a box with title and content", () => {
const result = box("Title", "Hello world");
const text = plain(result);
assert.ok(text.includes("Title"));
assert.ok(text.includes("Hello world"));
assert.ok(text.includes("╭"));
assert.ok(text.includes("╰"));
});
it("handles multi-line content", () => {
const result = box("Multi", "line one\nline two\nline three");
const text = plain(result);
assert.ok(text.includes("line one"));
assert.ok(text.includes("line two"));
assert.ok(text.includes("line three"));
});
it("respects custom width", () => {
const result = box("W", "test", 40);
const lines = result.split("\n");
// Bottom border should be exactly width chars (visible)
const bottomVisible = plain(lines[lines.length - 1]);
assert.equal(bottomVisible.length, 40);
});
});
describe("divider()", () => {
it("renders a horizontal line", () => {
const result = divider(30);
const text = plain(result);
assert.equal(text.length, 30);
assert.ok(text.includes("─"));
});
});
describe("table()", () => {
const cols: Column[] = [
{ key: "name", label: "Name", width: 12 },
{ key: "status", label: "Status", width: 10 },
];
it("renders header and rows", () => {
const rows = [
{ name: "Alice", status: "active" },
{ name: "Bob", status: "idle" },
];
const result = table(cols, rows);
const text = plain(result);
assert.ok(text.includes("Name"));
assert.ok(text.includes("Status"));
assert.ok(text.includes("Alice"));
assert.ok(text.includes("Bob"));
assert.ok(text.includes("active"));
assert.ok(text.includes("idle"));
});
it("handles empty rows", () => {
const result = table(cols, []);
const text = plain(result);
assert.ok(text.includes("Name"));
assert.ok(text.includes("Status"));
});
it("auto-sizes columns when width not specified", () => {
const autoCols: Column[] = [
{ key: "x", label: "X" },
{ key: "y", label: "LongerLabel" },
];
const rows = [{ x: "short", y: "val" }];
const result = table(autoCols, rows);
assert.ok(plain(result).includes("X"));
assert.ok(plain(result).includes("LongerLabel"));
});
it("applies column color functions", () => {
const colorCols: Column[] = [
{ key: "name", label: "Name", width: 10, color: (t: string) => `[${t}]` },
];
const rows = [{ name: "test" }];
const result = table(colorCols, rows);
assert.ok(result.includes("[test]"));
});
});
describe("badge()", () => {
it("renders known statuses", () => {
for (const status of ["active", "completed", "error", "abandoned", "idle", "ok", "healthy"]) {
const result = badge(status);
assert.ok(plain(result).includes(status.toUpperCase()));
}
});
it("renders unknown status with muted style", () => {
const result = badge("custom");
assert.ok(plain(result).includes("CUSTOM"));
});
});
describe("formatToolResult()", () => {
it("formats successful result with name and duration", () => {
const result = formatToolResult("my_tool", { ok: true }, 42);
const text = plain(result);
assert.ok(text.includes("✔"));
assert.ok(text.includes("my_tool"));
assert.ok(text.includes("42ms"));
});
it("handles null data", () => {
const result = formatToolResult("null_tool", null, 10);
const text = plain(result);
assert.ok(text.includes("(no data)"));
});
it("handles string data", () => {
const result = formatToolResult("str_tool", "just a string", 5);
const text = plain(result);
assert.ok(text.includes("just a string"));
});
it("truncates very long JSON output", () => {
const bigObj: Record<string, number> = {};
for (let i = 0; i < 100; i++) bigObj[`key_${i}`] = i;
const result = formatToolResult("big_tool", bigObj, 100);
const text = plain(result);
assert.ok(text.includes("more lines"));
});
});
describe("formatToolError()", () => {
it("formats error with name, message, and duration", () => {
const result = formatToolError("bad_tool", "Connection refused", 150);
const text = plain(result);
assert.ok(text.includes("✘"));
assert.ok(text.includes("bad_tool"));
assert.ok(text.includes("150ms"));
assert.ok(text.includes("Connection refused"));
});
});
describe("keyValue()", () => {
it("renders label-value pairs", () => {
const result = keyValue([
["Transport", "HTTP"],
["Port", "8819"],
]);
const text = plain(result);
assert.ok(text.includes("Transport"));
assert.ok(text.includes("HTTP"));
assert.ok(text.includes("Port"));
assert.ok(text.includes("8819"));
});
});
describe("sectionHeader()", () => {
it("renders section title with diamond icon", () => {
const result = sectionHeader("Sessions");
const text = plain(result);
assert.ok(text.includes("◆"));
assert.ok(text.includes("Sessions"));
});
});
describe("progressBar()", () => {
it("renders 0%", () => {
const result = progressBar(0, 100);
const text = plain(result);
assert.ok(text.includes("0%"));
});
it("renders 100%", () => {
const result = progressBar(100, 100);
const text = plain(result);
assert.ok(text.includes("100%"));
});
it("renders 50%", () => {
const result = progressBar(50, 100);
const text = plain(result);
assert.ok(text.includes("50%"));
});
it("clamps over 100%", () => {
const result = progressBar(200, 100);
const text = plain(result);
assert.ok(text.includes("100%"));
});
it("clamps negative to 0%", () => {
const result = progressBar(-5, 100);
const text = plain(result);
assert.ok(text.includes("0%"));
});
});
});
+99
View File
@@ -0,0 +1,99 @@
/**
* @file logger.test.ts
* @description Unit tests for the Logger class, which is responsible for logging messages in JSON format to stderr with different log levels (debug, info, warn, error). The tests cover writing logs to stderr, respecting minimum log levels, omitting meta when empty, and ensuring that each log line is valid JSON. The tests use Node's built-in test runner and assert module for assertions and mocking.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { Logger } from "../src/core/logger.js";
describe("Logger", () => {
it("writes to stderr", () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((data: string) => {
chunks.push(data);
return true;
}) as typeof process.stderr.write;
try {
const logger = new Logger("debug");
logger.info("test message", { key: "value" });
assert.equal(chunks.length, 1);
const parsed = JSON.parse(chunks[0]);
assert.equal(parsed.level, "info");
assert.equal(parsed.message, "test message");
assert.equal(parsed.meta.key, "value");
assert.ok(parsed.timestamp);
} finally {
process.stderr.write = origWrite;
}
});
it("respects minimum log level", () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((data: string) => {
chunks.push(data);
return true;
}) as typeof process.stderr.write;
try {
const logger = new Logger("warn");
logger.debug("should be suppressed");
logger.info("should be suppressed too");
logger.warn("should appear");
logger.error("should also appear");
assert.equal(chunks.length, 2);
assert.ok(JSON.parse(chunks[0]).level === "warn");
assert.ok(JSON.parse(chunks[1]).level === "error");
} finally {
process.stderr.write = origWrite;
}
});
it("omits meta when empty", () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((data: string) => {
chunks.push(data);
return true;
}) as typeof process.stderr.write;
try {
const logger = new Logger("info");
logger.info("no meta");
const parsed = JSON.parse(chunks[0]);
assert.equal(parsed.meta, undefined);
} finally {
process.stderr.write = origWrite;
}
});
it("outputs valid JSON on each line", () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((data: string) => {
chunks.push(data);
return true;
}) as typeof process.stderr.write;
try {
const logger = new Logger("debug");
logger.debug("d");
logger.info("i");
logger.warn("w");
logger.error("e");
for (const chunk of chunks) {
assert.doesNotThrow(() => JSON.parse(chunk), "Each log line must be valid JSON");
}
} finally {
process.stderr.write = origWrite;
}
});
});
+164
View File
@@ -0,0 +1,164 @@
/**
* @file tool-collector.test.ts
* @description Unit tests for the tool-collector module, which is responsible for collecting and registering all tools available in the application. The tests cover the presence of expected tools, their properties, uniqueness of tool names, adherence to naming conventions, inclusion of tools from all domains, and proper handling of mutation and destructive tools based on configuration. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { collectAllTools } from "../src/transports/tool-collector.js";
import { Logger } from "../src/core/logger.js";
import type { AppConfig } from "../src/config/app-config.js";
import { DashboardApiClient } from "../src/clients/dashboard-api-client.js";
function fakeConfig(overrides: Partial<AppConfig> = {}): AppConfig {
return {
serverName: "test",
serverVersion: "1.0.0",
dashboardBaseUrl: new URL("http://127.0.0.1:4820"),
requestTimeoutMs: 10_000,
retryCount: 0,
retryBackoffMs: 250,
allowMutations: false,
allowDestructive: false,
logLevel: "error",
transport: "stdio",
httpPort: 8819,
httpHost: "127.0.0.1",
...overrides,
};
}
describe("collectAllTools", () => {
const config = fakeConfig();
const logger = new Logger("error");
const api = new DashboardApiClient(config, logger);
it("registers all expected tools", () => {
const tools = collectAllTools(config, api, logger);
assert.ok(tools.length >= 25, `Expected at least 25 tools, got ${tools.length}`);
});
it("every tool has name, description, and handler", () => {
const tools = collectAllTools(config, api, logger);
for (const tool of tools) {
assert.ok(tool.name, `Tool missing name`);
assert.ok(tool.description, `Tool ${tool.name} missing description`);
assert.equal(typeof tool.handler, "function", `Tool ${tool.name} handler is not a function`);
}
});
it("tool names are unique", () => {
const tools = collectAllTools(config, api, logger);
const names = tools.map((t) => t.name);
const unique = new Set(names);
assert.equal(names.length, unique.size, "Duplicate tool names found");
});
it("tool names follow naming convention", () => {
const tools = collectAllTools(config, api, logger);
for (const tool of tools) {
assert.ok(
tool.name.startsWith("dashboard_"),
`Tool ${tool.name} should start with 'dashboard_'`
);
assert.ok(/^[a-z_]+$/.test(tool.name), `Tool ${tool.name} should be lowercase snake_case`);
}
});
it("includes tools from all domains", () => {
const tools = collectAllTools(config, api, logger);
const names = new Set(tools.map((t) => t.name));
// Observability
assert.ok(names.has("dashboard_health_check"));
assert.ok(names.has("dashboard_get_stats"));
assert.ok(names.has("dashboard_get_analytics"));
assert.ok(names.has("dashboard_get_system_info"));
assert.ok(names.has("dashboard_export_data"));
assert.ok(names.has("dashboard_get_operational_snapshot"));
// Sessions
assert.ok(names.has("dashboard_list_sessions"));
assert.ok(names.has("dashboard_get_session"));
assert.ok(names.has("dashboard_create_session"));
assert.ok(names.has("dashboard_update_session"));
// Agents
assert.ok(names.has("dashboard_list_agents"));
assert.ok(names.has("dashboard_get_agent"));
assert.ok(names.has("dashboard_create_agent"));
assert.ok(names.has("dashboard_update_agent"));
// Events
assert.ok(names.has("dashboard_list_events"));
assert.ok(names.has("dashboard_ingest_hook_event"));
// Pricing
assert.ok(names.has("dashboard_get_pricing_rules"));
assert.ok(names.has("dashboard_get_total_cost"));
assert.ok(names.has("dashboard_get_session_cost"));
assert.ok(names.has("dashboard_upsert_pricing_rule"));
assert.ok(names.has("dashboard_delete_pricing_rule"));
assert.ok(names.has("dashboard_reset_pricing_defaults"));
// Maintenance
assert.ok(names.has("dashboard_cleanup_data"));
assert.ok(names.has("dashboard_reimport_history"));
assert.ok(names.has("dashboard_reinstall_hooks"));
assert.ok(names.has("dashboard_clear_all_data"));
// Remote Data Sources
assert.ok(names.has("dashboard_list_remote_sources"));
assert.ok(names.has("dashboard_sync_remote_source"));
assert.ok(names.has("dashboard_sync_all_remote_sources"));
});
it("mutation tools throw when mutations disabled", async () => {
const mutConfig = fakeConfig({ allowMutations: false });
const tools = collectAllTools(mutConfig, api, logger);
const createSession = tools.find((t) => t.name === "dashboard_create_session");
assert.ok(createSession);
await assert.rejects(
() => createSession.handler({ id: "x", name: "y" }),
/Mutating tools are disabled/
);
});
it("destructive tool throws when destructive disabled", async () => {
const destConfig = fakeConfig({ allowMutations: true, allowDestructive: false });
const tools = collectAllTools(destConfig, api, logger);
const clearAll = tools.find((t) => t.name === "dashboard_clear_all_data");
assert.ok(clearAll);
await assert.rejects(
() => clearAll.handler({ confirmation_token: "CLEAR_ALL_DATA" }),
/Destructive tools are disabled/
);
});
it("destructive tool throws on wrong token", async () => {
const destConfig = fakeConfig({ allowMutations: true, allowDestructive: true });
const tools = collectAllTools(destConfig, api, logger);
const clearAll = tools.find((t) => t.name === "dashboard_clear_all_data");
assert.ok(clearAll);
await assert.rejects(
() => clearAll.handler({ confirmation_token: "WRONG" }),
/Invalid confirmation_token/
);
});
it("cleanup tool requires at least one parameter", async () => {
const mutConfig = fakeConfig({ allowMutations: true });
const tools = collectAllTools(mutConfig, api, logger);
const cleanup = tools.find((t) => t.name === "dashboard_cleanup_data");
assert.ok(cleanup);
await assert.rejects(
() => cleanup.handler({}),
/At least one of abandon_hours or purge_days is required/
);
});
});
+85
View File
@@ -0,0 +1,85 @@
/**
* @file tool-guards.test.ts
* @description Unit tests for the tool guard functions, which are responsible for enforcing configuration policies related to mutating and destructive tools. The tests cover scenarios where mutations and destructive actions are enabled or disabled in the configuration, as well as validating the confirmation token for destructive actions. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { assertMutationsEnabled, assertDestructiveEnabled } from "../src/policy/tool-guards.js";
import type { AppConfig } from "../src/config/app-config.js";
function fakeConfig(overrides: Partial<AppConfig> = {}): AppConfig {
return {
serverName: "test",
serverVersion: "1.0.0",
dashboardBaseUrl: new URL("http://127.0.0.1:4820"),
requestTimeoutMs: 10_000,
retryCount: 2,
retryBackoffMs: 250,
allowMutations: false,
allowDestructive: false,
logLevel: "error",
transport: "stdio",
httpPort: 8819,
httpHost: "127.0.0.1",
...overrides,
};
}
describe("assertMutationsEnabled", () => {
it("throws when mutations disabled", () => {
assert.throws(
() => assertMutationsEnabled(fakeConfig({ allowMutations: false })),
/Mutating tools are disabled/
);
});
it("does not throw when mutations enabled", () => {
assert.doesNotThrow(() => assertMutationsEnabled(fakeConfig({ allowMutations: true })));
});
});
describe("assertDestructiveEnabled", () => {
it("throws when mutations disabled (even if destructive enabled)", () => {
assert.throws(
() =>
assertDestructiveEnabled(
fakeConfig({ allowMutations: false, allowDestructive: true }),
"CLEAR_ALL_DATA"
),
/Mutating tools are disabled/
);
});
it("throws when destructive disabled", () => {
assert.throws(
() =>
assertDestructiveEnabled(
fakeConfig({ allowMutations: true, allowDestructive: false }),
"CLEAR_ALL_DATA"
),
/Destructive tools are disabled/
);
});
it("throws on wrong confirmation token", () => {
assert.throws(
() =>
assertDestructiveEnabled(
fakeConfig({ allowMutations: true, allowDestructive: true }),
"WRONG_TOKEN"
),
/Invalid confirmation_token/
);
});
it("passes with correct config and token", () => {
assert.doesNotThrow(() =>
assertDestructiveEnabled(
fakeConfig({ allowMutations: true, allowDestructive: true }),
"CLEAR_ALL_DATA"
)
);
});
});
+69
View File
@@ -0,0 +1,69 @@
/**
* @file tool-registry.test.ts
* @description Unit tests for the tool registry functions, which are responsible for registering tools in the MCP server. The tests cover the behavior of the createCollectorRegistrar and createDualRegistrar functions, ensuring that they correctly collect tool entries and integrate with the MCP server's registration mechanism. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
createCollectorRegistrar,
createDualRegistrar,
type ToolEntry,
} from "../src/core/tool-registry.js";
import { z } from "zod";
describe("createCollectorRegistrar", () => {
it("collects tool entries into the provided array", () => {
const collector: ToolEntry[] = [];
const register = createCollectorRegistrar(collector);
const handler = async () => ({ ok: true });
register("my_tool", "A test tool", { name: z.string() }, handler);
assert.equal(collector.length, 1);
assert.equal(collector[0].name, "my_tool");
assert.equal(collector[0].description, "A test tool");
assert.equal(collector[0].handler, handler);
});
it("collects multiple tools in order", () => {
const collector: ToolEntry[] = [];
const register = createCollectorRegistrar(collector);
register("tool_a", "First tool", {}, async () => "a");
register("tool_b", "Second tool", {}, async () => "b");
register("tool_c", "Third tool", {}, async () => "c");
assert.equal(collector.length, 3);
assert.deepEqual(
collector.map((t) => t.name),
["tool_a", "tool_b", "tool_c"]
);
});
it("handler is invocable and returns expected result", async () => {
const collector: ToolEntry[] = [];
const register = createCollectorRegistrar(collector);
register("echo_tool", "Echoes input", {}, async (args) => ({
echo: args.message,
}));
const result = await collector[0].handler({ message: "hello" });
assert.deepEqual(result, { echo: "hello" });
});
});
describe("createDualRegistrar", () => {
it("pushes to collector array", () => {
// We can't easily create a real McpServer in tests, so we test the collector
// behavior by verifying createCollectorRegistrar is the building block
const collector: ToolEntry[] = [];
const register = createCollectorRegistrar(collector);
register("dual_test", "Dual test tool", { id: z.string() }, async () => null);
assert.equal(collector.length, 1);
assert.equal(collector[0].name, "dual_test");
});
});
+69
View File
@@ -0,0 +1,69 @@
/**
* @file tool-result.test.ts
* @description Unit tests for the tool result formatting functions, which are responsible for converting tool outputs and errors into a standardized format that can be rendered in the MCP dashboard. The tests cover the behavior of the jsonResult function, ensuring that it correctly wraps payloads as text content with appropriate titles, and the errorResult function, verifying that it handles different types of errors (ApiError, generic Error, and non-Error values) and formats them into a consistent error result structure. The tests use Node's built-in test runner and assert module for assertions.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { jsonResult, errorResult } from "../src/core/tool-result.js";
import { ApiError } from "../src/clients/dashboard-api-client.js";
describe("jsonResult", () => {
it("wraps payload as text content with title", () => {
const result = jsonResult("my_tool", { status: "ok" });
assert.equal(result.content.length, 1);
assert.equal(result.content[0].type, "text");
assert.ok(result.content[0].type === "text" && result.content[0].text.includes("my_tool"));
assert.ok(
result.content[0].type === "text" && result.content[0].text.includes('"status": "ok"')
);
});
it("handles null payload", () => {
const result = jsonResult("null_tool", null);
assert.equal(result.content.length, 1);
assert.ok(result.content[0].type === "text" && result.content[0].text.includes("null"));
});
it("handles array payload", () => {
const result = jsonResult("arr_tool", [1, 2, 3]);
assert.equal(result.content.length, 1);
assert.ok(result.content[0].type === "text" && result.content[0].text.includes("["));
});
});
describe("errorResult", () => {
it("handles ApiError with status and details", () => {
const apiErr = new ApiError("Not found", {
status: 404,
code: "NOT_FOUND",
details: { path: "/api/sessions/x" },
});
const result = errorResult(apiErr);
assert.equal(result.isError, true);
assert.equal(result.content.length, 1);
const text = result.content[0].type === "text" ? result.content[0].text : "";
const parsed = JSON.parse(text);
assert.equal(parsed.error, "Not found");
assert.equal(parsed.status, 404);
assert.equal(parsed.code, "NOT_FOUND");
});
it("handles generic Error", () => {
const result = errorResult(new Error("Something broke"));
assert.equal(result.isError, true);
const text = result.content[0].type === "text" ? result.content[0].text : "";
const parsed = JSON.parse(text);
assert.equal(parsed.error, "Something broke");
assert.equal(parsed.code, "INTERNAL_ERROR");
});
it("handles non-Error thrown value", () => {
const result = errorResult("string error");
assert.equal(result.isError, true);
const text = result.content[0].type === "text" ? result.content[0].text : "";
const parsed = JSON.parse(text);
assert.equal(parsed.error, "Unknown error");
});
});