import { describe, expect, mock, test } from "bun:test"; import type { BranchProtection } from "./protection"; mock.module("cloudflare:workers", () => ({ env: {}, waitUntil: () => undefined, WorkflowEntrypoint: class {}, DurableObject: class {}, WorkerEntrypoint: class {}, })); const { tagUpdateViolation } = await import("./protection"); const { protectionVerdicts } = await import("./git/proxy"); const ZERO = "0".repeat(40); const A = "a".repeat(40); const B = "b".repeat(40); function rule(overrides: Partial = {}): BranchProtection { return { id: "r1", pattern: "refs/tags/v*", requirePullRequest: false, requiredApprovals: 0, requiredChecks: [], dismissStaleReviews: false, allowDeletions: false, allowForcePushes: false, enforceAdmins: false, requireCodeOwnerReview: false, ...overrides, }; } describe("tagUpdateViolation (GIT-A3 / GIT-A6)", () => { test("no rule means no restriction", () => { expect(tagUpdateViolation(null, "write", "create")).toBeNull(); expect(tagUpdateViolation(null, "write", "delete")).toBeNull(); }); test("writers cannot create, move or delete tags under a strict rule", () => { expect(tagUpdateViolation(rule(), "write", "create")).toMatch(/only administrators/); expect(tagUpdateViolation(rule(), "write", "move")).toMatch(/cannot be moved/); expect(tagUpdateViolation(rule(), "write", "delete")).toMatch(/deletion is not allowed/); }); test("allow_deletions permits delete but not the recreate half of a move", () => { const deletable = rule({ allowDeletions: true }); expect(tagUpdateViolation(deletable, "write", "delete")).toBeNull(); expect(tagUpdateViolation(deletable, "write", "create")).not.toBeNull(); }); test("allow_force_pushes permits creation and moves", () => { const movable = rule({ allowForcePushes: true }); expect(tagUpdateViolation(movable, "write", "create")).toBeNull(); expect(tagUpdateViolation(movable, "write", "move")).toBeNull(); expect(tagUpdateViolation(movable, "write", "delete")).not.toBeNull(); }); test("admins bypass unless enforce_admins", () => { expect(tagUpdateViolation(rule(), "admin", "create")).toBeNull(); expect(tagUpdateViolation(rule({ enforceAdmins: true }), "admin", "create")).not.toBeNull(); }); }); describe("protectionVerdicts (GIT-A5 / GIT-A6)", () => { test("an admin bound by enforce_admins may not pass an unverifiable fast-forward check", () => { const strict = rule({ pattern: "main", enforceAdmins: true }); const verdicts = protectionVerdicts([strict], "admin", [{ ref: "refs/heads/main", oldSha: A, newSha: B }]); expect(verdicts.violations.size).toBe(0); expect(verdicts.needsFastForward).toHaveLength(1); expect(verdicts.allowUnverifiable).toBe(false); }); test("an admin not bound by any rule bypasses the fast-forward check entirely", () => { const lax = rule({ pattern: "main" }); const verdicts = protectionVerdicts([lax], "admin", [{ ref: "refs/heads/main", oldSha: A, newSha: B }]); expect(verdicts.needsFastForward).toHaveLength(0); expect(verdicts.allowUnverifiable).toBe(true); }); test("writers never get unverifiable leniency", () => { const verdicts = protectionVerdicts([rule({ pattern: "main" })], "write", [ { ref: "refs/heads/main", oldSha: A, newSha: B }, ]); expect(verdicts.allowUnverifiable).toBe(false); }); test("tag creation under a tag rule is refused for writers", () => { const verdicts = protectionVerdicts([rule()], "write", [{ ref: "refs/tags/v9.9.9", oldSha: ZERO, newSha: A }]); expect(verdicts.violations.get("refs/tags/v9.9.9")).toMatch(/only administrators/); }); test("tag deletion follows allow_deletions and moves follow allow_force_pushes", () => { const rules = [rule({ allowDeletions: true })]; const verdicts = protectionVerdicts(rules, "write", [ { ref: "refs/tags/v1", oldSha: A, newSha: ZERO }, { ref: "refs/tags/v2", oldSha: A, newSha: B }, ]); expect(verdicts.violations.has("refs/tags/v1")).toBe(false); expect(verdicts.violations.get("refs/tags/v2")).toMatch(/cannot be moved/); }); });