repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/horst/__tests__/settings.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 } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
const extensionPath = path.resolve(import.meta.dirname, "../index.ts");
const host = os.hostname().toLowerCase();
type Case = { user?: unknown; project?: unknown; untrusted?: boolean };
describe("horst settings resolution", () => {
let t: TestSession | undefined;
let root: string | undefined;
afterEach(() => {
t?.dispose();
t = undefined;
if (root) rmSync(root, { recursive: true, force: true });
root = undefined;
});
async function start(input: Case) {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-horst-settings-"));
const write = (dir: string, value: unknown) => {
mkdirSync(dir, { recursive: true });
writeFileSync(
path.join(dir, "settings.json"),
JSON.stringify({ horst: value }),
);
};
if ("user" in input)
write(path.join(root, ".test-home/.pi/agent"), input.user);
if ("project" in input) write(path.join(root, ".pi"), input.project);
t = await createTestSession({ cwd: root, extensions: [extensionPath] });
const runner = t.session.extensionRunner;
if (input.untrusted) {
t.session.settingsManager.setProjectTrusted(false);
await runner.emit({ type: "session_start", reason: "startup" });
}
const { horst } = (
await runner.emitBeforeAgentStart("prompt", undefined, { cwd: t.cwd })
).systemPromptOptions.sections;
const warnings = t.events
.uiCallsFor("notify")
.filter((call) => call.args[1] === "warning")
.map((call) => call.args[0]);
return {
shell: /preferred shell: (.*)/.exec(horst ?? "")?.[1],
active: t.session.getActiveToolNames(),
warnings: [...new Set(warnings)],
};
}
it.each([
["default", {}, undefined],
["user", { user: { preferredShell: "nu" } }, "nu"],
[
"project over user",
{ user: { preferredShell: "nu" }, project: { preferredShell: "fish" } },
"fish",
],
[
"user kept when project omits key",
{ user: { preferredShell: "nu" }, project: { hosts: {} } },
"nu",
],
[
"untrusted project ignored",
{
user: { preferredShell: "nu" },
project: { preferredShell: "fish" },
untrusted: true,
},
"nu",
],
[
"untrusted project ignored without user",
{ project: { preferredShell: "fish" }, untrusted: true },
undefined,
],
] satisfies [string, Case, string | undefined][])(
"preferredShell: %s",
async (_name, input, expected) => {
const { shell, warnings } = await start(input);
expect(shell).toBe(expected);
expect(warnings).toEqual([]);
},
);
it("deep-merges host entries across user and project", async () => {
const { active, warnings } = await start({
user: { hosts: { [host]: { tools: { add: ["grep"] } } } },
project: { hosts: { [host]: { tools: { remove: ["write"] } } } },
});
expect(active).toContain("grep");
expect(active).not.toContain("write");
expect(warnings).toEqual([]);
});
it("replaces tool arrays instead of merging them", async () => {
const { active } = await start({
user: { hosts: { [host]: { tools: { add: ["grep"] } } } },
project: { hosts: { [host]: { tools: { add: ["find"] } } } },
});
expect(active).toContain("find");
expect(active).not.toContain("grep");
});
it.each([
[
"invalid project",
{
user: {
preferredShell: "nu",
hosts: { [host]: { tools: { add: ["grep"] } } },
},
project: { preferredShell: 42 },
},
"horst: Invalid horst from merged project and user settings: preferredShell must be a non-empty string; settings ignored",
],
[
"non-object project",
{ user: { preferredShell: "nu" }, project: "nu" },
"horst: Invalid horst from project settings: expected an object; settings ignored",
],
[
"invalid user",
{ user: { hosts: { [host]: { tools: { add: "grep" } } } } },
`horst: Invalid horst from user settings: hosts.${host}.tools.add must be an array of non-empty strings; settings ignored`,
],
] satisfies [string, Case, string][])(
"rejects %s without falling through",
async (_name, input, warning) => {
const { shell, active, warnings } = await start(input);
expect(shell).toBeUndefined();
expect(active).not.toContain("grep");
expect(warnings).toEqual([warning]);
},
);
});