/** * @file Tests for server/lib/shell-path.js — login-shell PATH hydration with a * stubbed spawner (noisy rc output, errors, opt-out, dedupe) plus one real * `/bin/sh` run to prove the argv works end to end, and the UTF-8 locale * default that keeps tmux from printing `_` for every non-ASCII character. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, beforeEach, afterEach } = require("node:test"); const assert = require("node:assert/strict"); const { hydrateShellPath, shellPath, ensureUtf8Locale } = require("../lib/shell-path"); const M = "__AGENTHUB_PATH__"; const stub = (stdout, err = null) => (_cmd, _args, _opts, cb) => { setImmediate(() => cb(err, stdout)); return { stdin: { end() {} } }; }; describe("hydrateShellPath", () => { let saved; beforeEach(() => { saved = { PATH: process.env.PATH, OPT: process.env.AGENTHUB_SHELL_PATH }; process.env.PATH = "/usr/bin:/bin"; delete process.env.AGENTHUB_SHELL_PATH; }); afterEach(() => { process.env.PATH = saved.PATH; if (saved.OPT === undefined) delete process.env.AGENTHUB_SHELL_PATH; else process.env.AGENTHUB_SHELL_PATH = saved.OPT; }); it("appends new segments from between the delimiters, ignoring rc noise", async () => { const ok = await hydrateShellPath({ spawn: stub(`welcome!\n${M}/home/u/.local/bin:/usr/bin:/opt/x${M}bye`), }); assert.equal(ok, true); assert.equal(process.env.PATH, "/usr/bin:/bin:/home/u/.local/bin:/opt/x"); }); it("remembers the shell's own PATH in the shell's order", async () => { await hydrateShellPath({ spawn: stub(`${M}/home/u/.nvm/bin:/usr/bin${M}`) }); assert.equal(shellPath(), "/home/u/.nvm/bin:/usr/bin"); }); it("keeps PATH on error or missing delimiters", async () => { assert.equal(await hydrateShellPath({ spawn: stub("", new Error("timeout")) }), false); assert.equal(await hydrateShellPath({ spawn: stub("no markers") }), false); assert.equal( await hydrateShellPath({ spawn: () => { throw new Error("ENOENT"); }, }), false ); assert.equal(process.env.PATH, "/usr/bin:/bin"); }); it("does nothing with AGENTHUB_SHELL_PATH=0", async () => { process.env.AGENTHUB_SHELL_PATH = "0"; assert.equal(await hydrateShellPath({ spawn: stub(`${M}/x${M}`) }), false); assert.equal(process.env.PATH, "/usr/bin:/bin"); }); it("reads a real shell's PATH", async () => { const ok = await hydrateShellPath({ shell: "/bin/sh" }); assert.equal(ok, true); assert.ok(process.env.PATH.startsWith("/usr/bin:/bin")); }); }); describe("ensureUtf8Locale", () => { it("sets LC_CTYPE when no locale is set (a macOS LaunchAgent)", () => { const env = {}; ensureUtf8Locale(env, "darwin"); assert.equal(env.LC_CTYPE, "en_US.UTF-8"); const linux = { LANG: "C" }; ensureUtf8Locale(linux, "linux"); assert.equal(linux.LC_CTYPE, "C.UTF-8"); }); it("leaves a UTF-8 locale, an explicit LC_ALL and Windows alone", () => { for (const [env, platform] of [ [{ LANG: "vi_VN.UTF-8" }, "darwin"], [{ LC_CTYPE: "en_US.utf8" }, "linux"], [{ LC_ALL: "C" }, "darwin"], [{}, "win32"], ]) { const before = { ...env }; ensureUtf8Locale(env, platform); assert.deepEqual(env, before); } }); });