Files
OmniRoute/tests/unit/opencode-transient-failure-predicate.test.ts
Dizzle cfa2fc7548 fix(sse): rotate opencode accounts on transient 5xx (#12975)
Reconciled and merged. This branch was stacked on #12941, which has since landed, so it read as 1484 additions across 16 files and CONFLICTING. I merged the current `release/v3.8.51` into it rather than rewriting your branch: `open-sse/executors/opencode.ts` conflicted in six places where your side was a strict superset of the squashed #12941, and the tip had touched that file through nothing but #12941, so your side was taken whole. The PR now reads as its real 12 files, +839/-25.

The change itself is right: a flapping upstream 5xx aborting the whole agentic chain is exactly the case where rotating to the next healthy account is safe, and keeping it a separate arm from the 400-empty branch matters because that one has to clone-read the body while this one never touches it. Threading `correlationId` through so interleaved requests stay attributable — and never fabricating one when absent — is the right discipline.

Both geo-block regression suites pass alongside the new ones (43/43 across the five opencode test files), which is what proves the conflict resolution preserved #12941's behaviour.

I also tightened the batch's file-size rebaseline here: `src/sse/handlers/chat.ts` needed no bump at all (it lands at 2452, under its existing 2458 freeze) and `open-sse/executors/base.ts` needed only your +2. An earlier measurement had included a local prettier reformat that is not part of this branch.

---

Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the rest of this batch — zero conflicts between them.

- `typecheck:core` clean; `check:changelog-integrity` OK
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 86 focused assertions green across the batch's 10 unit test files, plus 16/16 on the v1 plugin option schema and 16/16 on the v2 option tests
- `check-file-size` rebaselined for this batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode`, landed on #13141). `open-sse/utils/stream.ts` was deliberately left frozen: it is already 3115 > 3098 on the pure tip with zero contribution from this batch.

⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and the `stream.ts` freeze above). None of them touch these diffs.

Thanks @maxmad64bis.
2026-09-11 13:57:28 -03:00

37 lines
1.6 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert";
import { isRetriableUpstreamFailure } from "../../open-sse/executors/opencodeTransientFailure.ts";
const EMPTY_400_BODY = JSON.stringify({
id: "chatcmpl-abc123",
choices: [{ message: {}, finish_reason: null }],
});
const REAL_400_BODY = JSON.stringify({ error: { message: "bad request" } });
describe("isRetriableUpstreamFailure", () => {
it("matches 500/502/503/504 by status alone, no body needed", () => {
assert.strictEqual(isRetriableUpstreamFailure(500), true);
assert.strictEqual(isRetriableUpstreamFailure(502), true);
assert.strictEqual(isRetriableUpstreamFailure(503), true);
assert.strictEqual(isRetriableUpstreamFailure(504), true);
});
it("matches 500 even with a body present (status short-circuits first)", () => {
assert.strictEqual(isRetriableUpstreamFailure(500, "Internal server error"), true);
});
it("matches empty 400 with body", () => {
assert.strictEqual(isRetriableUpstreamFailure(400, EMPTY_400_BODY), true);
});
it("rejects real-error 400", () => {
assert.strictEqual(isRetriableUpstreamFailure(400, REAL_400_BODY), false);
});
it("rejects 400 without body (absent = non-empty = no retry)", () => {
assert.strictEqual(isRetriableUpstreamFailure(400), false);
assert.strictEqual(isRetriableUpstreamFailure(400, ""), false);
});
it("rejects 403/429/200", () => {
assert.strictEqual(isRetriableUpstreamFailure(403), false);
assert.strictEqual(isRetriableUpstreamFailure(429), false);
assert.strictEqual(isRetriableUpstreamFailure(200), false);
});
});