From e92c8597ebbf3f0afd088fbe67377c27f50ac9c5 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:47:38 -0300 Subject: [PATCH] =?UTF-8?q?fix(ci):=20clear=20the=20release/v3.8.51=20base?= =?UTF-8?q?-reds=20left=20by=20the=2009-15=20batch=20=E2=80=94=20stryker?= =?UTF-8?q?=20coverage,=20CLI=20ready=5Ftimeout=20key,=20paid-target=20fix?= =?UTF-8?q?ture,=20call-log=20traceId,=20Jina=20custom=20prefix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every PR into release/v3.8.51 pushed after #13635/#13678 still failed Fast Quality Gates and all four Unit fast-path shards on the same 16 tests. Each one reproduces on the pure tip; none is a product defect: - mutation-test-coverage: noauth-model-lockout and local-token-budget-429-skips-cooldown (#13606) were missing from stryker.conf.json tap.testFiles. - cli-i18n-catalog: --ready-timeout calls t("serve.ready_timeout") with no catalog entry; added to en, zh-CN and zh-TW (the parity-checked locales). - paid-model-target(-routes)-6540: #13407 removed Together's one-time credit from the free catalog, so "together/..." classifies as unknown and the save-time guard correctly lets it through. Fixture is now gemini/gemini-3.1-pro-preview, plus a precondition test on the fixtures. - attempt-logging-early-keepalive-merge / video-bridge-log-redaction: #13546 keys the call-log row on traceId; baseCtx now defaults traceId to pendingRequestId (same pattern as chatcore-attempt-logging). The keepalive test also moves to the 30s wall-clock poll deadline video-bridge uses. - models-catalog-route: custom Jina rows keep the jina-ai/ prefix; #13403 changed the custom assertion to jina/ (only synced rows use the alias). Refs #12732 --- bin/cli/locales/en.json | 1 + bin/cli/locales/zh-CN.json | 1 + bin/cli/locales/zh-TW.json | 1 + .../12732-attempt-log-tests-trace-id.md | 1 + .../12732-cli-serve-ready-timeout-i18n.md | 1 + .../12732-jina-custom-rows-provider-prefix.md | 1 + .../12732-paid-target-fixture-gemini-pro.md | 1 + ...12732-stryker-noauth-token-budget-coverage.md | 1 + stryker.conf.json | 2 ++ ...attempt-logging-early-keepalive-merge.test.ts | 16 +++++++++++++--- tests/unit/models-catalog-route.test.ts | 6 ++++-- tests/unit/paid-model-target-6540.test.ts | 2 +- tests/unit/paid-model-target-routes-6540.test.ts | 13 +++++++++++-- tests/unit/video-bridge-log-redaction.test.ts | 4 ++++ 14 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 changelog.d/maintenance/12732-attempt-log-tests-trace-id.md create mode 100644 changelog.d/maintenance/12732-cli-serve-ready-timeout-i18n.md create mode 100644 changelog.d/maintenance/12732-jina-custom-rows-provider-prefix.md create mode 100644 changelog.d/maintenance/12732-paid-target-fixture-gemini-pro.md create mode 100644 changelog.d/maintenance/12732-stryker-noauth-token-budget-coverage.md diff --git a/bin/cli/locales/en.json b/bin/cli/locales/en.json index 3ed2f2dbcf..4b37832ba3 100644 --- a/bin/cli/locales/en.json +++ b/bin/cli/locales/en.json @@ -256,6 +256,7 @@ "max_restarts": "Max crash restarts within 30s before giving up (default: 2)", "tray": "Start in the system tray (desktop only, opt-in)", "no_tray": "Disable system tray icon", + "ready_timeout": "Readiness probe timeout in ms (also OMNIROUTE_READY_TIMEOUT_MS, default 60000)", "tls_cert": "Path to a TLS certificate (PEM) to serve HTTPS (also OMNIROUTE_TLS_CERT)", "tls_key": "Path to the TLS private key (PEM) to serve HTTPS (also OMNIROUTE_TLS_KEY)" }, diff --git a/bin/cli/locales/zh-CN.json b/bin/cli/locales/zh-CN.json index 31be9d4c16..d3f1ab718b 100644 --- a/bin/cli/locales/zh-CN.json +++ b/bin/cli/locales/zh-CN.json @@ -254,6 +254,7 @@ "max_restarts": "30 秒内的最大崩溃重启次数(默认:2)", "tray": "显示系统托盘图标(仅桌面,选择加入)", "no_tray": "禁用系统托盘图标", + "ready_timeout": "就绪探测超时(毫秒)(也可用 OMNIROUTE_READY_TIMEOUT_MS,默认 60000)", "tls_cert": "用于提供 HTTPS 服务的 TLS 证书(PEM)路径(也可用 OMNIROUTE_TLS_CERT)", "tls_key": "用于提供 HTTPS 服务的 TLS 私钥(PEM)路径(也可用 OMNIROUTE_TLS_KEY)" }, diff --git a/bin/cli/locales/zh-TW.json b/bin/cli/locales/zh-TW.json index fa7ca866b8..f4c9c39e10 100644 --- a/bin/cli/locales/zh-TW.json +++ b/bin/cli/locales/zh-TW.json @@ -254,6 +254,7 @@ "max_restarts": "30 秒內的最大崩潰重啟次數(預設:2)", "tray": "顯示系統托盤圖示(僅桌面,選擇加入)", "no_tray": "停用系統托盤圖示", + "ready_timeout": "就緒探測逾時(毫秒)(也可用 OMNIROUTE_READY_TIMEOUT_MS,預設 60000)", "tls_cert": "用於提供 HTTPS 服務的 TLS 憑證(PEM)路徑(也可用 OMNIROUTE_TLS_CERT)", "tls_key": "用於提供 HTTPS 服務的 TLS 私鑰(PEM)路徑(也可用 OMNIROUTE_TLS_KEY)" }, diff --git a/changelog.d/maintenance/12732-attempt-log-tests-trace-id.md b/changelog.d/maintenance/12732-attempt-log-tests-trace-id.md new file mode 100644 index 0000000000..4ed29296c1 --- /dev/null +++ b/changelog.d/maintenance/12732-attempt-log-tests-trace-id.md @@ -0,0 +1 @@ +- **test(call-logs):** the early-keepalive merge and video-bridge redaction tests pass a `traceId` (defaulting to `pendingRequestId`) now that #13546 keys each attempt's call-log row on it, and the keepalive test polls against a 30s wall-clock deadline like the video-bridge test instead of a 2.4s try count ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732)) diff --git a/changelog.d/maintenance/12732-cli-serve-ready-timeout-i18n.md b/changelog.d/maintenance/12732-cli-serve-ready-timeout-i18n.md new file mode 100644 index 0000000000..e54864c118 --- /dev/null +++ b/changelog.d/maintenance/12732-cli-serve-ready-timeout-i18n.md @@ -0,0 +1 @@ +- **fix(cli):** add the `serve.ready_timeout` string to the `en`, `zh-CN` and `zh-TW` CLI catalogs; `--ready-timeout` shipped calling `t("serve.ready_timeout")` without a catalog entry, which the CLI i18n key-coverage and parity tests report ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732)) diff --git a/changelog.d/maintenance/12732-jina-custom-rows-provider-prefix.md b/changelog.d/maintenance/12732-jina-custom-rows-provider-prefix.md new file mode 100644 index 0000000000..543617a21b --- /dev/null +++ b/changelog.d/maintenance/12732-jina-custom-rows-provider-prefix.md @@ -0,0 +1 @@ +- **test(models):** the custom Jina specialty-model catalog test expects the `jina-ai/` prefix again: custom rows keep the connection provider id, only synced rows resolve through the `jina` alias, and #13403 had switched the custom assertion to `jina/` ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732)) diff --git a/changelog.d/maintenance/12732-paid-target-fixture-gemini-pro.md b/changelog.d/maintenance/12732-paid-target-fixture-gemini-pro.md new file mode 100644 index 0000000000..9520ada679 --- /dev/null +++ b/changelog.d/maintenance/12732-paid-target-fixture-gemini-pro.md @@ -0,0 +1 @@ +- **test(settings):** the #6540 paid-target tests now use `gemini/gemini-3.1-pro-preview` as the paid fixture and assert the fixtures still classify as paid/free/unknown; the old Together target became "unknown" once #13407 removed Together's one-time signup credit from the free catalog, so the three save-time blocking tests read a correct 200 as a missing guard ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732)) diff --git a/changelog.d/maintenance/12732-stryker-noauth-token-budget-coverage.md b/changelog.d/maintenance/12732-stryker-noauth-token-budget-coverage.md new file mode 100644 index 0000000000..d0a68d70a4 --- /dev/null +++ b/changelog.d/maintenance/12732-stryker-noauth-token-budget-coverage.md @@ -0,0 +1 @@ +- **fix(ci):** register `noauth-model-lockout` and `local-token-budget-429-skips-cooldown` in `stryker.conf.json` `tap.testFiles`; both cover `accountFallback.ts`/`auth.ts`/`comboPredicates.ts`, so the strict `mutation-test-coverage` gate failed Fast Quality Gates on every PR into the release line ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732)) diff --git a/stryker.conf.json b/stryker.conf.json index 6feacd68f8..d19a2621d4 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -291,6 +291,7 @@ "tests/unit/lkgp-stale-pin-exhaustion-11911.test.ts", "tests/unit/search-432-plan-limit-cooldown.test.ts", "tests/unit/livews-forward-backoff-4604.test.ts", + "tests/unit/local-token-budget-429-skips-cooldown.test.ts", "tests/unit/management-auth-hardening.test.ts", "tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts", "tests/unit/masked-200-exhaustion-fallback-6427.test.ts", @@ -309,6 +310,7 @@ "tests/unit/native-codex-turn-pin-model-scoped-fallback.test.ts", "tests/unit/no-memory-header.test.ts", "tests/unit/noauth-autocombo-lockout-7623.test.ts", + "tests/unit/noauth-model-lockout.test.ts", "tests/unit/ollama-404-model-lockout-11071.test.ts", "tests/unit/non-streaming-client-translate.test.ts", "tests/unit/non-streaming-provider-leg.test.ts", diff --git a/tests/unit/attempt-logging-early-keepalive-merge.test.ts b/tests/unit/attempt-logging-early-keepalive-merge.test.ts index 2c346b4e22..0da0e0bea4 100644 --- a/tests/unit/attempt-logging-early-keepalive-merge.test.ts +++ b/tests/unit/attempt-logging-early-keepalive-merge.test.ts @@ -23,7 +23,11 @@ const { recordEarlyKeepaliveBytes, takeEarlyKeepaliveBytes } = await import("../../open-sse/utils/earlyKeepaliveByteBuffer.ts"); function baseCtx(overrides: Record = {}) { + // #13481/#13546: the call log row is keyed on traceId. It defaults to + // pendingRequestId so these tests keep polling by the id they pass in. + const pendingRequestId = (overrides.pendingRequestId as string) ?? "REPLACE"; return { + traceId: overrides.traceId ?? pendingRequestId, provider: "openai", connectionId: "conn-1", model: "gpt-x", @@ -48,13 +52,19 @@ function baseCtx(overrides: Record = {}) { } as Parameters[1]; } -async function pollForCallLog(id: string, tries = 120) { - for (let i = 0; i < tries; i++) { +// Wall-clock deadline instead of 120 tries x 20ms (2.4s): on a loaded runner the +// async SQLite write routinely outlasts that ceiling and the row reads as missing. +// Same budget and rationale as tests/unit/video-bridge-log-redaction.test.ts. +const POLL_DEADLINE_MS = 30_000; + +async function pollForCallLog(id: string, deadlineMs = POLL_DEADLINE_MS) { + const deadline = Date.now() + deadlineMs; + for (;;) { const row = await getCallLogById(id); if (row) return row as Record; + if (Date.now() >= deadline) return null; await new Promise((r) => setTimeout(r, 20)); } - return null; } before(async () => { diff --git a/tests/unit/models-catalog-route.test.ts b/tests/unit/models-catalog-route.test.ts index addd8561df..151704d1fe 100644 --- a/tests/unit/models-catalog-route.test.ts +++ b/tests/unit/models-catalog-route.test.ts @@ -1093,9 +1093,11 @@ test("v1 models catalog does not duplicate custom Jina specialty models", async assert.equal(response.status, 200); assert.equal(visibleJinaEmbeddingRows.length, 1); - assert.equal(visibleJinaEmbeddingRows[0].id, "jina/jina-embeddings-v5-text-small"); + // Custom rows keep the connection's provider id as the prefix; only the synced + // rows above resolve through the `jina` alias (#13403 flipped this by mistake). + assert.equal(visibleJinaEmbeddingRows[0].id, "jina-ai/jina-embeddings-v5-text-small"); assert.equal(visibleJinaRerankRows.length, 1); - assert.equal(visibleJinaRerankRows[0].id, "jina/jina-reranker-v3"); + assert.equal(visibleJinaRerankRows[0].id, "jina-ai/jina-reranker-v3"); }); test("v1 models catalog exposes image model input and output modalities for advanced image providers", async () => { diff --git a/tests/unit/paid-model-target-6540.test.ts b/tests/unit/paid-model-target-6540.test.ts index d8783f975b..cdd6d8e2f7 100644 --- a/tests/unit/paid-model-target-6540.test.ts +++ b/tests/unit/paid-model-target-6540.test.ts @@ -7,7 +7,7 @@ test("isPaidModelTarget — documented free model → 'free'", () => { }); test("isPaidModelTarget — provider in free catalog but model not listed free → 'paid'", () => { - assert.equal(isPaidModelTarget("together/Qwen/Qwen3-235B-A22B"), "paid"); + assert.equal(isPaidModelTarget("gemini/gemini-3.1-pro-preview"), "paid"); }); test("isPaidModelTarget — no separator (combo/alias name) → 'unknown' (fail open)", () => { diff --git a/tests/unit/paid-model-target-routes-6540.test.ts b/tests/unit/paid-model-target-routes-6540.test.ts index 5e6181f629..e338209fc7 100644 --- a/tests/unit/paid-model-target-routes-6540.test.ts +++ b/tests/unit/paid-model-target-routes-6540.test.ts @@ -14,10 +14,13 @@ const settingsRoute = await import("../../src/app/api/settings/route.ts"); const comboDefaultsRoute = await import("../../src/app/api/settings/combo-defaults/route.ts"); const backgroundDegradationRoute = await import("../../src/app/api/settings/background-degradation/route.ts"); +const { isPaidModelTarget } = await import("../../src/shared/utils/freeModels.ts"); // A provider present in the free-model catalog (so providerHasFreeModels is -// true) but a model id that is NOT one of its documented free models. -const PAID_TARGET = "together/Qwen/Qwen3-235B-A22B"; +// true) but a model id that is NOT one of its documented free models. Gemini's +// free tier is recurring and excludes the Pro line; the previous Together target +// stopped classifying once its one-time signup credit left the catalog (#13407). +const PAID_TARGET = "gemini/gemini-3.1-pro-preview"; // A documented free model. const FREE_TARGET = "openrouter/auto"; // No "/" or "," — a combo/alias name, fails open ("unknown"). @@ -38,6 +41,12 @@ test.after(() => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); +test("fixtures classify as intended against the current free-model catalog", () => { + assert.equal(isPaidModelTarget(PAID_TARGET), "paid", "PAID_TARGET must stay a paid model"); + assert.equal(isPaidModelTarget(FREE_TARGET), "free", "FREE_TARGET must stay a free model"); + assert.equal(isPaidModelTarget(UNKNOWN_TARGET), "unknown"); +}); + // ── PATCH /api/settings — webSearchRouteModel ────────────────────────────── test("PATCH /api/settings blocks a paid webSearchRouteModel when hidePaidModels is on", async () => { diff --git a/tests/unit/video-bridge-log-redaction.test.ts b/tests/unit/video-bridge-log-redaction.test.ts index d828cb31a4..0d9ca50c9a 100644 --- a/tests/unit/video-bridge-log-redaction.test.ts +++ b/tests/unit/video-bridge-log-redaction.test.ts @@ -60,7 +60,11 @@ function videoBody() { } function baseCtx(overrides: Record = {}) { + // #13481/#13546: the call log row is keyed on traceId. It defaults to + // pendingRequestId so these tests keep polling by the id they pass in. + const pendingRequestId = (overrides.pendingRequestId as string) ?? "REPLACE"; return { + traceId: overrides.traceId ?? pendingRequestId, provider: "openai", connectionId: "conn-1", model: "gpt-x",