mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
* fix(model-test): stream slow chat probes * fix(model-test): handle JSON responses for streaming probes * fix(model-test): preserve transient errors from streaming probes * fix(model-test): keep transient probe failures visible * chore(ci): rerun pull request checks * docs(model-test): clarify transient failure handling * chore(ci): rerun pull request checks * test(model-test): cover slow timeout response path * chore(quality): register base-branch mutation tests * fix(sse): stop real-network leak and DOMException crash in model-test-runner timeout path Two new tests added by this PR fail against the current release tip: - tests/unit/model-test-runner.test.ts's slow-timeout regression test races the cold-start cost of the chat-completions pipeline (SSE translators, compression settings, etc. all lazily init on the first real request in a process). With a 1s AbortController timeout, the abort can fire before chatCore ever reaches the executor's fetch() call; the mocked fetch is then invoked after the test's own `finally` block has already restored the real fetch, so the assertion on the mock never fires and the request leaks onto the real network. Warm up the pipeline with one fast, resolving mock call before timing the 1s scenario. - Once the warm-up unblocks that race, a second, real bug surfaces: withRateLimit's abort handling (open-sse/services/rateLimitManager.ts) mutates `reason.name = "AbortError"` in place. When `AbortController.abort()` is called with no explicit reason (as modelTestRunner's timeout path does), the default reason is a native DOMException, whose `name` is a read-only getter — the mutation throws `TypeError: Cannot set property name of [object DOMException] which has only a getter` instead of rejecting cleanly. Build a fresh Error instead of mutating the caller-supplied reason, preserving the original as `.cause`. Adds a focused regression test in tests/unit/rate-limit-manager.test.ts that reproduces the DOMException crash directly against withRateLimit. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
// Regression for the "Test all models" status-icon bug:
|
|
// individual ▶ test turns each model's icon green/red (onTestModel sets
|
|
// modelTestStatus), but "Test all models" only showed a toast and left every
|
|
// icon blank — the user could not tell which model passed or failed.
|
|
//
|
|
// Both handleTestAll implementations (ProviderDetailPageClient + PassthroughModelsSection)
|
|
// now route each model's result through evaluateTestAllEntry() and apply the returned
|
|
// status to modelTestStatus. This pure helper captures the status + auto-hide decision
|
|
// so it is testable in the gating node suite (matches the #3610 helper-extraction idiom).
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { evaluateTestAllEntry } from "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts";
|
|
|
|
test("ok entry maps to status 'ok' and is never hidden", () => {
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "ok" }, true), {
|
|
status: "ok",
|
|
shouldHide: false,
|
|
});
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "ok" }, false), {
|
|
status: "ok",
|
|
shouldHide: false,
|
|
});
|
|
});
|
|
|
|
test("failed entry maps to status 'error' and hides only when autoHide is on", () => {
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error" }, true), {
|
|
status: "error",
|
|
shouldHide: true,
|
|
});
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error" }, false), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
});
|
|
|
|
test("rate-limited / timeout failures show 'error' but are NOT auto-hidden", () => {
|
|
// Rate-limited and timeout are TRANSIENT — the model itself is fine, the
|
|
// provider was just throttled during a parallel Test All. Hiding it would
|
|
// silently remove a working model from /v1/models with no recovery path
|
|
// short of manual DB edit or per-row eye-toggle. We surface the failure on
|
|
// the row icon (status: 'error') but keep the model visible.
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error", rateLimited: true }, true), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error", isTimeout: true }, true), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error", isTransient: true }, true), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
// Toggle off → still not hidden, of course.
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "error", rateLimited: true }, false), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
});
|
|
|
|
test("slow batch probes remain visible and unconfirmed", () => {
|
|
assert.deepEqual(evaluateTestAllEntry({ status: "slow", isTimeout: true }, true), {
|
|
status: "error",
|
|
shouldHide: false,
|
|
});
|
|
});
|
|
|
|
test("missing / null / empty entry is treated as a failure", () => {
|
|
for (const entry of [undefined, null, {}]) {
|
|
const out = evaluateTestAllEntry(entry, true);
|
|
assert.equal(out.status, "error", `entry=${JSON.stringify(entry)} → error`);
|
|
assert.equal(out.shouldHide, true);
|
|
}
|
|
});
|