Files
OmniRoute/tests/unit/test-all-model-status.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

113 lines
4.3 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);
}
});
// ---------------------------------------------------------------------------
// #9511 — isQuota errors are NOT auto-hidden (quota-exhausted / insufficient_balance)
// ---------------------------------------------------------------------------
test("quota-exhausted entry is NOT auto-hidden even when autoHideFailed is on", () => {
// Credits-exhausted (terminal) — isQuota but NOT isTransient
assert.deepEqual(evaluateTestAllEntry({ status: "error", isQuota: true }, true), {
status: "error",
isQuota: true,
shouldHide: false,
});
// Daily-quota (transient) — isQuota + isTransient
assert.deepEqual(
evaluateTestAllEntry({ status: "error", isQuota: true, isTransient: true }, true),
{
status: "error",
isQuota: true,
shouldHide: false,
}
);
});
test("quota-exhausted entry with autoHideFailed off is also not hidden", () => {
assert.deepEqual(evaluateTestAllEntry({ status: "error", isQuota: true }, false), {
status: "error",
isQuota: true,
shouldHide: false,
});
});
test("regression: non-quota error is still auto-hidden when autoHideFailed is on", () => {
// A plain error without isQuota/isTransient/rateLimited/isTimeout should still hide
assert.deepEqual(evaluateTestAllEntry({ status: "error" }, true), {
status: "error",
shouldHide: true,
});
});