Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/horst/__tests__/harness.test.ts

Raw
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os, { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
import {
	getHostSection,
	type HostSnapshot,
	renderHostSection,
} from "../host.ts";
import {
	EMPTY_HORST_SETTINGS,
	parseHorst,
	resolveHorstTools,
} from "../settings.ts";

const horstPath = () => path.resolve(import.meta.dirname, "../index.ts");

describe("parseHorst", () => {
	it("keeps well-formed tool deltas and lowercases keys", () => {
		const settings = parseHorst({
			preferredShell: " nu ",
			os: { WIN32: { tools: { add: ["powershell"], remove: ["bash"] } } },
			hosts: { WinBox: { tools: { remove: ["write"] } } },
		});
		expect(settings).toEqual({
			os: { win32: { add: ["powershell"], remove: ["bash"] } },
			hosts: { winbox: { remove: ["write"] } },
			preferredShell: "nu",
		});
		expect(parseHorst({})).toEqual(EMPTY_HORST_SETTINGS);
		expect(parseHorst({ os: { lnx: { tools: { add: [] } } } })).toEqual(
			EMPTY_HORST_SETTINGS,
		);
	});

	it.each([
		[undefined, "expected an object"],
		[[], "expected an object"],
		[{ preferredShell: "  " }, "preferredShell must be a non-empty string"],
		[{ preferredShell: 42 }, "preferredShell must be a non-empty string"],
		[{ hosts: "nope" }, "hosts must be an object"],
		[{ os: { lnx: { flat: {} } } }, "os.lnx.tools must be an object"],
		[
			{ os: { lnx: { tools: { add: [42] } } } },
			"os.lnx.tools.add must be an array of non-empty strings",
		],
		[
			{ hosts: { a: { tools: { remove: "read" } } } },
			"hosts.a.tools.remove must be an array of non-empty strings",
		],
	])("rejects %j", (raw, message) => {
		expect(() => parseHorst(raw)).toThrow(message);
	});
});

describe("resolveHorstTools", () => {
	it("maps platform names to os keys", () => {
		expect(
			resolveHorstTools(
				{ os: { lnx: { add: ["grep"] } }, hosts: {} },
				"linux",
				"x",
			),
		).toEqual({ add: ["grep"] });
		expect(
			resolveHorstTools(
				{ os: { mac: { add: ["grep"] } }, hosts: {} },
				"darwin",
				"x",
			),
		).toEqual({ add: ["grep"] });
		expect(
			resolveHorstTools(
				{ os: { win32: { add: ["powershell"] } }, hosts: {} },
				"win32",
				"x",
			),
		).toEqual({ add: ["powershell"] });
	});

	it("matches hostnames case-insensitively and wins per field over os", () => {
		const settings = {
			os: { win32: { add: ["powershell"], remove: ["bash"] } },
			hosts: { winbox: { remove: ["write"] } },
		};
		expect(resolveHorstTools(settings, "win32", "WinBox")).toEqual({
			add: ["powershell"],
			remove: ["write"],
		});
	});

	it("returns undefined without a match", () => {
		expect(
			resolveHorstTools(EMPTY_HORST_SETTINGS, "linux", "x"),
		).toBeUndefined();
	});
});

describe("renderHostSection", () => {
	it("renders host facts without a misleading environment shell or editor", () => {
		const host: HostSnapshot = {
			hostname: "winbox",
			osLabel: "Fedora Linux 42",
			kernel: "6.14.0",
			arch: "x64",
			cpuModel: "AMD Ryzen 9",
			cpuCount: 24,
			memGiB: 32,
		};
		const section = renderHostSection(host);
		expect(section).toContain("hostname: winbox");
		expect(section).toContain("os: Fedora Linux 42");
		expect(section).toContain("cpu: AMD Ryzen 9 × 24");
		expect(section).toContain("mem: 32 GiB");
		expect(section).not.toContain("Stable host environment facts:");
		expect(section).not.toContain("shell:");
		expect(section).not.toContain("editor:");
		expect(
			renderHostSection({ ...host, loginShell: "/bin/bash" }, "nu"),
		).toContain("login shell: /bin/bash\npreferred shell: nu");
	});

	it("reads account login shell independently of inherited SHELL", async () => {
		vi.resetModules();
		const userInfo = vi.spyOn(os, "userInfo").mockReturnValue({
			...os.userInfo(),
			shell: "/bin/zsh",
		});
		try {
			const { getHostSection: freshSection } = await import("../host.ts");
			expect(await freshSection()).toContain("login shell: /bin/zsh");
			expect(await freshSection()).not.toContain("preferred shell:");
		} finally {
			userInfo.mockRestore();
		}
	});

	it("omits unavailable account shell", async () => {
		for (const shell of [null, "", "throws"] as const) {
			vi.resetModules();
			const userInfo = vi.spyOn(os, "userInfo");
			if (shell === "throws")
				userInfo.mockImplementation(() => {
					throw new Error("unavailable");
				});
			else userInfo.mockReturnValue({ ...os.userInfo(), shell });
			try {
				const { getHostSection: freshSection } = await import("../host.ts");
				expect(await freshSection()).not.toContain("login shell:");
			} finally {
				userInfo.mockRestore();
			}
		}
	});
});

describe("horst pi-test-harness", () => {
	let t: TestSession | undefined;

	afterEach(() => {
		t?.dispose();
		t = undefined;
	});

	it("loads in real Pi extension runtime", async () => {
		t = await createTestSession({ extensions: [horstPath()] });
		expect(
			t.session.extensionRunner.extensions.map((extension) => extension.path),
		).toContain(horstPath());
	});

	it("applies the host tool delta from project settings", async () => {
		const root = mkdtempSync(path.join(tmpdir(), "horst-delta-"));
		try {
			mkdirSync(path.join(root, ".pi"), { recursive: true });
			writeFileSync(
				path.join(root, ".pi", "settings.json"),
				JSON.stringify({
					horst: {
						hosts: {
							[os.hostname().toLowerCase()]: {
								tools: { add: ["grep", "find"], remove: ["write"] },
							},
						},
					},
				}),
			);
			t = await createTestSession({ cwd: root, extensions: [horstPath()] });
			const active = t.session.getActiveToolNames();
			expect(active).toContain("grep");
			expect(active).toContain("find");
			expect(active).not.toContain("write");
		} finally {
			t?.dispose();
			t = undefined;
			rmSync(root, { recursive: true, force: true });
		}
	});

	it("injects the stable host section each turn", async () => {
		t = await createTestSession({ extensions: [horstPath()] });
		const runner = t.session.extensionRunner;
		const first = await runner.emitBeforeAgentStart("prompt", undefined, {
			cwd: t.cwd,
		});
		const second = await runner.emitBeforeAgentStart("prompt", undefined, {
			cwd: t.cwd,
		});
		const { horst } = first.systemPromptOptions.sections;
		expect(horst).toBe(second.systemPromptOptions.sections.horst);
		expect(horst).toContain(`hostname: ${os.hostname()}`);
		expect(getHostSection()).resolves.toBe(horst);
	});

	it("injects project shell preference even without a tool delta", async () => {
		const root = mkdtempSync(path.join(tmpdir(), "horst-shell-"));
		try {
			mkdirSync(path.join(root, ".pi"), { recursive: true });
			writeFileSync(
				path.join(root, ".pi", "settings.json"),
				JSON.stringify({ horst: { preferredShell: " fish " } }),
			);
			t = await createTestSession({ cwd: root, extensions: [horstPath()] });
			const runner = t.session.extensionRunner;
			const first = await runner.emitBeforeAgentStart("prompt", undefined, {
				cwd: t.cwd,
			});
			const second = await runner.emitBeforeAgentStart("prompt", undefined, {
				cwd: t.cwd,
			});
			expect(first.systemPromptOptions.sections.horst).toContain(
				"preferred shell: fish",
			);
			expect(second.systemPromptOptions.sections.horst).toBe(
				first.systemPromptOptions.sections.horst,
			);
		} finally {
			t?.dispose();
			t = undefined;
			rmSync(root, { recursive: true, force: true });
		}
	});
});