repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/chrome-cdp/__tests__/debug.test.ts
Rawimport { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createPiSession,
type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
const extensionPath = fileURLToPath(new URL("../index.ts", import.meta.url));
let root = "";
let session: PiSession | undefined;
async function tracePath(): Promise<string> {
const directory = join(root, ".test-state", "pi-ext", "debug", "chrome-cdp");
const entries = await readdir(directory);
expect(entries).toHaveLength(1);
return join(directory, entries[0]!);
}
async function records(): Promise<Record<string, unknown>[]> {
return (await readFile(await tracePath(), "utf8"))
.trim()
.split("\n")
.map((line) => JSON.parse(line));
}
function sandboxEnv(dir: string, debug: "0" | "1"): NodeJS.ProcessEnv {
return {
...process.env,
HOME: join(dir, ".test-home"),
USERPROFILE: join(dir, ".test-home"),
XDG_STATE_HOME: join(dir, ".test-state"),
APPDATA: join(dir, ".test-config"),
LOCALAPPDATA: join(dir, ".test-state"),
PI_CHROME_CDP_DEBUG: debug,
};
}
async function withProcessEnv<T>(
env: NodeJS.ProcessEnv,
fn: () => T | Promise<T>,
): Promise<T> {
const original = { ...process.env };
for (const key of Object.keys(process.env)) delete process.env[key];
Object.assign(process.env, env);
try {
return await fn();
} finally {
for (const key of Object.keys(process.env)) delete process.env[key];
Object.assign(process.env, original);
}
}
async function sandbox(debug: "0" | "1"): Promise<NodeJS.ProcessEnv> {
root = await mkdtemp(join(tmpdir(), "pi-ext-chrome-cdp-debug-"));
return sandboxEnv(root, debug);
}
afterEach(async () => {
vi.doUnmock("node:fs");
await session?.dispose();
session = undefined;
if (root) await rm(root, { recursive: true, force: true });
root = "";
});
describe("chrome-cdp debug", () => {
it("writes session lifecycle events to sandbox user state", async () => {
root = await mkdtemp(join(tmpdir(), "pi-ext-chrome-cdp-debug-"));
session = await createPiSession({
cwd: root,
extensionPaths: [extensionPath],
env: { PI_CHROME_CDP_DEBUG: "1" },
});
await session.session.extensionRunner.emit({
type: "session_start",
reason: "startup",
});
await session.session.extensionRunner.emit({
type: "session_shutdown",
reason: "quit",
});
await vi.waitFor(async () => {
expect((await records()).map((record) => record["event"])).toEqual(
expect.arrayContaining(["session.start", "session.shutdown"]),
);
});
});
it("pairs monotonic operation spans", async () => {
const env = await sandbox("1");
vi.resetModules();
await withProcessEnv(env, async () => {
const { closeDebug, span } = await import("../src/pi-ext-debug.ts");
const end = span?.("operation", { index: 2, kind: "cdp" });
await new Promise((resolve) => setTimeout(resolve, 1));
end?.("finish", { status: "ok" });
closeDebug();
});
const [start, finish] = await records();
expect(start).toMatchObject({
event: "operation.start",
index: 2,
kind: "cdp",
});
expect(finish).toMatchObject({
event: "operation.finish",
spanId: start!["spanId"],
status: "ok",
});
expect(finish!["durationMs"]).toEqual(expect.any(Number));
});
it("does no debug work when disabled", async () => {
const env = await sandbox("0");
vi.resetModules();
await withProcessEnv(env, async () => {
const { dbg } = await import("../src/pi-ext-debug.ts");
let built = false;
dbg?.(
"test.disabled",
(() => {
built = true;
return {};
})(),
);
expect(dbg).toBeUndefined();
expect(built).toBe(false);
});
await expect(tracePath()).rejects.toMatchObject({ code: "ENOENT" });
});
it("absorbs writer failures", async () => {
const env = await sandbox("1");
vi.resetModules();
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
const writeSync = vi.fn(() => {
throw new Error("write failed");
});
vi.doMock("node:fs", () => ({ ...actual, writeSync }));
await withProcessEnv(env, async () => {
const { dbg } = await import("../src/pi-ext-debug.ts");
expect(() => dbg?.("test.failure")).not.toThrow();
expect(() => dbg?.("test.failure")).not.toThrow();
});
expect(writeSync).toHaveBeenCalledOnce();
});
});