943ba03b76
- self-restart: a server in our unit's cgroup (/agenthub.service, system or user) restarts via `systemctl restart --no-block` (sudo -n for the system unit), our launchd job by exiting into KeepAlive. The detached restart-helper replacement held the port while Restart=always started the unit every 3 s (v2.7.2 on the S3 box: 240+ restarts, MainPID 0). - run-as.mayHaveTmux: only ENOENT means "no tmux". A dev server in server mode without CAP_DAC_READ_SEARCH got EACCES on a member's 0700 .agenthub/tmux, dropped the member from the list and ended their runs within seconds — the real cause of `401 not a live pane's token`. - tmux: a member's list is re-asked every 5 s, not 1 s (one sudo + three auth.log lines each). - POST /api/lanes/:id/start: 409 ENOCWD for a folder that no longer exists (the pane started in the account's home otherwise). - Settings: Remote sources only for staff (a developer got a 403). - scripts/test-server.js drops every inherited AGENTHUB_* (pane env). Tests: self-restart.test.js, server-s3-panes (mayHaveTmux), lanes-api (ENOCWD); touched suites 771 + client pages 191, tsc -b.
94 lines
4.1 KiB
JavaScript
94 lines
4.1 KiB
JavaScript
#!/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-<id>` 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. Every
|
|
* inherited `AGENTHUB_*` variable is dropped as well: a dashboard pane carries
|
|
* `AGENTHUB_MODE=server` and friends, which turned the suites login-gated.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
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-"));
|
|
// A dashboard pane's own AGENTHUB_* (AGENTHUB_MODE=server, AGENTHUB_USERS_ROOT,
|
|
// its pane token…) would turn the suites into a login-gated server: hundreds
|
|
// of 401s and EFOLDERs that have nothing to do with the code under test.
|
|
const inherited = Object.fromEntries(
|
|
Object.entries(process.env).filter(([k]) => !k.startsWith("AGENTHUB_"))
|
|
);
|
|
const env = {
|
|
...inherited,
|
|
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"),
|
|
// Never this machine's server-mode S3 install: its system unit would become
|
|
// "the" service manager and its server.conf would send user changes to sudo.
|
|
AGENTHUB_SYSTEM_UNIT_DIR: path.join(tmuxDir, "system-units"),
|
|
AGENTHUB_SERVER_CONF: path.join(tmuxDir, "server.conf"),
|
|
AGENTHUB_PASSWD: path.join(tmuxDir, "passwd"),
|
|
};
|
|
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);
|