// 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> = {}): BranchRule => ({
id: pattern,
pattern,
requirePr: true,
requiredApprovals: 1,
requireCi: true,
blockForcePush: true,
blockDeletion: true,
agentsMayMerge: false,
createdAt: 0,
...over,
});
const ctx = (over: Partial<PushContext> = {}): 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> = {}): 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<MergeFacts>) => 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);
});