#!/usr/bin/env node /** * @file test-server.js * @description Runs the server test suite against a PRIVATE tmux server. * Several suites start real `agenthub-lane-` tmux sessions and then kill * or reset them, with lane ids from their own throwaway database — on the * user's default tmux server those names collide with the dashboard's real * lane panes, and a test run kills whichever live session shares an id * (including the pane the test was launched from). Dropping `TMUX` and * pointing `TMUX_TMPDIR` at a fresh directory gives every tmux call — the * app's own and the tests' direct `execFileSync("tmux", …)` — its own socket. * It also points `DASHBOARD_PIPELINES_DIR` at `server/__tests__/fixtures/ * pipelines/`: AgentHub ships no pipeline template any more, and the two old * ones kept there are what the dormant stage/detection code is tested against. * `AGENTHUB_SETTINGS_FILE` goes to the same temp dir, so a suite that saves a * setting never writes the developer's real `~/.config/agenthub/settings.env`, * and `AGENTHUB_PRICING_SYNC=0` keeps every suite off the network. The * `GIT_DIR`-family variables a git hook exports are dropped too, so a test's * own `git init`/`commit` can never land in the real repository. * @author Nguyễn Ngọc Trí Vĩ */ const { spawnSync } = require("node:child_process"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const root = path.join(__dirname, ".."); const dir = path.join(root, "server", "__tests__"); const files = fs .readdirSync(dir) .filter((f) => f.endsWith(".test.js")) .map((f) => path.join(dir, f)); const args = process.argv.slice(2); const tmuxDir = fs.mkdtempSync(path.join(os.tmpdir(), "agenthub-test-tmux-")); const env = { ...process.env, TMUX_TMPDIR: tmuxDir, // Never the developer's real ~/.config/agenthub/settings.env: it can pin // DASHBOARD_DATA_DIR at their live database. AGENTHUB_SETTINGS_FILE: path.join(tmuxDir, "settings.env"), // No suite may fetch LiteLLM's price list (reset-pricing syncs at once). AGENTHUB_PRICING_SYNC: "0", // Nor run the developer's real agy (it may update itself) or send their // real Cursor, Grok, Kimi or OpenCode login for the quota. AGENTHUB_AGY_USAGE: "0", XDG_CONFIG_HOME: tmuxDir, XDG_DATA_HOME: tmuxDir, GROK_HOME: tmuxDir, KIMI_CODE_HOME: tmuxDir, DASHBOARD_PIPELINES_DIR: path.join(dir, "fixtures", "pipelines"), }; delete env.TMUX; delete env.TMUX_PANE; // Git exports these to a hook it runs from a linked worktree (the pre-commit // hook runs this suite): inherited, every `git init`/`commit` a test makes in // its temp folder went to the REAL repository instead — commits on the // worktree's HEAD and `core.bare = true` in the shared config. for (const k of [ "GIT_DIR", "GIT_INDEX_FILE", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_PREFIX", "GIT_OBJECT_DIRECTORY", ]) { delete env[k]; } const result = spawnSync(process.execPath, ["--test", ...args, ...(args.length ? [] : files)], { stdio: "inherit", env, cwd: root, }); // Whatever a test leaked dies with the private server, never a real pane. spawnSync("tmux", ["kill-server"], { env, stdio: "ignore" }); fs.rmSync(tmuxDir, { recursive: true, force: true }); process.exit(result.status ?? 1);