// Run with `bun test`. import assert from "node:assert/strict"; import { test } from "node:test"; import { agentKeyFromEmail, agentCommitter, appAgentKey, isAgentKey, parseAppAgentKey, tokenAgentKey } from "./actor"; import { agentMayWrite, canMerge, isValidPattern, linkedIssueNumbers, mergeRequirements, protectionFor, refusal, type BranchRule, type MergeFacts, type PushContext } from "./branch-rules"; const rule = (pattern: string, over: Partial = {}): BranchRule => ({ id: pattern, pattern, requirePr: true, requiredApprovals: 1, requireCi: true, blockForcePush: true, blockDeletion: true, agentsMayMerge: false, createdAt: 0, ...over, }); const ctx = (over: Partial = {}): PushContext => ({ rules: [rule("main"), rule("release/*", { requirePr: false, requiredApprovals: 0 })], agentPush: "agent_branches", agent: false, empty: false, ...over }); const update = (ref: string, over: { old?: string | null; new?: string | null; fastForward?: boolean } = {}) => ({ ref, old: "a".repeat(40), new: "b".repeat(40), fastForward: true, ...over }); test("rules match by name and glob, and combine to the strictest", () => { const rules = [rule("main", { requiredApprovals: 1 }), rule("**", { requirePr: false, requiredApprovals: 2, agentsMayMerge: true })]; const p = protectionFor(rules, "main")!; assert.equal(p.requiredApprovals, 2); assert.equal(p.requirePr, true); assert.equal(p.agentsMayMerge, false); assert.equal(protectionFor([rule("release/*")], "release/1.0")?.patterns[0], "release/*"); assert.equal(protectionFor([rule("release/*")], "release/1/2"), null); assert.equal(protectionFor([rule("release/**")], "release/1/2")?.patterns[0], "release/**"); assert.equal(protectionFor([rule("main")], "feature"), null); }); test("patterns", () => { for (const ok of ["main", "release/*", "**", "feat/**/x", "v1.x"]) assert.equal(isValidPattern(ok), true, ok); for (const bad of ["", "/main", "-x", "a//b", "a b", "x".repeat(256)]) assert.equal(isValidPattern(bad), false, bad); }); test("people: protected branches change through pull requests, can't be force-pushed or deleted", () => { assert.match(refusal(ctx(), update("refs/heads/main"))!, /pull requests/); assert.equal(refusal(ctx({ merge: true }), update("refs/heads/main")), null); assert.equal(refusal(ctx(), update("refs/heads/feature")), null); assert.match(refusal(ctx(), update("refs/heads/release/1", { fastForward: false }))!, /force pushes/); assert.equal(refusal(ctx(), update("refs/heads/release/1")), null); assert.match(refusal(ctx(), update("refs/heads/main", { new: null }))!, /can't be deleted/); // Creating a protected branch is allowed, and so is the first push of an empty repository. assert.equal(refusal(ctx(), update("refs/heads/release/2", { old: null })), null); assert.equal(refusal(ctx({ empty: true }), update("refs/heads/main", { old: null })), null); assert.equal(refusal(ctx(), update("refs/tags/v1", { old: null })), null); // refs/pull/ holds forks' commits for their pull requests: nobody pushes there. assert.match(refusal(ctx(), update("refs/pull/3/head", { old: null }))!, /refs\/pull\//); assert.match(refusal(ctx(), update("refs/pull/3/head", { new: null }))!, /refs\/pull\//); }); test("agents: only agent/ branches by default, and no moving tags", () => { const agent = ctx({ agent: true }); assert.match(refusal(agent, update("refs/heads/feature"))!, /agent\//); assert.match(refusal(agent, update("refs/heads/main", { old: null }))!, /agent\//); assert.equal(refusal(agent, update("refs/heads/agent/fix", { old: null })), null); assert.equal(refusal(agent, update("refs/heads/agent/fix", { new: null })), null); assert.equal(refusal(agent, update("refs/tags/v1", { old: null })), null); assert.match(refusal(agent, update("refs/tags/v1"))!, /tags/); assert.equal(refusal(ctx({ agent: true, agentPush: "unprotected" }), update("refs/heads/feature")), null); assert.match(refusal(ctx({ agent: true, agentPush: "unprotected" }), update("refs/heads/release/1"))!, /no branch rule/); assert.match(refusal(ctx({ agent: true, agentPush: "any" }), update("refs/heads/main"))!, /pull requests/); // A rule can let agents merge where they can't push. assert.match(refusal(ctx({ agent: true, merge: true }), update("refs/heads/main"))!, /agent\//); assert.equal(refusal(ctx({ agent: true, merge: true, rules: [rule("main", { agentsMayMerge: true })] }), update("refs/heads/main")), null); assert.equal(agentMayWrite("agent_branches", [], "agent/"), false); }); const facts = (over: Partial = {}): MergeFacts => ({ state: "open", draft: false, headExists: true, ahead: 2, conflicts: [], checks: "success", approvals: 1, changesRequestedBy: [], protection: protectionFor([rule("main")], "main"), byAgent: false, agentMayWriteBase: false, ...over, }); test("merge requirements", () => { assert.equal(canMerge(mergeRequirements(facts())), true); const blocked = (over: Partial) => mergeRequirements(facts(over)).filter((r) => !r.ok).map((r) => r.key); assert.deepEqual(blocked({ approvals: 0 }), ["approvals"]); assert.deepEqual(blocked({ checks: "pending" }), ["checks"]); assert.deepEqual(blocked({ checks: "none" }), []); assert.deepEqual(blocked({ checks: "failure", protection: null, approvals: 0 }), []); assert.deepEqual(blocked({ conflicts: ["a.ts"] }), ["conflicts"]); assert.deepEqual(blocked({ draft: true }), ["draft"]); assert.deepEqual(blocked({ ahead: 0 }), ["commits"]); assert.deepEqual(blocked({ changesRequestedBy: ["bob"] }), ["changes"]); assert.deepEqual(blocked({ byAgent: true }), ["agent"]); assert.deepEqual(blocked({ byAgent: true, agentMayWriteBase: true }), []); assert.deepEqual(blocked({ byAgent: true, protection: protectionFor([rule("main", { agentsMayMerge: true })], "main") }), []); assert.deepEqual(blocked({ state: "merged" }), ["open"]); }); test("linked issues", () => { assert.deepEqual(linkedIssueNumbers("Fixes #12", "closes #3, and Resolves: #4. See #5. fixed #12"), [3, 4, 12]); assert.deepEqual(linkedIssueNumbers("prefix#1 fix#2 fixes#3"), []); }); test("agent keys and committer addresses", () => { const key = appAgentKey("abcdefghijklmno", "pqrstuvwxyz0123"); assert.equal(isAgentKey(key), true); assert.equal(isAgentKey(tokenAgentKey("abcdefghijklmno")), true); assert.equal(isAgentKey("abcdefghijklmno"), false); assert.deepEqual(parseAppAgentKey(key), { clientId: "abcdefghijklmno", userId: "pqrstuvwxyz0123" }); const committer = agentCommitter({ key, name: "Claude Code" }); assert.equal(committer.name, "Claude Code"); assert.equal(agentKeyFromEmail(committer.email), key); assert.equal(agentKeyFromEmail("alice@example.com"), null); });