Files
OmniRoute/tests/unit/rotation-config-omniroute.test.ts
Tushar Agarwal 1089c24bc8 Remove/mimocode sunset provider (#10186)
* remove: drop sunset MiMoCode provider from model catalog

* remove: drop sunset MiMoCode provider from model catalog (shared.ts)

Remove unused imports, types, and comments from shared.ts.

* remove: MiMoCode provider (Xiaomi sunset) — executor, registry, no-auth config, icon, tests

* refactor(providers): finish MiMoCode removal — sweep remaining no-auth references

Drop the leftover mimocode entries from the no-auth provider controls, the
translate-path snapshot, the eslint suppressions, and the #3061 auth-loop
test. Re-point the fingerprint-pin (#6696) and proxy-noauth (#6272) tests at
opencode, which exercises the same fingerprint path, so the removal does not
break runtime behavior.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(providers): reconcile provider/executor counts after MiMoCode sunset

The base's parallel doc-count sync (#10433) pinned 340 providers / 101
executors. With mimocode removed, live code has 339 providers and 100
executors; refresh the user-facing counts (package.json description,
llm.txt, README/AGENTS, i18n llm.txt, provider reference, diagrams) so the
check-docs-counts STRICT gate stays green.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(providers): fix orphaned mimocode references after MiMoCode sunset

The sunset removed mimocode/mcode from the free-onboarding candidates and
from FINGERPRINT_PROVIDERS, but two tests still referenced them:

- free-provider-onboarding-setup: the mimocode->theoldllm substitution
  introduced duplicate 'opencode' rows (impossible given the request-set
  dedupe) and the wrong display name; align expectations with the actual
  {opencode, theoldllm} dedupe behavior and 'The Old LLM (Free)' name.
- combo-system-prompt-templates-5501: resolveTargetFingerprint tested with
  provider 'mcode', which is no longer a fingerprint provider; point it at
  the remaining fingerprint provider 'opencode'.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Tushar49 <Tushar49@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-08-18 10:49:24 -03:00

197 lines
7.5 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
// Runtime rotation config (operator-managed, e.g. VibeProxy): env-driven per-status fallback
// enable, rate-limit cooldown override, per-connection overrides, and a sliding-window
// threshold counter — with defaults that preserve the engine's historical behavior.
const rc = await import("../../open-sse/services/rotationConfig.ts");
const { checkFallbackError } = await import("../../open-sse/services/accountFallback.ts");
const ROTATION_ENV_KEYS = [
"OMNIROUTE_ROTATION_ENABLED",
"OMNIROUTE_ROTATION_RATE_LIMIT_RESET_SECONDS",
"OMNIROUTE_ROTATION_DISABLE_TAG_WITHOUT_RESET",
"OMNIROUTE_ROTATE_ON_429",
"OMNIROUTE_ROTATE_429_THRESHOLD",
"OMNIROUTE_ROTATE_429_WINDOW_SECONDS",
"OMNIROUTE_ROTATE_ON_500",
"OMNIROUTE_ROTATE_500_THRESHOLD",
"OMNIROUTE_ROTATE_500_WINDOW_SECONDS",
"OMNIROUTE_ROTATE_ON_502",
"OMNIROUTE_ROTATE_502_THRESHOLD",
"OMNIROUTE_ROTATE_502_WINDOW_SECONDS",
"OMNIROUTE_ROTATE_ON_400",
"OMNIROUTE_ROTATE_400_THRESHOLD",
"OMNIROUTE_ROTATE_400_WINDOW_SECONDS",
];
function clearEnv() {
for (const k of ROTATION_ENV_KEYS) delete process.env[k];
rc.resetGlobalRotationConfigForTest();
}
test("defaults preserve historical behavior (no env set)", () => {
clearEnv();
const cfg = rc.getGlobalRotationConfig();
assert.equal(cfg.enabled, true);
assert.equal(cfg.rateLimit429.enabled, true);
assert.equal(cfg.serverError500.enabled, true);
assert.equal(cfg.badGateway502.enabled, true);
assert.equal(cfg.badRequest400.enabled, false); // 400 is opt-in
assert.equal(cfg.rateLimit429.threshold, 1); // immediate
assert.equal(rc.rateLimitCooldownOverrideMs(cfg), null); // no override => engine default
clearEnv();
});
test("env parse: disable 502, enable 400, set reset seconds + thresholds", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_ON_502 = "false";
process.env.OMNIROUTE_ROTATE_ON_400 = "true";
process.env.OMNIROUTE_ROTATION_RATE_LIMIT_RESET_SECONDS = "10";
process.env.OMNIROUTE_ROTATE_429_THRESHOLD = "6";
process.env.OMNIROUTE_ROTATE_429_WINDOW_SECONDS = "120";
rc.resetGlobalRotationConfigForTest();
const cfg = rc.getGlobalRotationConfig();
assert.equal(cfg.badGateway502.enabled, false);
assert.equal(cfg.badRequest400.enabled, true);
assert.equal(rc.rateLimitCooldownOverrideMs(cfg), 10_000);
assert.equal(cfg.rateLimit429.threshold, 6);
assert.equal(cfg.rateLimit429.windowMs, 120_000);
clearEnv();
});
test("isFallbackBlockedForStatus: restrictive for 429/500/502, never for 400/ungated", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_ON_502 = "false";
rc.resetGlobalRotationConfigForTest();
const cfg = rc.getGlobalRotationConfig();
assert.equal(rc.isFallbackBlockedForStatus(502, cfg), true); // disabled => blocked
assert.equal(rc.isFallbackBlockedForStatus(429, cfg), false); // enabled default
assert.equal(rc.isFallbackBlockedForStatus(500, cfg), false);
assert.equal(rc.isFallbackBlockedForStatus(400, cfg), false); // additive, never blocked
assert.equal(rc.isFallbackBlockedForStatus(401, cfg), false); // ungated
clearEnv();
});
test("shouldForceFallbackFor400 only when opted in", () => {
clearEnv();
let cfg = rc.getGlobalRotationConfig();
assert.equal(rc.shouldForceFallbackFor400(400, cfg), false); // default off
process.env.OMNIROUTE_ROTATE_ON_400 = "true";
rc.resetGlobalRotationConfigForTest();
cfg = rc.getGlobalRotationConfig();
assert.equal(rc.shouldForceFallbackFor400(400, cfg), true);
assert.equal(rc.shouldForceFallbackFor400(429, cfg), false); // not a 400
clearEnv();
});
test("resolveRotationConfig merges per-connection overrides over global", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_429_THRESHOLD = "6";
rc.resetGlobalRotationConfigForTest();
const merged = rc.resolveRotationConfig({
rotateOn429: false,
error429Threshold: 3,
rateLimitResetSeconds: 20,
});
assert.equal(merged.rateLimit429.enabled, false); // overridden
assert.equal(merged.rateLimit429.threshold, 3); // overridden (not the global 6)
assert.equal(merged.rateLimitResetMs, 20_000); // overridden
assert.equal(merged.serverError500.enabled, true); // inherited
clearEnv();
});
test("recordErrorAndCheckThreshold: sliding window reaches threshold then resets", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_429_THRESHOLD = "3";
process.env.OMNIROUTE_ROTATE_429_WINDOW_SECONDS = "60";
rc.resetGlobalRotationConfigForTest();
const cfg = rc.getGlobalRotationConfig();
const t0 = 1_000_000;
assert.equal(rc.recordErrorAndCheckThreshold("conn-a", 429, cfg, t0), false); // 1
assert.equal(rc.recordErrorAndCheckThreshold("conn-a", 429, cfg, t0 + 1000), false); // 2
assert.equal(rc.recordErrorAndCheckThreshold("conn-a", 429, cfg, t0 + 2000), true); // 3 => rotate
// Counter reset after threshold — next error starts a fresh window.
assert.equal(rc.recordErrorAndCheckThreshold("conn-a", 429, cfg, t0 + 3000), false);
clearEnv();
});
test("recordErrorAndCheckThreshold: errors outside the window do not accumulate", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_500_THRESHOLD = "3";
process.env.OMNIROUTE_ROTATE_500_WINDOW_SECONDS = "60";
rc.resetGlobalRotationConfigForTest();
const cfg = rc.getGlobalRotationConfig();
const t0 = 2_000_000;
assert.equal(rc.recordErrorAndCheckThreshold("conn-b", 500, cfg, t0), false); // 1
// 2 minutes later — the first error has fallen out of the 60s window.
assert.equal(rc.recordErrorAndCheckThreshold("conn-b", 500, cfg, t0 + 120_000), false); // still 1
clearEnv();
});
test("recordErrorAndCheckThreshold: threshold 1 (default) => immediate rotate", () => {
clearEnv();
const cfg = rc.getGlobalRotationConfig();
assert.equal(rc.recordErrorAndCheckThreshold("conn-c", 429, cfg, 1), true);
clearEnv();
});
test("integration: OMNIROUTE_ROTATE_ON_502=false blocks 502 fallback in checkFallbackError", () => {
clearEnv();
process.env.OMNIROUTE_ROTATE_ON_502 = "false";
rc.resetGlobalRotationConfigForTest();
const blocked = checkFallbackError(502, "bad gateway", 0, null, "openai");
assert.equal(blocked.shouldFallback, false);
clearEnv();
// With default config, a 502 still falls over (historical behavior).
const dflt = checkFallbackError(502, "bad gateway", 0, null, "openai");
assert.equal(dflt.shouldFallback, true);
clearEnv();
});
test("integration: 400 with rate-limit text still falls over under default config (#4976 preserved)", () => {
clearEnv();
const res = checkFallbackError(
400,
"Detected high-frequency non-compliant requests from you.",
0,
null,
"opencode"
);
assert.equal(res.shouldFallback, true);
clearEnv();
});
test("integration: plain 400 does not fall over by default, but does when opted in", () => {
clearEnv();
const dflt = checkFallbackError(400, "Invalid JSON: unexpected token", 0, null, "openai");
assert.equal(dflt.shouldFallback, false);
clearEnv();
process.env.OMNIROUTE_ROTATE_ON_400 = "true";
rc.resetGlobalRotationConfigForTest();
const opted = checkFallbackError(400, "Invalid JSON: unexpected token", 0, null, "openai");
assert.equal(opted.shouldFallback, true);
clearEnv();
});
test("integration: rate-limit cooldown override applies to a 429 with no upstream hint", () => {
clearEnv();
process.env.OMNIROUTE_ROTATION_RATE_LIMIT_RESET_SECONDS = "10";
rc.resetGlobalRotationConfigForTest();
const res = checkFallbackError(429, "rate limit", 0, null, "openai");
assert.equal(res.shouldFallback, true);
assert.equal(res.cooldownMs, 10_000);
clearEnv();
});