repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/nu-bang/__tests__/contract.test.ts
Rawimport { 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");
});
});