mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 07:42:13 +03:00
143 lines
5.3 KiB
TypeScript
143 lines
5.3 KiB
TypeScript
/**
|
|
* Vision Bridge Auto-Router Tests
|
|
*
|
|
* Moved from visionBridgeRouter.test.tsx (which was only collected by the
|
|
* advisory `test:vitest:ui` script, never by the blocking `test:unit` /
|
|
* `test:vitest` gates — see tests/unit/guardrails/*.test.ts glob in
|
|
* package.json vs the `.tsx`-only include in vitest.config.ts). This file
|
|
* has no JSX and needs no jsdom environment, so it belongs under node:test.
|
|
*
|
|
* Credential-usability checks are exercised via the `deps.hasUsableCredentials`
|
|
* injection point on getBestVisionModel()/getFallbackModels() rather than by
|
|
* mocking the `@/lib/db/providers` module: this project's Node native test
|
|
* runner (`node:test`) has no supported ESM module-mocking mechanism (see
|
|
* the "mock.module() is unavailable" notes across tests/unit/*.test.ts).
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const {
|
|
getBestVisionModel,
|
|
getFallbackModels,
|
|
recordLatency,
|
|
clearSelectionCache,
|
|
getLatencyStats,
|
|
} = await import("../../../src/lib/guardrails/visionBridgeRouter.ts");
|
|
type VisionBridgeRouterDepsT =
|
|
import("../../../src/lib/guardrails/visionBridgeRouter.ts").VisionBridgeRouterDeps;
|
|
|
|
// Fail-open default: credential store "unreadable" (indeterminate `null`),
|
|
// matching hasUsableCredentialsForModel's real behavior when the DB call
|
|
// throws. This mirrors pre-existing test expectations — every catalog
|
|
// candidate is still eligible when the credential store can't be checked.
|
|
const FAIL_OPEN_DEPS: VisionBridgeRouterDepsT = {
|
|
hasUsableCredentials: async () => null,
|
|
};
|
|
|
|
test.beforeEach(() => {
|
|
clearSelectionCache();
|
|
});
|
|
|
|
// ── getBestVisionModel ──────────────────────────────────────────────────────
|
|
|
|
test("getBestVisionModel — should return a vision-capable model", async () => {
|
|
const model = await getBestVisionModel({}, FAIL_OPEN_DEPS);
|
|
assert.ok(model);
|
|
assert.equal(typeof model, "string");
|
|
});
|
|
|
|
test("getBestVisionModel — should respect fixed model override", async () => {
|
|
const fixedModel = "openai/gpt-4o-mini";
|
|
const model = await getBestVisionModel({ fixedModel }, FAIL_OPEN_DEPS);
|
|
assert.equal(model, fixedModel);
|
|
});
|
|
|
|
test("getBestVisionModel — should exclude specified models", async () => {
|
|
const model = await getBestVisionModel(
|
|
{ excludedModels: ["openai/gpt-4o-mini", "openai/gpt-4o"] },
|
|
FAIL_OPEN_DEPS
|
|
);
|
|
assert.notEqual(model, "openai/gpt-4o-mini");
|
|
assert.notEqual(model, "openai/gpt-4o");
|
|
});
|
|
|
|
test("getBestVisionModel — excludes a candidate with no usable active connection", async () => {
|
|
// Every candidate reports a confirmed-unusable connection (`false`) ->
|
|
// no candidate survives -> returns null instead of an unreachable default.
|
|
const model = await getBestVisionModel(
|
|
{},
|
|
{ hasUsableCredentials: async () => false }
|
|
);
|
|
assert.equal(model, null);
|
|
});
|
|
|
|
test(
|
|
"getBestVisionModel — selects a credentialed candidate over an uncredentialed higher-priority one",
|
|
async () => {
|
|
// openai (priority 50, would normally win) has no usable connection;
|
|
// every other vision-capable provider does.
|
|
const model = await getBestVisionModel(
|
|
{},
|
|
{
|
|
hasUsableCredentials: async (fullModelId) => fullModelId.split("/")[0] !== "openai",
|
|
}
|
|
);
|
|
assert.equal(model.startsWith("openai/"), false);
|
|
}
|
|
);
|
|
|
|
// ── getFallbackModels ───────────────────────────────────────────────────────
|
|
|
|
test("getFallbackModels — should return fallback models excluding the primary", async () => {
|
|
const primary = "openai/gpt-4o-mini";
|
|
const fallbacks = await getFallbackModels(primary, {}, FAIL_OPEN_DEPS);
|
|
assert.ok(!fallbacks.includes(primary));
|
|
assert.ok(fallbacks.length > 0);
|
|
});
|
|
|
|
test("getFallbackModels — should respect max fallback attempts", async () => {
|
|
const fallbacks = await getFallbackModels(
|
|
"openai/gpt-4o-mini",
|
|
{ maxFallbackAttempts: 2 },
|
|
FAIL_OPEN_DEPS
|
|
);
|
|
assert.ok(fallbacks.length <= 2);
|
|
});
|
|
|
|
test(
|
|
"getFallbackModels — does not include candidates with a confirmed-unusable connection",
|
|
async () => {
|
|
const fallbacks = await getFallbackModels(
|
|
"openai/gpt-4o-mini",
|
|
{},
|
|
{ hasUsableCredentials: async (fullModelId) => fullModelId.split("/")[0] !== "anthropic" }
|
|
);
|
|
assert.ok(!fallbacks.some((m) => m.startsWith("anthropic/")));
|
|
}
|
|
);
|
|
|
|
// ── recordLatency / getLatencyStats ─────────────────────────────────────────
|
|
|
|
test("recordLatency — should record latency measurements", () => {
|
|
recordLatency("test-model", 100, true);
|
|
recordLatency("test-model", 150, true);
|
|
recordLatency("test-model", 200, false);
|
|
|
|
const stats = getLatencyStats();
|
|
assert.ok(stats["test-model"]);
|
|
assert.equal(stats["test-model"].samples, 3);
|
|
});
|
|
|
|
test("getLatencyStats — should return latency statistics", () => {
|
|
recordLatency("model-a", 100, true);
|
|
recordLatency("model-a", 120, true);
|
|
recordLatency("model-b", 200, true);
|
|
|
|
const stats = getLatencyStats();
|
|
assert.ok(stats["model-a"]);
|
|
assert.ok(stats["model-b"]);
|
|
assert.equal(stats["model-a"].avg, 110);
|
|
assert.equal(stats["model-a"].successRate, 1);
|
|
});
|