Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/git-safe/__tests__/settings.test.ts

Raw
import { 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`,
			});
		},
	);
});