import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createTestSession, type TestSession } from "../../../test/harness"; const extensionPath = path.resolve(import.meta.dirname, "../index.ts"); type Case = { user?: unknown; project?: unknown; env?: string; untrusted?: boolean; }; describe("nu-bang.command resolution", () => { let t: TestSession | undefined; let root: string | undefined; afterEach(async () => { await t?.session.extensionRunner.emit({ type: "session_shutdown", reason: "quit", }); t?.dispose(); t = undefined; if (root) rmSync(root, { recursive: true, force: true }); root = undefined; }); /** Start a session, run `print real-nu` as bang input, and report which executable ran it. */ async function run(input: Case) { const dir = mkdtempSync(path.join(tmpdir(), "pi-ext-nu-bang-settings-")); root = dir; // Fake executables print their own name instead of running Nushell. const fake = (name: string) => { const file = path.join(dir, name); writeFileSync(file, '#!/bin/sh\necho "fake:${0##*/}"\n'); chmodSync(file, 0o755); return file; }; const resolve = (value: unknown) => typeof value === "string" && value.startsWith("fake-") ? fake(value) : value; const write = (settingsDir: string, value: unknown) => { mkdirSync(settingsDir, { recursive: true }); writeFileSync( path.join(settingsDir, "settings.json"), JSON.stringify({ "nu-bang": { command: resolve(value) } }), ); }; if ("user" in input) write(path.join(dir, ".test-home/.pi/agent"), input.user); if ("project" in input) write(path.join(dir, ".pi"), input.project); const env = input.env === undefined ? {} : { PI_NU_BANG_COMMAND: String(resolve(input.env)) }; t = await createTestSession({ cwd: dir, env, extensions: [extensionPath] }); if (input.untrusted) t.session.settingsManager.setProjectTrusted(false); const before = t.events.uiCallsFor("notify").length; await t.session.extensionRunner.emit({ type: "session_start", reason: "startup", }); const warnings = t.events .uiCallsFor("notify") .slice(before) .filter((call) => call.args[1] === "warning") .map((call) => call.args[0]); const result = await t.session.extensionRunner.emitUserBash({ type: "user_bash", command: "print real-nu", excludeFromContext: false, cwd: dir, }); let output = ""; await result?.operations?.exec("print real-nu", dir, { onData: (chunk) => { output += chunk.toString("utf8"); }, }); return { ran: output.trim(), warnings }; } it.each([ ["default nu", {}, "real-nu"], ["user path", { user: "fake-user" }, "fake:fake-user"], [ "env over settings", { user: "fake-user", env: "fake-env" }, "fake:fake-env", ], [ "trusted project over user", { user: "fake-user", project: "fake-project" }, "fake:fake-project", ], [ "untrusted project ignored", { user: "fake-user", project: "fake-project", untrusted: true }, "fake:fake-user", ], ] satisfies [string, Case, string][])( "%s", async (_name, input, expected) => { const { ran, warnings } = await run(input); expect(ran).toBe(expected); expect(warnings).toEqual([]); }, ); it.each([ ["numeric user", { user: 5 }, "user settings"], ["empty project", { user: "fake-user", project: "" }, "project settings"], [ "blank env", { user: "fake-user", env: " " }, "environment variable PI_NU_BANG_COMMAND", ], ] satisfies [string, Case, string][])( "rejects %s without falling through", async (_name, input, source) => { const { ran, warnings } = await run(input); expect(ran).toBe("real-nu"); expect(warnings).toEqual([ `Invalid nu-bang.command from ${source}; using nu`, ]); }, ); });