import { test, expect, vi } from "vitest"; import { EventEmitter } from "node:events"; // Mock node:child_process so the PRODUCTION spawnFn (no injected spawnFn) is exercised // without launching a real pi. Per-file mock — kept out of pi-process-manager.test.ts, // which deliberately spawns the real fake_pi. vi.mock("node:child_process", () => ({ spawn: vi.fn(() => { const ch: any = new EventEmitter(); ch.stdout = new EventEmitter(); ch.stderr = new EventEmitter(); ch.stderr.resume = () => {}; ch.stdin = { write: () => true }; ch.kill = () => {}; return ch; }), })); import { spawn as nodeSpawn } from "node:child_process"; import { PiProcessManager } from "../src/pi/pi-process-manager.js"; import { loadConfig } from "../src/config.js"; test("production spawnFn launches `pi --mode rpc` with no --approve (pi 0.73 dropped it)", async () => { (nodeSpawn as any).mockClear(); // loadConfig({}) leaves provider/model/thinking unset → spawnFor skips the rpc // handshake and just spawns + sends the prompt, so the mocked child never hangs. const cfg = loadConfig({}); const mgr = new PiProcessManager(cfg); // NO injected spawnFn → uses the production spawnFn await mgr.spawnFor("s1", {}); const [bin, args] = (nodeSpawn as any).mock.calls[0]; expect(bin).toBe(cfg.piBin); expect(args).toEqual(["--mode", "rpc"]); expect(args).not.toContain("--approve"); mgr.teardown("s1"); }); test("Pi child replaces stale principal env and omits absent display names", async () => { const saved = Object.fromEntries([ "THT_PRINCIPAL_ISSUER", "THT_PRINCIPAL_SUBJECT", "THT_PRINCIPAL_DISPLAY_NAME", "THT_PRINCIPAL_IS_ADMIN", "THT_PRINCIPAL_PERMISSIONS", ].map((key) => [key, process.env[key]])); Object.assign(process.env, { THT_PRINCIPAL_ISSUER: "stale", THT_PRINCIPAL_SUBJECT: "stale", THT_PRINCIPAL_DISPLAY_NAME: "stale", THT_PRINCIPAL_IS_ADMIN: "true", THT_PRINCIPAL_PERMISSIONS: "pi.manage", }); try { (nodeSpawn as any).mockClear(); const mgr = new PiProcessManager(loadConfig({})); await mgr.spawnFor("s-principal", { principal: { issuer: "portal", subject: "42", roles: ["user"], permissions: ["session.use"], isAdmin: false }, }); const env = (nodeSpawn as any).mock.calls[0][2].env; expect(env).toMatchObject({ THT_PRINCIPAL_ISSUER: "portal", THT_PRINCIPAL_SUBJECT: "42", THT_PRINCIPAL_IS_ADMIN: "false", THT_PRINCIPAL_PERMISSIONS: "session.use", }); expect(env).not.toHaveProperty("THT_PRINCIPAL_DISPLAY_NAME"); mgr.teardown("s-principal"); } finally { for (const [key, value] of Object.entries(saved)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } } });