import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, 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 session: PiSession | undefined; afterEach(async () => { await session?.dispose(); session = undefined; vi.restoreAllMocks(); }); it("records a terminal transition after session shutdown", async () => { session = await createPiSession({ extensionPaths: [extensionPath], env: { PI_NOTES_DEBUG: "1" }, }); const runner = session.session.extensionRunner; await runner.emit({ type: "session_start", reason: "startup" }); const command = session.extensions[0]!.commands.get("note"); if (!command) throw new Error("missing note command"); await command.handler("add failed delivery", runner.createCommandContext()); const ctx = runner.createCommandContext() as any; const manager = session.session.sessionManager as any; ctx.newSession = vi.fn(async (options) => { await runner.emit({ type: "session_shutdown", reason: "new", }); manager.newSession(); await runner.emit({ type: "session_start", reason: "new", }); await options.withSession({ ...ctx, sendUserMessage: async () => { throw new Error("delivery failed"); }, }); return { cancelled: false }; }); await command.handler("new 1", ctx); await command.handler("add later", runner.createCommandContext()); await command.handler("add", runner.createCommandContext()); const directory = join( session.env["XDG_STATE_HOME"] as string, "pi-ext", "debug", "notes", ); const files = readdirSync(directory); expect(files).toHaveLength(2); const events = files.flatMap((file) => readFileSync(join(directory, file), "utf8") .trim() .split("\n") .map((line) => JSON.parse(line)), ); const starts = events.filter( (event) => event.event === "note.session_transition.start", ); const terminals = events.filter( (event) => event.event === "note.session_transition.finish" || event.event === "note.session_transition.error", ); expect(starts).toHaveLength(1); expect(events).toEqual( expect.arrayContaining([ expect.objectContaining({ event: "note.change", kind: "add", outcome: "applied", }), expect.objectContaining({ event: "note.change", kind: "add", outcome: "invalid", }), ]), ); expect(terminals).toEqual([ expect.objectContaining({ extension: "notes", event: "note.session_transition.finish", kind: "new", outcome: "delivery_failed", spanId: starts[0]?.spanId, durationMs: expect.any(Number), }), ]); });