Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/horst/__tests__/settings.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 } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";

const extensionPath = path.resolve(import.meta.dirname, "../index.ts");
const host = os.hostname().toLowerCase();

type Case = { user?: unknown; project?: unknown; untrusted?: boolean };

describe("horst settings resolution", () => {
	let t: TestSession | undefined;
	let root: string | undefined;
	afterEach(() => {
		t?.dispose();
		t = undefined;
		if (root) rmSync(root, { recursive: true, force: true });
		root = undefined;
	});

	async function start(input: Case) {
		root = mkdtempSync(path.join(tmpdir(), "pi-ext-horst-settings-"));
		const write = (dir: string, value: unknown) => {
			mkdirSync(dir, { recursive: true });
			writeFileSync(
				path.join(dir, "settings.json"),
				JSON.stringify({ horst: value }),
			);
		};
		if ("user" in input)
			write(path.join(root, ".test-home/.pi/agent"), input.user);
		if ("project" in input) write(path.join(root, ".pi"), input.project);
		t = await createTestSession({ cwd: root, extensions: [extensionPath] });
		const runner = t.session.extensionRunner;
		if (input.untrusted) {
			t.session.settingsManager.setProjectTrusted(false);
			await runner.emit({ type: "session_start", reason: "startup" });
		}
		const { horst } = (
			await runner.emitBeforeAgentStart("prompt", undefined, { cwd: t.cwd })
		).systemPromptOptions.sections;
		const warnings = t.events
			.uiCallsFor("notify")
			.filter((call) => call.args[1] === "warning")
			.map((call) => call.args[0]);
		return {
			shell: /preferred shell: (.*)/.exec(horst ?? "")?.[1],
			active: t.session.getActiveToolNames(),
			warnings: [...new Set(warnings)],
		};
	}

	it.each([
		["default", {}, undefined],
		["user", { user: { preferredShell: "nu" } }, "nu"],
		[
			"project over user",
			{ user: { preferredShell: "nu" }, project: { preferredShell: "fish" } },
			"fish",
		],
		[
			"user kept when project omits key",
			{ user: { preferredShell: "nu" }, project: { hosts: {} } },
			"nu",
		],
		[
			"untrusted project ignored",
			{
				user: { preferredShell: "nu" },
				project: { preferredShell: "fish" },
				untrusted: true,
			},
			"nu",
		],
		[
			"untrusted project ignored without user",
			{ project: { preferredShell: "fish" }, untrusted: true },
			undefined,
		],
	] satisfies [string, Case, string | undefined][])(
		"preferredShell: %s",
		async (_name, input, expected) => {
			const { shell, warnings } = await start(input);
			expect(shell).toBe(expected);
			expect(warnings).toEqual([]);
		},
	);

	it("deep-merges host entries across user and project", async () => {
		const { active, warnings } = await start({
			user: { hosts: { [host]: { tools: { add: ["grep"] } } } },
			project: { hosts: { [host]: { tools: { remove: ["write"] } } } },
		});
		expect(active).toContain("grep");
		expect(active).not.toContain("write");
		expect(warnings).toEqual([]);
	});

	it("replaces tool arrays instead of merging them", async () => {
		const { active } = await start({
			user: { hosts: { [host]: { tools: { add: ["grep"] } } } },
			project: { hosts: { [host]: { tools: { add: ["find"] } } } },
		});
		expect(active).toContain("find");
		expect(active).not.toContain("grep");
	});

	it.each([
		[
			"invalid project",
			{
				user: {
					preferredShell: "nu",
					hosts: { [host]: { tools: { add: ["grep"] } } },
				},
				project: { preferredShell: 42 },
			},
			"horst: Invalid horst from merged project and user settings: preferredShell must be a non-empty string; settings ignored",
		],
		[
			"non-object project",
			{ user: { preferredShell: "nu" }, project: "nu" },
			"horst: Invalid horst from project settings: expected an object; settings ignored",
		],
		[
			"invalid user",
			{ user: { hosts: { [host]: { tools: { add: "grep" } } } } },
			`horst: Invalid horst from user settings: hosts.${host}.tools.add must be an array of non-empty strings; settings ignored`,
		],
	] satisfies [string, Case, string][])(
		"rejects %s without falling through",
		async (_name, input, warning) => {
			const { shell, active, warnings } = await start(input);
			expect(shell).toBeUndefined();
			expect(active).not.toContain("grep");
			expect(warnings).toEqual([warning]);
		},
	);
});