Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

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

Raw
import { spawnSync } from "node:child_process";
import {
	chmodSync,
	copyFileSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { applyGitPolicy, createGitPolicyRuntime } from "../git-policy";

const testDirs: string[] = [];
afterEach(() => {
	for (const dir of testDirs.splice(0))
		rmSync(dir, { recursive: true, force: true });
});

describe("git-safe agent Git policy", () => {
	it("generates and reuses a wrapper with fixed executables", async () => {
		const bin = mkdtempSync(path.join(tmpdir(), "pi-git-policy-bin-"));
		testDirs.push(bin);
		const git = path.join(
			bin,
			process.platform === "win32" ? "git.exe" : "git",
		);
		if (process.platform === "win32") copyFileSync(process.execPath, git);
		else {
			writeFileSync(git, "#!/bin/sh\nexit 0\n", "utf8");
			chmodSync(git, 0o755);
		}

		const first = await createGitPolicyRuntime({ PATH: bin }, process.platform);
		const second = await createGitPolicyRuntime(
			{ PATH: bin },
			process.platform,
		);
		const wrapper = readFileSync(
			path.join(first.wrapperDir, "git-wrapper.cjs"),
			"utf8",
		);

		expect(second).toBe(first);
		expect(wrapper).toContain(JSON.stringify(first.realGit));
		expect(wrapper).not.toContain("__PI_GIT_SAFE_REAL_GIT__");
	});

	it.runIf(process.platform === "win32")(
		"launches when callers disable MSYS argument conversion",
		async () => {
			const bin = mkdtempSync(path.join(tmpdir(), "pi-git-policy-bin-"));
			testDirs.push(bin);
			copyFileSync(process.execPath, path.join(bin, "git.exe"));
			const runtime = await createGitPolicyRuntime({ PATH: bin }, "win32");
			const launcher = path
				.join(runtime.wrapperDir, "git")
				.replaceAll("\\", "/")
				.replace(
					/^([A-Za-z]):\//u,
					(_match, drive: string) => `/${drive.toLowerCase()}/`,
				);
			const result = spawnSync(
				"bash",
				["-lc", `MSYS2_ARG_CONV_EXCL='*' '${launcher}' --version`],
				{ encoding: "utf8" },
			);
			expect(result.status, result.stderr).toBe(0);
			expect(result.stdout.trim()).toBe(process.version);

			rmSync(path.join(runtime.wrapperDir, "git-wrapper.cjs"));
			const unavailable = spawnSync(
				"bash",
				["-lc", `'${launcher}' --version`],
				{
					encoding: "utf8",
				},
			);
			expect(unavailable.status).toBe(127);
			expect(unavailable.stderr.trim()).toBe(
				"git: command unavailable; ask the user to restart Pi",
			);
		},
	);

	it("routes Git, command, and env through the executable wrapper", () => {
		const runtime = { wrapperDir: "/tmp/git policy", realGit: "/usr/bin/git" };
		const wrapped = applyGitPolicy("git status", runtime);

		expect(wrapped).toContain("'/tmp/git policy'");
		expect(wrapped).toContain("'/tmp/git policy/git'");
		expect(wrapped).not.toContain("PI_GIT_SAFE_REAL_GIT");
		expect(wrapped).not.toContain("PI_GIT_SAFE_NODE");
		expect(applyGitPolicy(wrapped, runtime)).not.toBe(wrapped);
	});

	it("converts Windows paths for Git Bash", () => {
		const wrapped = applyGitPolicy("git status", {
			wrapperDir: "C:\\Users\\me\\AppData\\Local\\Temp\\git-safe",
			realGit: "C:\\Program Files\\Git\\cmd\\git.exe",
		});

		expect(wrapped).toContain(
			"export PATH='/c/Users/me/AppData/Local/Temp/git-safe':\"$PATH\"",
		);
		expect(wrapped).toContain(
			"'/c/Users/me/AppData/Local/Temp/git-safe/git' \"$@\"",
		);
	});

	it("does not trust an agent-supplied policy marker", () => {
		const command = "# pi-git-safe-policy-v1\ngit status";
		const wrapped = applyGitPolicy(command, {
			wrapperDir: "/tmp/wrapper",
			realGit: "/usr/bin/git",
		});

		expect(wrapped).not.toBe(command);
		expect(wrapped.endsWith(command)).toBe(true);
	});
});