CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
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 | /**
* Tests for src/lib/ai-review.ts.
*
* The Anthropic-calling path (reviewDiff) and the DB-touching paths
* (triggerAiReview's idempotency probe + insert) require external
* services we don't stand up in unit tests. We therefore cover the
* pure invariants:
*
* - The marker constant is stable so future searches keep working.
* - isAiReviewEnabled is a boolean reflecting config.anthropicApiKey.
* - triggerAiReview is a no-op (resolves cleanly) when the API key
* is absent — this is the documented graceful-degrade contract.
* - triggerAiReview never throws even when called with garbage.
* - The internal __test helpers are exported and shaped correctly.
*/
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", () => {
// Important: any change to this string is a breaking change for
// idempotency. New version → write a migration to back-fill old
// markers so older AI summaries still suppress duplicates.
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", () => {
// Note: in the test sandbox ANTHROPIC_API_KEY is unset, so the
// function should resolve immediately without touching the DB. We
// also pass garbage repo names + nonexistent PR ids to confirm the
// overall try/catch holds.
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", () => {
// Regression coverage for a real bug: three call sites (the MCP merge
// tool, the PR-page gate preview, and the web merge handler) each had
// their own ad-hoc heuristic for "did AI review approve this", and none
// of them matched the literal verdict strings triggerAiReview actually
// writes -- so the MCP path in particular hard-blocked every merge the
// instant AI review ran at all, clean or not. Reproduced live via
// scripts/agent-journey.ts against production.
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");
expect(out).toBe(false);
});
});
|