Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/nu-bang/__tests__/contract.test.ts

Raw
import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import {
	createPiSession,
	type PiSession,
} from "../../../test/fixture/create-pi-session.ts";

describe("nu-bang contract", () => {
	let session: PiSession | undefined;

	afterEach(async () => {
		await session?.dispose();
		session = undefined;
	});

	it("writes enabled lifecycle debug records in an isolated sandbox", async () => {
		const root = mkdtempSync(join(tmpdir(), "pi-ext-nu-bang-debug-"));
		try {
			session = await createPiSession({
				cwd: root,
				env: {
					PI_NU_BANG_DEBUG: "1",
					XDG_STATE_HOME: join(root, "state"),
				},
				extensionPaths: [
					fileURLToPath(new URL("../index.ts", import.meta.url)),
				],
			});
			await session.session.extensionRunner.emit({
				type: "session_start",
				reason: "startup",
			});
			await session.session.extensionRunner.emit({
				type: "session_shutdown",
				reason: "quit",
			});
			const events = readdirSync(
				join(root, "state", "pi-ext", "debug", "nu-bang"),
			).flatMap((file) =>
				readFileSync(
					join(root, "state", "pi-ext", "debug", "nu-bang", file),
					"utf8",
				)
					.trim()
					.split("\n")
					.map((line) => JSON.parse(line)),
			);
			expect(events).toEqual(
				expect.arrayContaining([
					expect.objectContaining({
						extension: "nu-bang",
						event: "session.start",
					}),
					expect.objectContaining({
						extension: "nu-bang",
						event: "session.shutdown",
					}),
				]),
			);
		} finally {
			await session?.dispose();
			session = undefined;
			rmSync(root, { recursive: true, force: true });
		}
	});

	it("loads in real Pi extension runtime", async () => {
		session = await createPiSession({
			extensionPaths: [fileURLToPath(new URL("../index.ts", import.meta.url))],
		});

		expect(session.extensions).toHaveLength(1);
	});

	it("streams ordinary bang commands and refuses interactive ones outside the TUI", async () => {
		session = await createPiSession({
			extensionPaths: [fileURLToPath(new URL("../index.ts", import.meta.url))],
		});
		const runner = session.session.extensionRunner;

		const streamed = await runner.emitUserBash({
			type: "user_bash",
			command: "[1 2 3] | math sum",
			excludeFromContext: false,
			cwd: session.cwd,
		});
		if (!streamed?.operations) throw new Error("expected bash operations");
		const executed = await session.session.executeBash(
			"[1 2 3] | math sum",
			undefined,
			{ operations: streamed.operations },
		);
		expect(executed.exitCode).toBe(0);
		expect(executed.output.trim()).toBe("6");

		const interactive = await runner.emitUserBash({
			type: "user_bash",
			command: "vim notes.md",
			excludeFromContext: false,
			cwd: session.cwd,
		});
		expect(interactive?.result).toMatchObject({
			exitCode: 1,
			cancelled: false,
		});
		expect(interactive?.result?.output).toContain("require Pi's TUI");
	});
});