import { readdirSync, readFileSync } from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createPiSession, type PiSession, } from "../../../test/fixture/create-pi-session.ts"; const extensionPath = path.resolve(import.meta.dirname, "../index.ts"); describe("masks debug interface", () => { let session: PiSession | undefined; afterEach(async () => { await session?.dispose(); session = undefined; }); it("writes lifecycle records in the isolated sandbox", async () => { session = await createPiSession({ env: { PI_MASKS_DEBUG: "1" }, extensionPaths: [extensionPath], }); await session.session.extensionRunner.emit({ type: "session_start", reason: "startup", }); await session.session.extensionRunner.emit({ type: "session_shutdown", reason: "quit", }); const stateHome = session.env["XDG_STATE_HOME"]; if (!stateHome) throw new Error("missing sandbox state home"); const directory = path.join(stateHome, "pi-ext", "debug", "masks"); // Debug records flush asynchronously; poll until both events appear. const records = await vi.waitFor( () => { const parsed = readdirSync(directory).flatMap((file) => readFileSync(path.join(directory, file), "utf8") .trim() .split("\n") .filter(Boolean) .map( (line) => JSON.parse(line) as { event: string; extension: string }, ), ); const events = parsed.map((record) => record.event); if ( !events.includes("session.start") || !events.includes("session.shutdown") ) throw new Error("lifecycle events not flushed yet"); return parsed; }, { timeout: 2_000, interval: 20 }, ); expect(records).toContainEqual( expect.objectContaining({ event: "session.start", extension: "masks" }), ); expect(records).toContainEqual( expect.objectContaining({ event: "session.shutdown", extension: "masks", }), ); }); });