repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/chrome-cdp/__tests__/settings.test.ts
Rawimport { 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`);
},
);
});