Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/chrome-cdp/__tests__/settings.test.ts

Raw
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it } from "vitest";
import {
	createPiSession,
	type PiSession,
} from "../../../test/fixture/create-pi-session.ts";

const extensionPath = fileURLToPath(new URL("../index.ts", import.meta.url));

type Case = {
	user?: Record<string, unknown>;
	project?: Record<string, unknown>;
	flag?: boolean;
	untrusted?: boolean;
};

type Notification = {
	message: string;
	type?: "info" | "warning" | "error" | undefined;
};

describe("chrome-cdp settings resolution", () => {
	let session: PiSession | undefined;
	let root: string | undefined;
	let notifications: Notification[] = [];
	afterEach(async () => {
		await session?.dispose();
		session = undefined;
		notifications = [];
		if (root) rmSync(root, { recursive: true, force: true });
		root = undefined;
	});

	function recordingUI(): ExtensionUIContext {
		const noop = () => undefined;
		return {
			select: async () => undefined,
			confirm: async () => false,
			input: async () => undefined,
			editor: async () => undefined,
			custom: async () => undefined,
			notify: (message: string, type?: Notification["type"]) =>
				notifications.push({ message, type }),
			onTerminalInput: () => noop,
			setStatus: noop,
			setWorkingMessage: noop,
			setWorkingVisible: noop,
			setWorkingIndicator: noop,
		} as unknown as ExtensionUIContext;
	}

	async function start(input: Case) {
		root = mkdtempSync(path.join(tmpdir(), "pi-ext-chrome-cdp-settings-"));
		if (input.project) {
			const dir = path.join(root, ".pi");
			mkdirSync(dir, { recursive: true });
			writeFileSync(
				path.join(dir, "settings.json"),
				JSON.stringify({ "chrome-cdp": input.project }),
			);
		}
		session = await createPiSession({
			cwd: root,
			extensionPaths: [extensionPath],
			...(input.user ? { userSettings: { "chrome-cdp": input.user } } : {}),
		});
		const runner = session.session.extensionRunner;
		runner.setUIContext(recordingUI());
		if (input.untrusted)
			session.session.settingsManager.setProjectTrusted(false);
		if (input.flag !== undefined) runner.setFlagValue("chrome", input.flag);
		await runner.emit({ type: "session_start", reason: "startup" });
		return session;
	}

	const warnings = () => [
		...new Set(
			notifications
				.filter(
					(call) =>
						call.type === "warning" &&
						!call.message.includes("chrome_cdp is deferred"),
				)
				.map((call) => String(call.message)),
		),
	];
	const active = (s: PiSession) =>
		s.session.getActiveToolNames().includes("chrome_cdp");

	it("registers --chrome without a default so omission stays distinct", async () => {
		const s = await start({});
		const runner = s.session.extensionRunner;
		expect(active(s)).toBe(false);
		expect(runner.getFlags().get("chrome")?.default).toBe(undefined);
		expect(runner.getFlagValues().has("chrome")).toBe(false);
	});

	it.each([
		["user true", { user: { enable: true } }, true],
		[
			"project false over user true",
			{ user: { enable: true }, project: { enable: false } },
			false,
		],
		[
			"project true over user false",
			{ user: { enable: false }, project: { enable: true } },
			true,
		],
		[
			"flag over project false",
			{ project: { enable: false }, flag: true },
			true,
		],
		[
			"untrusted project ignored",
			{ user: { enable: true }, project: { enable: false }, untrusted: true },
			true,
		],
	] satisfies [string, Case, boolean][])(
		"enable: %s",
		async (_name, input, expected) => {
			const s = await start(input);
			expect(active(s)).toBe(expected);
			expect(warnings()).toEqual([]);
		},
	);

	it.each([
		[
			"string project",
			{ user: { enable: true }, project: { enable: "true" } },
			"project settings",
		],
		["numeric user", { user: { enable: 1 } }, "user settings"],
	] satisfies [string, Case, string][])(
		"enable: rejects %s without falling through",
		async (_name, input, source) => {
			const s = await start(input);
			expect(active(s)).toBe(false);
			expect(warnings()).toEqual([`Invalid chrome-cdp.enable from ${source}`]);
		},
	);

	// findExecutable names the configured path in its warning, exposing the resolved value.
	async function toggle(input: Case) {
		const s = await start(input);
		const runner = s.session.extensionRunner;
		await runner.getCommand("chrome")?.handler("", {
			...runner.createCommandContext(),
			mode: "print" as const,
			hasUI: false,
		});
		return warnings();
	}

	it.each([
		[
			"user only",
			{ user: { executablePath: "/missing/user" } },
			"/missing/user",
		],
		[
			"project over user",
			{
				user: { executablePath: "/missing/user" },
				project: { executablePath: "/missing/project" },
			},
			"/missing/project",
		],
		[
			"untrusted project ignored",
			{
				user: { executablePath: "/missing/user" },
				project: { executablePath: "/missing/project" },
				untrusted: true,
			},
			"/missing/user",
		],
	] satisfies [string, Case, string][])(
		"executablePath: %s",
		async (_name, input, expected) => {
			expect(await toggle(input)).toEqual([
				`Chrome unavailable: Chrome not found; set chrome-cdp.executablePath (checked: ${expected})`,
			]);
		},
	);

	it("executablePath: rejects invalid project value without falling through", async () => {
		expect(
			await toggle({
				user: { executablePath: "/missing/user" },
				project: { executablePath: 1 },
			}),
		).toEqual([
			"Chrome unavailable: Invalid chrome-cdp.executablePath from project settings",
		]);
	});

	it.each([
		["flags", { flags: ["--ok", 1] }],
		["profile", { profile: 1 }],
		["artifactDir", { artifactDir: [] }],
	] satisfies [string, Record<string, unknown>][])(
		"%s: invalid value fails the tool before launch",
		async (key, project) => {
			const s = await start({ project });
			const runner = s.session.extensionRunner;
			const tool = s.extensions[0]?.tools.get("chrome_cdp")?.definition;
			await expect(
				tool?.execute(
					"id",
					{ operations: [{ kind: "status" }] } as never,
					undefined,
					undefined,
					runner.createToolContext("id", undefined),
				),
			).rejects.toThrow(`Invalid chrome-cdp.${key} from project settings`);
		},
	);
});