repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/horst/__tests__/contract.test.ts
Rawimport { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os, { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createPiSession,
type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
import {
getHostSection,
type HostSnapshot,
renderHostSection,
} from "../host.ts";
import {
EMPTY_HORST_SETTINGS,
parseHorst,
resolveHorstTools,
} from "../settings.ts";
const horstPath = fileURLToPath(new URL("../index.ts", import.meta.url));
describe("parseHorst", () => {
it("keeps well-formed tool deltas and lowercases keys", () => {
const settings = parseHorst({
preferredShell: " nu ",
os: { WIN32: { tools: ["+powershell", "-bash"] } },
hosts: { WinBox: { tools: ["-write"] } },
});
expect(settings).toEqual({
os: { win32: ["+powershell", "-bash"] },
hosts: { winbox: ["-write"] },
preferredShell: "nu",
});
expect(parseHorst({})).toEqual(EMPTY_HORST_SETTINGS);
expect(parseHorst({ os: { lnx: { tools: [] } } })).toEqual({
os: { lnx: [] },
hosts: {},
});
});
it.each([
[undefined, "expected an object"],
[[], "expected an object"],
[{ preferredShell: " " }, "preferredShell must be a non-empty string"],
[{ preferredShell: 42 }, "preferredShell must be a non-empty string"],
[{ hosts: "nope" }, "hosts must be an object"],
[{ os: { lnx: { flat: {} } } }, "os.lnx.tools must be an array"],
[
{ os: { lnx: { tools: { add: ["grep"], remove: ["bash"] } } } },
'use ["+name", "-name"] instead of { add, remove }',
],
[{ hosts: { a: { tools: "-read" } } }, "hosts.a.tools must be an array"],
...[42, "read", "+", "-", "+read*", "+ read", "-read "].map(
(tool): [unknown, string] => [
{ os: { lnx: { tools: [tool] } } },
"os.lnx.tools must contain only +name/-name modifiers",
],
),
] satisfies [unknown, string][])("rejects %j", (raw, message) => {
expect(() => parseHorst(raw)).toThrow(message);
});
});
describe("resolveHorstTools", () => {
it.each([
["linux", "lnx"],
["darwin", "mac"],
["win32", "win32"],
])("maps %s to %s", (platform, key) => {
expect(
resolveHorstTools({ os: { [key]: ["+grep"] }, hosts: {} }, platform, "x"),
).toEqual(["+grep"]);
});
it("matches hostnames case-insensitively and appends host modifiers after os", () => {
const settings = {
os: { win32: ["+powershell", "-bash"] },
hosts: { winbox: ["-powershell", "+bash", "-write"] },
};
expect(resolveHorstTools(settings, "win32", "WinBox")).toEqual([
"+powershell",
"-bash",
"-powershell",
"+bash",
"-write",
]);
});
it("returns undefined without a match", () => {
expect(
resolveHorstTools(EMPTY_HORST_SETTINGS, "linux", "x"),
).toBeUndefined();
});
});
describe("renderHostSection", () => {
it("renders host facts without a misleading environment shell or editor", () => {
const host: HostSnapshot = {
hostname: "winbox",
osLabel: "Fedora Linux 42",
kernel: "6.14.0",
arch: "x64",
cpuModel: "AMD Ryzen 9",
cpuCount: 24,
memGiB: 32,
};
const section = renderHostSection(host);
expect(section).toContain("hostname: winbox");
expect(section).toContain("os: Fedora Linux 42");
expect(section).toContain("cpu: AMD Ryzen 9 × 24");
expect(section).toContain("mem: 32 GiB");
expect(section).not.toContain("Stable host environment facts:");
expect(section).not.toContain("shell:");
expect(section).not.toContain("editor:");
expect(
renderHostSection({ ...host, loginShell: "/bin/bash" }, "nu"),
).toContain("login shell: /bin/bash\npreferred shell: nu");
});
it("reads account login shell independently of inherited SHELL", async () => {
vi.resetModules();
const userInfo = vi.spyOn(os, "userInfo").mockReturnValue({
...os.userInfo(),
shell: "/bin/zsh",
});
try {
const { getHostSection: freshSection } = await import("../host.ts");
expect(await freshSection()).toContain("login shell: /bin/zsh");
expect(await freshSection()).not.toContain("preferred shell:");
} finally {
userInfo.mockRestore();
}
});
it("omits unavailable account shell", async () => {
for (const shell of [null, "", "throws"] as const) {
vi.resetModules();
const userInfo = vi.spyOn(os, "userInfo");
if (shell === "throws")
userInfo.mockImplementation(() => {
throw new Error("unavailable");
});
else userInfo.mockReturnValue({ ...os.userInfo(), shell });
try {
const { getHostSection: freshSection } = await import("../host.ts");
expect(await freshSection()).not.toContain("login shell:");
} finally {
userInfo.mockRestore();
}
}
});
});
describe("horst owned fixture", () => {
let session: PiSession | undefined;
afterEach(async () => {
await session?.dispose();
session = undefined;
});
it("loads in real Pi extension runtime", async () => {
session = await createPiSession({ extensionPaths: [horstPath] });
expect(session.extensions.map((extension) => extension.path)).toContain(
horstPath,
);
});
it("applies the host tool delta from project settings", async () => {
const root = mkdtempSync(path.join(tmpdir(), "horst-delta-"));
try {
mkdirSync(path.join(root, ".pi"), { recursive: true });
writeFileSync(
path.join(root, ".pi", "settings.json"),
JSON.stringify({
horst: {
hosts: {
[os.hostname().toLowerCase()]: {
tools: ["+grep", "-grep", "+find", "+find", "-write"],
},
},
},
}),
);
session = await createPiSession({
cwd: root,
extensionPaths: [horstPath],
});
await session.session.extensionRunner.emit({
type: "session_start",
reason: "startup",
});
const active = session.session.getActiveToolNames();
expect(active).not.toContain("grep");
expect(active.filter((name) => name === "find")).toHaveLength(1);
expect(active).not.toContain("write");
} finally {
await session?.dispose();
session = undefined;
rmSync(root, { recursive: true, force: true });
}
});
it("injects the stable host section each turn", async () => {
session = await createPiSession({ extensionPaths: [horstPath] });
const runner = session.session.extensionRunner;
const first = await runner.emitBeforeAgentStart(
{ messageType: "user", prompt: "prompt" },
{
cwd: session.cwd,
},
);
const second = await runner.emitBeforeAgentStart(
{ messageType: "user", prompt: "prompt" },
{
cwd: session.cwd,
},
);
const { horst } = first.systemPromptOptions.sections;
expect(horst).toBe(second.systemPromptOptions.sections["horst"]);
expect(horst).toContain(`hostname: ${os.hostname()}`);
await expect(getHostSection()).resolves.toBe(horst);
});
it("injects project shell preference even without a tool delta", async () => {
const root = mkdtempSync(path.join(tmpdir(), "horst-shell-"));
try {
mkdirSync(path.join(root, ".pi"), { recursive: true });
writeFileSync(
path.join(root, ".pi", "settings.json"),
JSON.stringify({ horst: { preferredShell: " fish " } }),
);
session = await createPiSession({
cwd: root,
extensionPaths: [horstPath],
});
const runner = session.session.extensionRunner;
await runner.emit({ type: "session_start", reason: "startup" });
const first = await runner.emitBeforeAgentStart(
{ messageType: "user", prompt: "prompt" },
{
cwd: session.cwd,
},
);
const second = await runner.emitBeforeAgentStart(
{ messageType: "user", prompt: "prompt" },
{
cwd: session.cwd,
},
);
expect(first.systemPromptOptions.sections["horst"]).toContain(
"preferred shell: fish",
);
expect(second.systemPromptOptions.sections["horst"]).toBe(
first.systemPromptOptions.sections["horst"],
);
} finally {
await session?.dispose();
session = undefined;
rmSync(root, { recursive: true, force: true });
}
});
});