1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
import { describe, it, expect } from "bun:test";
import {
AI_REVIEW_MARKER,
isAiReviewEnabled,
triggerAiReview,
computeAiReviewApproval,
__test,
} from "../lib/ai-review";
describe("AI_REVIEW_MARKER", () => {
it("is the documented stable string", () => {
expect(AI_REVIEW_MARKER).toBe("<!-- gluecron-ai-review:summary -->");
});
it("is an HTML comment so it doesn't render in markdown", () => {
expect(AI_REVIEW_MARKER.startsWith("<!--")).toBe(true);
expect(AI_REVIEW_MARKER.endsWith("-->")).toBe(true);
});
});
describe("isAiReviewEnabled", () => {
it("returns a boolean", () => {
const v = isAiReviewEnabled();
expect(typeof v).toBe("boolean");
});
});
describe("triggerAiReview — graceful degrade + crash-free", () => {
it("resolves without throwing when API key is absent", async () => {
const before = process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
try {
await triggerAiReview(
"alice",
"demo",
"00000000-0000-0000-0000-000000000000",
"Test PR",
"Body",
"main",
"feature"
);
} finally {
if (before) process.env.ANTHROPIC_API_KEY = before;
}
expect(true).toBe(true);
});
it("never throws even with invalid inputs", async () => {
let threw = false;
try {
await triggerAiReview(
"",
"",
"not-a-uuid",
"",
"",
"",
""
);
} catch {
threw = true;
}
expect(threw).toBe(false);
});
it("survives an unknown branch combination without throwing", async () => {
let threw = false;
try {
await triggerAiReview(
"definitely-not-a-real-owner",
"definitely-not-a-real-repo",
"00000000-0000-0000-0000-000000000000",
"Title",
"Body",
"definitely-not-a-real-base",
"definitely-not-a-real-head"
);
} catch {
threw = true;
}
expect(threw).toBe(false);
});
});
describe("computeAiReviewApproval — merge-gate decision", () => {
const marker = AI_REVIEW_MARKER;
it("approves when there are no AI comments at all", () => {
expect(computeAiReviewApproval([])).toBe(true);
});
it("approves on the real clean-verdict string", () => {
const body = `${marker}\n## AI Code Review\n\n**AI review:** no blocking issues found.\n\nLooks good.`;
expect(computeAiReviewApproval([{ body, createdAt: new Date() }])).toBe(true);
});
it("blocks on the real flagged-items verdict string", () => {
const body = `${marker}\n## AI Code Review\n\n**AI review:** flagged 2 item(s) for human attention.\n\nSee inline comments.`;
expect(computeAiReviewApproval([{ body, createdAt: new Date() }])).toBe(false);
});
it("fails open on 'AI review unavailable' (provider error, e.g. no API credit)", () => {
const body = `${marker}\n## AI review unavailable\n\nThe AI review attempt failed: 400 credit balance too low. The PR is otherwise unchanged.`;
expect(computeAiReviewApproval([{ body, createdAt: new Date() }])).toBe(true);
});
it("fails open on 'AI review skipped' (quota exhausted)", () => {
const body = `${marker}\n## AI review skipped\n\nYour monthly AI token budget has been reached.`;
expect(computeAiReviewApproval([{ body, createdAt: new Date() }])).toBe(true);
});
it("ignores non-summary AI comments (e.g. inline findings, triage) with no marker", () => {
const inlineFinding = { body: "This line looks risky.", createdAt: new Date() };
expect(computeAiReviewApproval([inlineFinding])).toBe(true);
});
it("uses the most recent summary when a PR was re-reviewed after a push", () => {
const older = {
body: `${marker}\n**AI review:** flagged 1 item(s) for human attention.`,
createdAt: new Date("2026-01-01T00:00:00Z"),
};
const newer = {
body: `${marker}\n**AI review:** no blocking issues found.`,
createdAt: new Date("2026-01-02T00:00:00Z"),
};
expect(computeAiReviewApproval([older, newer])).toBe(true);
expect(computeAiReviewApproval([newer, older])).toBe(true);
});
});
describe("__test internals", () => {
it("exports diffBetweenBranches and alreadyReviewed", () => {
expect(typeof __test.diffBetweenBranches).toBe("function");
expect(typeof __test.alreadyReviewed).toBe("function");
});
it("diffBetweenBranches returns '' for a nonexistent repo", async () => {
const out = await __test.diffBetweenBranches(
"definitely-not-a-real-owner",
"definitely-not-a-real-repo",
"main",
"feature"
);
expect(out).toBe("");
});
it("alreadyReviewed returns false for an unknown PR id (fail-open)", async () => {
const out = await __test.alreadyReviewed(
"00000000-0000-0000-0000-000000000000",
"0000000000000000000000000000000000000000"
);
expect(out).toBe(false);
});
});
|