repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/git-safe/__tests__/settings.test.ts
Rawimport { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { createTestSession, type TestSession } from "../../../test/harness";
const extensionPath = path.resolve(import.meta.dirname, "../index.ts");
type Limits = Record<string, unknown>;
type Case = {
user?: Limits;
project?: Limits;
env?: Record<string, string>;
untrusted?: boolean;
};
describe("git-safe limit settings", () => {
let t: TestSession | undefined;
let root: string | undefined;
afterEach(async () => {
await t?.session.extensionRunner.emit({
type: "session_shutdown",
reason: "quit",
});
t?.dispose();
t = undefined;
if (root) rmSync(root, { recursive: true, force: true });
root = undefined;
});
// Clones a local three-file repository and returns details or the error message.
async function clone(input: Case) {
root = mkdtempSync(path.join(tmpdir(), "pi-ext-git-safe-settings-"));
const source = path.join(root, "source");
mkdirSync(source);
for (const name of ["a", "b", "c"])
writeFileSync(path.join(source, name), name);
const git = (...args: string[]) =>
execFileSync("git", args, { cwd: source, stdio: "ignore" });
git("init", "-q");
git("add", ".");
git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", "init");
const write = (dir: string, value: Limits) => {
mkdirSync(dir, { recursive: true });
writeFileSync(
path.join(dir, "settings.json"),
JSON.stringify({ "git-safe": value }),
);
};
if (input.user) write(path.join(root, ".test-home/.pi/agent"), input.user);
if (input.project) write(path.join(root, ".pi"), input.project);
t = await createTestSession({
cwd: root,
env: input.env,
extensions: [extensionPath],
});
if (input.untrusted) t.session.settingsManager.setProjectTrusted(false);
const runner = t.session.extensionRunner;
const tool = runner.extensions[0]?.tools.get("git_clone_safe")?.definition;
if (!tool) throw new Error("missing git_clone_safe");
try {
const result = await tool.execute(
"id",
{ url: pathToFileURL(source).href, path: "clones/repo" } as never,
undefined,
undefined,
runner.createContext(),
);
return { fileCount: (result.details as { fileCount: number }).fileCount };
} catch (error) {
return { error: (error as Error).message };
}
}
const tooMany = "repository has too many files (3 > 2)";
it("default limits allow the clone", async () => {
expect(await clone({})).toEqual({ fileCount: 3 });
});
it.each([
["user fileCountLimit applied", { user: { fileCountLimit: 2 } }, tooMany],
[
"user sizeLimitBytes applied",
{ user: { sizeLimitBytes: 1 } },
"repository content exceeds safety limit (3B > 1B)",
],
[
"user cloneTimeoutMs applied",
{ user: { cloneTimeoutMs: 1 } },
"Clone did not complete within 0.001s",
],
[
"env over user",
{
user: { fileCountLimit: 2 },
env: { PI_GIT_SAFE_FILE_COUNT_LIMIT: "10" },
},
undefined,
],
[
"trusted project over user",
{ user: { fileCountLimit: 2 }, project: { fileCountLimit: 10 } },
undefined,
],
[
"untrusted project ignored",
{
user: { fileCountLimit: 2 },
project: { fileCountLimit: 10 },
untrusted: true,
},
tooMany,
],
] satisfies [string, Case, string | undefined][])(
"%s",
async (_name, input, error) => {
const result = await clone(input);
if (error) expect(result.error).toContain(error);
else expect(result).toEqual({ fileCount: 3 });
},
);
it.each([
[
"wrong type",
{ user: { fileCountLimit: "10" } },
"git-safe.fileCountLimit from user settings",
],
[
"zero over valid user",
{ user: { sizeLimitBytes: 10 }, project: { sizeLimitBytes: 0 } },
"git-safe.sizeLimitBytes from project settings",
],
[
"fraction",
{ user: { cloneTimeoutMs: 1.5 } },
"git-safe.cloneTimeoutMs from user settings",
],
[
"non-numeric env",
{
user: { cloneTimeoutMs: 60_000 },
env: { PI_GIT_SAFE_CLONE_TIMEOUT_MS: "soon" },
},
"git-safe.cloneTimeoutMs from environment variable PI_GIT_SAFE_CLONE_TIMEOUT_MS",
],
] satisfies [string, Case, string][])(
"rejects %s without falling through",
async (_name, input, source) => {
expect(await clone(input)).toEqual({
error: `git_clone_safe failed: Invalid ${source}: expected a positive integer`,
});
},
);
});