repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/rtk/__tests__/contract.test.ts
Rawimport {
chmodSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
createPiSession,
type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
let session: PiSession | undefined;
let fakeRtkDir: string | undefined;
function rtkPath(): string {
return fileURLToPath(new URL("../index.ts", import.meta.url));
}
function rtkPathEnv(dir: string): string {
return `${dir}${path.delimiter}${process.env["PATH"] ?? ""}`;
}
function installFakeRtk(): string {
const dir = mkdtempSync(path.join(tmpdir(), "pi-ext-rtk-"));
const script = [
"#!/usr/bin/env node",
"const [, , sub, command] = process.argv;",
"if (sub === '--version') { console.log('rtk 0.37.2'); process.exit(0); }",
"if (sub === 'rewrite' && command === 'git status') { console.log('rtk git status'); process.exit(0); }",
"if (sub === 'rewrite' && command === 'find . -name AGENTS.md') { console.log('rtk find . -name AGENTS.md'); process.exit(3); }",
"if (sub === 'rewrite' && command === 'uptime') { process.exit(2); }",
"process.exit(1);",
"",
].join("\n");
const bin = path.join(dir, process.platform === "win32" ? "rtk.cmd" : "rtk");
if (process.platform === "win32") {
writeFileSync(
bin,
`@echo off\nnode "${path.join(dir, "rtk.js")}" %*\n`,
"utf8",
);
writeFileSync(path.join(dir, "rtk.js"), script, "utf8");
} else {
writeFileSync(bin, script, "utf8");
chmodSync(bin, 0o755);
}
return dir;
}
beforeEach(async () => {
fakeRtkDir = installFakeRtk();
session = await createPiSession({
extensionPaths: [rtkPath()],
env: { PATH: rtkPathEnv(fakeRtkDir) },
});
});
afterEach(async () => {
await session?.dispose();
session = undefined;
if (fakeRtkDir) rmSync(fakeRtkDir, { recursive: true, force: true });
fakeRtkDir = undefined;
});
describe("rtk contract integration", () => {
it("loads with no tools and no commands", () => {
const [extension] = session?.extensions ?? [];
expect(extension?.path).toBe(rtkPath());
expect([...(extension?.tools.keys() ?? [])]).toEqual([]);
expect([...(extension?.commands.keys() ?? [])]).toEqual([]);
});
it("writes its opt-in lifecycle trace inside the fixture sandbox", async () => {
const root = mkdtempSync(path.join(tmpdir(), "pi-ext-rtk-debug-"));
const debug = await createPiSession({
cwd: root,
extensionPaths: [rtkPath()],
env: {
PI_RTK_DEBUG: "1",
PATH: rtkPathEnv(fakeRtkDir ?? ""),
},
});
try {
const runner = debug.session.extensionRunner;
await runner.emit({ type: "session_start", reason: "startup" });
await runner.emitToolCall({
type: "tool_call",
toolName: "bash",
toolCallId: "debug-rewrite",
input: { command: "git status" },
} as never);
await runner.emitToolCall({
type: "tool_call",
toolName: "bash",
toolCallId: "debug-fallback",
input: { command: "ps aux" },
} as never);
await runner.emitToolCall({
type: "tool_call",
toolName: "bash",
toolCallId: "debug-failure",
input: { command: "uptime" },
} as never);
await runner.emit({ type: "session_shutdown", reason: "quit" });
const stateHome = debug.env["XDG_STATE_HOME"];
if (!stateHome) throw new Error("missing sandbox state home");
const directory = path.join(stateHome, "pi-ext", "debug", "rtk");
const [file] = readdirSync(directory);
if (!file) throw new Error("missing debug file");
const records = readFileSync(path.join(directory, file), "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line));
expect(records.map((record) => record.event)).toEqual(
expect.arrayContaining([
"session.start",
"session.shutdown",
"rtk.rewrite",
]),
);
expect(
records
.filter((record) => record.event === "rtk.rewrite")
.map((record) => record.outcome),
).toEqual(["rewritten", "fallback", "unchanged"]);
expect(
records.find(
(record) =>
record.event === "rtk.rewrite" && record.outcome === "unchanged",
)?.reason,
).toBe("unexpected_exit");
} finally {
await debug.dispose();
rmSync(root, { recursive: true, force: true });
}
});
it("mutates bash tool calls before execution", async () => {
const event = {
type: "tool_call",
toolName: "bash",
toolCallId: "1",
input: { command: "git status" },
};
await session?.session.extensionRunner.emitToolCall(event as never);
expect(event.input.command).toBe("rtk git status");
});
it("mutates local fallback bash tool calls before execution", async () => {
const event = {
type: "tool_call",
toolName: "bash",
toolCallId: "2",
input: { command: "ps aux" },
};
await session?.session.extensionRunner.emitToolCall(event as never);
expect(event.input.command).toBe("rtk summary ps aux");
});
it("rewrites find commands without a compatibility probe", async () => {
const event = {
type: "tool_call",
toolName: "bash",
toolCallId: "3",
input: { command: "find . -name AGENTS.md" },
};
await session?.session.extensionRunner.emitToolCall(event as never);
expect(event.input.command).toBe("rtk find . -name AGENTS.md");
});
});