Vectrum
Sign In
120 lines · 6.7 KB · TypeScript
// 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);
});