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

push-policy.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.

push-policy.test.tsBlame123 lines · 1 contributor
5dc83edClaude1/**
2 * Tests for src/lib/push-policy.ts.
3 *
4 * The DB-touching paths (matchProtectedTag, listRulesetsForRepo,
5 * canBypassProtectedTag) require a live DB; we don't have one in unit
6 * tests. Instead we cover:
7 *
8 * - The pure formatPolicyError formatter — exhaustive.
9 * - The fail-open guarantee on bad input (empty refs, missing repo id).
10 * - Module shape (imports don't throw, expected exports are present).
11 *
12 * Real end-to-end coverage of the policy decisions runs in the existing
13 * rulesets + protected-tags test suites (which exercise evaluatePush and
14 * matchGlob directly). This file verifies that the new wrapper preserves
15 * fail-open semantics, which is the property that protects production
16 * pushes from a Postgres hiccup.
17 */
18
19import { describe, it, expect } from "bun:test";
20import {
21 evaluatePushPolicy,
22 formatPolicyError,
23 ZERO_SHA,
24} from "../lib/push-policy";
25
26describe("ZERO_SHA constant", () => {
27 it("is exactly 40 zeros", () => {
28 expect(ZERO_SHA).toBe("0000000000000000000000000000000000000000");
29 expect(ZERO_SHA.length).toBe(40);
30 });
31});
32
33describe("formatPolicyError", () => {
34 it("returns a generic message when violations is empty", () => {
35 expect(formatPolicyError([])).toBe("Push rejected by Gluecron policy.");
36 });
37
38 it("returns a generic message when violations is null/undefined", () => {
39 expect(formatPolicyError(null as any)).toBe(
40 "Push rejected by Gluecron policy."
41 );
42 expect(formatPolicyError(undefined as any)).toBe(
43 "Push rejected by Gluecron policy."
44 );
45 });
46
47 it("renders one violation as a bulleted list", () => {
48 const out = formatPolicyError(['tag "v1.0" is protected']);
49 expect(out).toContain("Push rejected by Gluecron policy:");
50 expect(out).toContain(' - tag "v1.0" is protected');
51 });
52
53 it("renders multiple violations on separate lines", () => {
54 const out = formatPolicyError([
55 'tag "v1.0" is protected',
56 'ruleset "no-prod-pushes" rule branch_name_pattern: blocked',
57 ]);
58 const lines = out.trimEnd().split("\n");
59 expect(lines[0]).toBe("Push rejected by Gluecron policy:");
60 expect(lines[1]).toBe(' - tag "v1.0" is protected');
61 expect(lines[2]).toBe(
62 ' - ruleset "no-prod-pushes" rule branch_name_pattern: blocked'
63 );
64 });
65
66 it("ends with a newline (so git surfaces the body cleanly)", () => {
67 expect(formatPolicyError(["one violation"]).endsWith("\n")).toBe(true);
68 });
69});
70
71describe("evaluatePushPolicy — fail-open on bad input", () => {
72 it("returns allowed=true for an empty refs list", async () => {
73 const r = await evaluatePushPolicy({
74 repositoryId: "repo-1",
75 refs: [],
76 pusherUserId: null,
77 });
78 expect(r.allowed).toBe(true);
79 expect(r.violations).toEqual([]);
80 });
81
82 it("returns allowed=true for missing repositoryId", async () => {
83 const r = await evaluatePushPolicy({
84 repositoryId: "" as any,
85 refs: [
86 {
87 oldSha: ZERO_SHA,
88 newSha: "a".repeat(40),
89 refName: "refs/tags/v1.0",
90 },
91 ],
92 pusherUserId: null,
93 });
94 expect(r.allowed).toBe(true);
95 });
96
97 it("returns allowed=true when DB is unreachable (refs target a nonexistent repo)", async () => {
98 // No real repo exists with id "definitely-not-a-real-repo-id"; the
99 // matchProtectedTag + listRulesetsForRepo callers catch their own
100 // errors and return empty arrays, so the wrapper returns allowed.
101 const r = await evaluatePushPolicy({
102 repositoryId: "definitely-not-a-real-repo-id",
103 refs: [
104 {
105 oldSha: ZERO_SHA,
106 newSha: "a".repeat(40),
107 refName: "refs/heads/main",
108 },
109 ],
110 pusherUserId: null,
111 });
112 expect(r.allowed).toBe(true);
113 });
114});
115
116describe("evaluatePushPolicy — module shape", () => {
117 it("exports the functions the route depends on", async () => {
118 const mod = await import("../lib/push-policy");
119 expect(typeof mod.evaluatePushPolicy).toBe("function");
120 expect(typeof mod.formatPolicyError).toBe("function");
121 expect(typeof mod.ZERO_SHA).toBe("string");
122 });
123});