repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/horst/__tests__/harness.test.ts
Rawimport { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os, { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
import {
getHostSection,
type HostSnapshot,
renderHostSection,
} from "../host.ts";
import {
EMPTY_HORST_SETTINGS,
parseHorst,
resolveHorstTools,
} from "../settings.ts";
const horstPath = () => path.resolve(import.meta.dirname, "../index.ts");
describe("parseHorst", () => {
it("keeps well-formed tool deltas and lowercases keys", () => {
const settings = parseHorst({
preferredShell: " nu ",
os: { WIN32: { tools: { add: ["powershell"], remove: ["bash"] } } },
hosts: { WinBox: { tools: { remove: ["write"] } } },
});
expect(settings).toEqual({
os: { win32: { add: ["powershell"], remove: ["bash"] } },
hosts: { winbox: { remove: ["write"] } },
preferredShell: "nu",
});
expect(parseHorst({})).toEqual(EMPTY_HORST_SETTINGS);
expect(parseHorst({ os: { lnx: { tools: { add: [] } } } })).toEqual(
EMPTY_HORST_SETTINGS,
);
});
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 object"],
[
{ os: { lnx: { tools: { add: [42] } } } },
"os.lnx.tools.add must be an array of non-empty strings",
],
[
{ hosts: { a: { tools: { remove: "read" } } } },
"hosts.a.tools.remove must be an array of non-empty strings",
],
])("rejects %j", (raw, message) => {
expect(() => parseHorst(raw)).toThrow(message);
});
});
describe("resolveHorstTools", () => {
it("maps platform names to os keys", () => {
expect(
resolveHorstTools(
{ os: { lnx: { add: ["grep"] } }, hosts: {} },
"linux",
"x",
),
).toEqual({ add: ["grep"] });
expect(
resolveHorstTools(
{ os: { mac: { add: ["grep"] } }, hosts: {} },
"darwin",
"x",
),
).toEqual({ add: ["grep"] });
expect(
resolveHorstTools(
{ os: { win32: { add: ["powershell"] } }, hosts: {} },
"win32",
"x",
),
).toEqual({ add: ["powershell"] });
});
it("matches hostnames case-insensitively and wins per field over os", () => {
const settings = {
os: { win32: { add: ["powershell"], remove: ["bash"] } },
hosts: { winbox: { remove: ["write"] } },
};
expect(resolveHorstTools(settings, "win32", "WinBox")).toEqual({
add: ["powershell"],
remove: ["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 pi-test-harness", () => {
let t: TestSession | undefined;
afterEach(() => {
t?.dispose();
t = undefined;
});
it("loads in real Pi extension runtime", async () => {
t = await createTestSession({ extensions: [horstPath()] });
expect(
t.session.extensionRunner.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: { add: ["grep", "find"], remove: ["write"] },
},
},
},
}),
);
t = await createTestSession({ cwd: root, extensions: [horstPath()] });
const active = t.session.getActiveToolNames();
expect(active).toContain("grep");
expect(active).toContain("find");
expect(active).not.toContain("write");
} finally {
t?.dispose();
t = undefined;
rmSync(root, { recursive: true, force: true });
}
});
it("injects the stable host section each turn", async () => {
t = await createTestSession({ extensions: [horstPath()] });
const runner = t.session.extensionRunner;
const first = await runner.emitBeforeAgentStart("prompt", undefined, {
cwd: t.cwd,
});
const second = await runner.emitBeforeAgentStart("prompt", undefined, {
cwd: t.cwd,
});
const { horst } = first.systemPromptOptions.sections;
expect(horst).toBe(second.systemPromptOptions.sections.horst);
expect(horst).toContain(`hostname: ${os.hostname()}`);
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 " } }),
);
t = await createTestSession({ cwd: root, extensions: [horstPath()] });
const runner = t.session.extensionRunner;
const first = await runner.emitBeforeAgentStart("prompt", undefined, {
cwd: t.cwd,
});
const second = await runner.emitBeforeAgentStart("prompt", undefined, {
cwd: t.cwd,
});
expect(first.systemPromptOptions.sections.horst).toContain(
"preferred shell: fish",
);
expect(second.systemPromptOptions.sections.horst).toBe(
first.systemPromptOptions.sections.horst,
);
} finally {
t?.dispose();
t = undefined;
rmSync(root, { recursive: true, force: true });
}
});
});