Skip to content

Commit dfbfdf1

Browse files
committed
test(ai-review): add unit tests for isRateLimitError function
Introduces a new test suite for the isRateLimitError function to ensure it correctly identifies rate limit errors (HTTP 429) across various provider error messages. The tests cover matching valid 429 errors, rejecting non-429 errors, and validating the function's behavior with non-Error values. This addition helps prevent future regressions in error handling related to rate limits.
1 parent 68ce986 commit dfbfdf1

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from "vitest";
2+
import { isRateLimitError } from "../../src/services/ai-review";
3+
4+
// Direct contract test for isRateLimitError (#5385-sentry / GITTENSORY-K/8, added in #5481). The helper is the
5+
// SHARED short-circuit that four independent AI-calling retry loops now depend on — runWorkersOpinion +
6+
// runDualAiTieBreakJudgeCall (services/ai-review.ts), planner.ts, ai-slop.ts, and
7+
// linked-issue-satisfaction-run.ts all `break` out of the same-model retry on a 429 instead of burning the
8+
// remaining per-model budget on a rate-limit window that will not have cleared a few hundred ms later. It
9+
// shipped wired into those loops but with no test of its own; this pins the exact set of error shapes that count
10+
// as a rate limit so a future tweak to the `/_(?:http|error)_429$/` matcher can't silently regress the
11+
// short-circuit across every one of those call sites at once.
12+
13+
describe("isRateLimitError — the shared 429 retry short-circuit (#5481)", () => {
14+
it("matches every provider 429 shape src/selfhost/ai.ts actually throws", () => {
15+
// ai.ts line 344 `ai_http_${status}`, line 392 `anthropic_http_${status}`, line 990
16+
// `claude_code_error_${errStatus}`, and the embeddings path's `ai_embed_http_${status}`.
17+
for (const message of ["ai_http_429", "anthropic_http_429", "claude_code_error_429", "ai_embed_http_429"]) {
18+
expect(isRateLimitError(new Error(message))).toBe(true);
19+
}
20+
});
21+
22+
it("does NOT match a non-429 provider status (a 4xx/5xx that a retry might legitimately clear)", () => {
23+
for (const message of ["claude_code_error_404", "ai_http_400", "anthropic_http_500", "ai_http_503"]) {
24+
expect(isRateLimitError(new Error(message))).toBe(false);
25+
}
26+
});
27+
28+
it("does NOT match the sibling non-transient error that has its own separate short-circuit", () => {
29+
// runWorkersOpinion breaks on `isSubscriptionCliTimeout(error) || isRateLimitError(error)` — the two guards
30+
// are distinct, so a CLI timeout must not be absorbed by the 429 matcher.
31+
expect(isRateLimitError(new Error("subscription_cli_timeout"))).toBe(false);
32+
expect(isRateLimitError(new Error("codex_exit_1: unknown model"))).toBe(false);
33+
expect(isRateLimitError(new Error("claude_code_empty_output"))).toBe(false);
34+
});
35+
36+
it("anchors on 429 as the FULL suffix — a longer status or a trailing detail is not a 429", () => {
37+
// The `$` anchor is deliberate: only a bare `..._http_429` / `..._error_429` is the rate-limit signal.
38+
expect(isRateLimitError(new Error("ai_http_4291"))).toBe(false);
39+
expect(isRateLimitError(new Error("claude_code_error_429_retryable"))).toBe(false);
40+
expect(isRateLimitError(new Error("ai_http_429: too many requests"))).toBe(false);
41+
});
42+
43+
it("requires the `_http`/`_error` delimiter, so a bare '429' substring never trips it", () => {
44+
expect(isRateLimitError(new Error("http_429"))).toBe(false); // no leading `_` before `http`
45+
expect(isRateLimitError(new Error("429"))).toBe(false);
46+
expect(isRateLimitError(new Error("rate limited (429)"))).toBe(false);
47+
});
48+
49+
it("is false for any non-Error value (the `error instanceof Error` guard)", () => {
50+
expect(isRateLimitError("ai_http_429")).toBe(false); // a matching string, but not an Error instance
51+
expect(isRateLimitError(null)).toBe(false);
52+
expect(isRateLimitError(undefined)).toBe(false);
53+
expect(isRateLimitError(429)).toBe(false);
54+
expect(isRateLimitError({ message: "ai_http_429" })).toBe(false);
55+
});
56+
});

0 commit comments

Comments
 (0)