Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

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

Raw
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
	createTestSession,
	sandboxEnv,
	type TestSession,
	withProcessEnv,
} from "../../../test/harness";

let root = "";
let session: TestSession | 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));
}

async function sandbox(debug: "0" | "1"): Promise<NodeJS.ProcessEnv> {
	root = await mkdtemp(join(tmpdir(), "pi-ext-chrome-cdp-debug-"));
	return sandboxEnv(root, { env: { PI_CHROME_CDP_DEBUG: debug } });
}

afterEach(async () => {
	vi.doUnmock("node:fs");
	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 () => {
		const env = await sandbox("1");
		vi.resetModules();
		const factory = await withProcessEnv(
			env,
			async () => (await import("../index.ts")).default,
		);
		session = await createTestSession({
			cwd: root,
			extensionFactories: [factory],
			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",
		});

		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();
	});
});