Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

required-checks.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

required-checks.test.tsBlame128 lines · 1 contributor
a79a9edClaude1/**
2 * Block E6 — Required status checks matrix tests.
3 *
4 * Covers the pure protection-evaluator path (no DB) + route auth smoke.
5 */
6
7import { describe, it, expect } from "bun:test";
8import app from "../app";
9import { evaluateProtection } from "../lib/branch-protection";
10import type { BranchProtection } from "../db/schema";
11
12function rule(overrides: Partial<BranchProtection> = {}): BranchProtection {
13 return {
14 id: "rule-1",
15 repositoryId: "repo-1",
16 pattern: "main",
17 requirePullRequest: true,
18 requireGreenGates: true,
19 requireAiApproval: false,
20 requireHumanReview: false,
21 requiredApprovals: 0,
22 allowForcePush: false,
23 allowDeletion: false,
24 createdAt: new Date(),
25 updatedAt: new Date(),
26 ...overrides,
27 } as BranchProtection;
28}
29
30describe("evaluateProtection — required checks matrix", () => {
31 const green = {
32 aiApproved: true,
33 humanApprovalCount: 1,
34 gateResultGreen: true,
35 hasFailedGates: false,
36 };
37
38 it("allows when no required checks are configured", () => {
39 const d = evaluateProtection(rule(), green, []);
40 expect(d.allowed).toBe(true);
41 expect(d.reasons.length).toBe(0);
42 expect(d.missingChecks).toBeUndefined();
43 });
44
45 it("allows when all required checks are in passingCheckNames", () => {
46 const d = evaluateProtection(
47 rule(),
48 { ...green, passingCheckNames: ["GateTest", "AI Review", "CI"] },
49 ["GateTest", "AI Review"]
50 );
51 expect(d.allowed).toBe(true);
52 expect(d.missingChecks).toBeUndefined();
53 });
54
55 it("blocks when a required check is missing", () => {
56 const d = evaluateProtection(
57 rule(),
58 { ...green, passingCheckNames: ["GateTest"] },
59 ["GateTest", "AI Review"]
60 );
61 expect(d.allowed).toBe(false);
62 expect(d.missingChecks).toEqual(["AI Review"]);
63 expect(d.reasons.join(" ")).toContain("AI Review");
64 });
65
66 it("blocks when passingCheckNames is empty but checks are required", () => {
67 const d = evaluateProtection(
68 rule(),
69 { ...green, passingCheckNames: [] },
70 ["GateTest"]
71 );
72 expect(d.allowed).toBe(false);
73 expect(d.missingChecks).toEqual(["GateTest"]);
74 });
75
76 it("still reports other failures alongside missing checks", () => {
77 const d = evaluateProtection(
78 rule({ requireAiApproval: true, requiredApprovals: 2 }),
79 {
80 aiApproved: false,
81 humanApprovalCount: 0,
82 gateResultGreen: true,
83 hasFailedGates: false,
84 passingCheckNames: [],
85 },
86 ["CI"]
87 );
88 expect(d.allowed).toBe(false);
89 // 3 reasons: AI approval, required approvals, missing check
90 expect(d.reasons.length).toBeGreaterThanOrEqual(3);
91 expect(d.missingChecks).toEqual(["CI"]);
92 });
93});
94
95describe("required-checks — route smoke", () => {
96 it("GET protection/:id/checks without auth → 302 /login", async () => {
97 const res = await app.request(
98 "/any/repo/gates/protection/abc/checks"
99 );
100 expect(res.status).toBe(302);
101 const loc = res.headers.get("location") || "";
102 expect(loc.startsWith("/login")).toBe(true);
103 });
104
105 it("POST protection/:id/checks without auth → 302 /login", async () => {
106 const res = await app.request(
107 "/any/repo/gates/protection/abc/checks",
108 {
109 method: "POST",
110 body: new URLSearchParams({ checkName: "CI" }),
111 headers: { "content-type": "application/x-www-form-urlencoded" },
112 }
113 );
114 expect(res.status).toBe(302);
115 const loc = res.headers.get("location") || "";
116 expect(loc.startsWith("/login")).toBe(true);
117 });
118
119 it("POST protection/:id/checks/:cid/delete without auth → 302 /login", async () => {
120 const res = await app.request(
121 "/any/repo/gates/protection/abc/checks/xyz/delete",
122 { method: "POST" }
123 );
124 expect(res.status).toBe(302);
125 const loc = res.headers.get("location") || "";
126 expect(loc.startsWith("/login")).toBe(true);
127 });
128});