repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/firefox-bidi/__tests__/debug.test.ts
Rawimport { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createTestSession,
sandboxEnv,
type TestSession,
withProcessEnv,
} from "../../../test/harness";
function envValue(env: NodeJS.ProcessEnv, key: string): string {
const value = env[key];
if (!value) throw new Error(`missing sandbox environment variable: ${key}`);
return value;
}
function directory(env: NodeJS.ProcessEnv): string {
const home = envValue(env, "HOME");
if (process.platform === "darwin")
return path.join(home, "Library", "Logs", "pi-ext", "firefox-bidi");
if (process.platform === "win32")
return path.join(
env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"),
"pi-ext",
"debug",
"firefox-bidi",
);
return path.join(
envValue(env, "XDG_STATE_HOME"),
"pi-ext",
"debug",
"firefox-bidi",
);
}
function records(env: NodeJS.ProcessEnv): Record<string, unknown>[] {
try {
const files = readdirSync(directory(env)).filter((file) =>
file.endsWith(".jsonl"),
);
return files.flatMap((file) =>
readFileSync(path.join(directory(env), file), "utf8")
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as Record<string, unknown>),
);
} catch {
return [];
}
}
async function writeSettings(
env: NodeJS.ProcessEnv,
debug: unknown,
): Promise<void> {
const agentDirectory = path.join(envValue(env, "HOME"), ".pi", "agent");
await mkdir(agentDirectory, { recursive: true });
await writeFile(
path.join(agentDirectory, "settings.json"),
JSON.stringify({ "pi-ext": { debug } }),
);
}
describe("firefox-bidi debug", () => {
let root: string | undefined;
let session: TestSession | undefined;
afterEach(() => {
vi.doUnmock("node:fs");
vi.resetModules();
session?.dispose();
if (root) rmSync(root, { recursive: true, force: true });
session = undefined;
root = undefined;
});
it("writes lifecycle events and pairs command spans", async () => {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-firefox-bidi-debug-"));
const env = sandboxEnv(root, {
env: { PI_FIREFOX_BIDI_DEBUG: "1" },
});
vi.resetModules();
const factory = await withProcessEnv(
env,
async () => (await import("../index.ts")).default,
);
session = await createTestSession({
cwd: root,
extensionFactories: [factory],
env: { PI_FIREFOX_BIDI_DEBUG: "1" },
});
const runner = session.session.extensionRunner;
await runner.emit({ type: "session_start", reason: "startup" });
await runner
.getCommand("firefox")
?.handler("close", runner.createCommandContext());
await runner.emit({ type: "session_shutdown", reason: "quit" });
const trace = records(env);
const events = trace.map((record) => record.event);
expect(events).toEqual(
expect.arrayContaining([
"session.start",
"session.shutdown",
"firefox.command.start",
"firefox.command.finish",
]),
);
const start = trace.find(
(record) => record.event === "firefox.command.start",
);
const finish = trace.find(
(record) => record.event === "firefox.command.finish",
);
expect(finish).toMatchObject({
spanId: start?.spanId,
durationMs: expect.any(Number),
});
expect(finish?.durationMs).toBeGreaterThanOrEqual(0);
});
it("uses user-level extension selection", async () => {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-firefox-bidi-debug-"));
const env = sandboxEnv(root);
await writeSettings(env, "firefox-bidi");
vi.resetModules();
const debug = await withProcessEnv(
env,
() => import("../src/pi-ext-debug.ts"),
);
await withProcessEnv(env, () => {
debug.dbg?.("test.selection", { count: 1, payload: "secret" });
debug.closeDebug();
});
const [record] = records(env);
expect(record).toMatchObject({
extension: "firefox-bidi",
event: "test.selection",
count: 1,
});
expect(JSON.stringify(record)).not.toContain("secret");
});
it("does no debug work when explicit disable overrides settings", async () => {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-firefox-bidi-debug-"));
const env = sandboxEnv(root, {
env: { PI_FIREFOX_BIDI_DEBUG: "0" },
});
await writeSettings(env, true);
vi.resetModules();
const debug = await withProcessEnv(
env,
() => import("../src/pi-ext-debug.ts"),
);
let evaluated = false;
debug.dbg?.(
"test.disabled",
(() => {
evaluated = true;
return {};
})(),
);
expect(debug.dbg).toBeUndefined();
expect(evaluated).toBe(false);
expect(records(env)).toEqual([]);
});
it("does not throw when a debug write fails", async () => {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-firefox-bidi-debug-"));
const env = sandboxEnv(root, {
env: { PI_FIREFOX_BIDI_DEBUG: "1" },
});
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 }));
vi.resetModules();
const debug = await withProcessEnv(
env,
() => import("../src/pi-ext-debug.ts"),
);
await expect(
withProcessEnv(env, () => {
debug.dbg?.("test.failure");
debug.dbg?.("test.failure");
debug.closeDebug();
}),
).resolves.toBeUndefined();
expect(writeSync).toHaveBeenCalledOnce();
});
});