Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/notes/__tests__/debug.test.ts

Raw
import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";

it("records a terminal transition after session shutdown", async () => {
	const root = mkdtempSync(join(tmpdir(), "pi-ext-notes-debug-"));
	const original = {
		debug: process.env.PI_NOTES_DEBUG,
		state: process.env.XDG_STATE_HOME,
	};
	let session: TestSession | undefined;
	try {
		process.env.PI_NOTES_DEBUG = "1";
		process.env.XDG_STATE_HOME = join(root, "state");
		const { default: extension } = await import("../index.ts");
		session = await createTestSession({
			cwd: root,
			env: {
				PI_NOTES_DEBUG: "1",
				XDG_STATE_HOME: join(root, "state"),
			},
			extensionFactories: [extension],
		});
		const runner = session.session.extensionRunner;
		await runner.emit({ type: "session_start", reason: "startup" });
		const command = runner.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",
				targetSessionFile: undefined,
			});
			manager.newSession();
			await runner.emit({
				type: "session_start",
				reason: "new",
				previousSessionFile: undefined,
			});
			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(root, "state", "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),
			}),
		]);
	} finally {
		session?.dispose();
		vi.restoreAllMocks();
		if (original.debug === undefined) delete process.env.PI_NOTES_DEBUG;
		else process.env.PI_NOTES_DEBUG = original.debug;
		if (original.state === undefined) delete process.env.XDG_STATE_HOME;
		else process.env.XDG_STATE_HOME = original.state;
		rmSync(root, { recursive: true, force: true });
	}
});