import { readdirSync, readFileSync } from "node:fs"; import path 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"; function extensionPath(): string { return fileURLToPath(new URL("../index.ts", import.meta.url)); } async function eventually( read: () => T, accept: (value: T) => boolean, ): Promise { const deadline = Date.now() + 3_000; let value = read(); while (!accept(value) && Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 20)); value = read(); } return value; } describe("good-job debug harness", () => { let session: PiSession | undefined; afterEach(async () => { await session?.dispose(); session = undefined; vi.restoreAllMocks(); }); it("writes its lifecycle records in the isolated sandbox", async () => { session = await createPiSession({ env: { PI_GOOD_JOB_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 directory = path.join( session.env["XDG_STATE_HOME"] ?? "", "pi-ext", "debug", "good-job", ); const readRecords = () => 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 records = await eventually( readRecords, (value) => value.some((record) => record.event === "session.start") && value.some((record) => record.event === "session.shutdown"), ); expect(records).toContainEqual( expect.objectContaining({ event: "session.start", extension: "good-job", }), ); expect(records).toContainEqual( expect.objectContaining({ event: "session.shutdown", extension: "good-job", }), ); }); });