Files
OmniRoute/tests/unit/auto-combo-budget.test.ts
Dizzle d88fc2bce7 fix(routing): table pricing with catalog fallback for off-table models, pooled latency bootstrap, fresh tier cache (#12792)
Um modelo grátis fora da tabela herdando $5/$15 por milhão e afundando no roteamento cost-aware é o defeito mais caro desta onda: silencioso, e inverte exatamente a decisão que o operador quer.

Parar de chutar 1500ms de latência para modelo desconhecido e usar a mediana observada do pool — com contador de quantas vezes o chute dispara — é trocar heurística por medição do jeito certo. O contador é o que permite saber se valeu.

Revalidei após reconstruir a branch sobre o tip: **33/33** nas suítes da PR, typecheck:core limpo, `check-api-typecheck` OK (289).

**Duas integrações:**

1. `computeSnapshotWeights` conflitou com o #12794 (health via breaker + quality), já mergeado. Os dois compõem e ambos ficaram: o seu termo de `reliability` — que era a única chave que o caminho de snapshot ainda ignorava — mais o health observado e o quality do #12794.
2. `scripts/quality/run-all-gates.mjs` conflitou com o `check:provider-order-sync` do #12790. Aditivo, os dois gates coexistem.

**Nota de dívida:** o `virtualFactory.ts` cruzou o teto de 1200 linhas pela primeira vez (1187 → 1207) somando esta onda. Congelei em vez de dividir e registrei os dois candidatos a extração na justificativa — `computeSnapshotWeights` (~85 linhas) e o grupo de elegibilidade de credencial (~70). Qualquer um dos dois volta o arquivo para baixo do cap.
2026-09-10 09:20:18 -03:00

58 lines
1.7 KiB
TypeScript

// tests/unit/auto-combo-budget.test.ts
import test from "node:test";
import assert from "node:assert/strict";
import { selectProvider } from "../../open-sse/services/autoCombo/engine.ts";
import { getSelfHealingManager } from "../../open-sse/services/autoCombo/selfHealing.ts";
import { DEFAULT_WEIGHTS } from "../../open-sse/services/autoCombo/scoring.ts";
const healer = getSelfHealingManager();
test.beforeEach(() => {
healer.exclusions.clear();
healer.incidentMode = false;
});
function config(over: Record<string, unknown> = {}) {
return {
id: "budget-probe",
name: "Budget Probe",
type: "auto",
candidatePool: [],
weights: DEFAULT_WEIGHTS,
explorationRate: 0,
...over,
} as never;
}
function cand(provider: string, model: string, cost: number) {
return {
provider,
model,
quotaRemaining: 95,
quotaTotal: 100,
circuitBreakerState: "CLOSED",
costPer1MTokens: cost,
p95LatencyMs: 120,
latencyStdDev: 8,
} as never;
}
test("default budget math is unchanged without the new field", () => {
const c = [cand("openai", "gpt-4o", 5)];
const r = selectProvider(config({ budgetCap: 1 }), c);
assert.equal(r.provider, "openai"); // (5/1M)*1000 = $0.005 < $1
});
test("32k estimated tokens enforce a 32x tighter budget", () => {
const c = [cand("openai", "gpt-4o", 5)];
// (5/1M)*32000 = $0.16 — exceeds a $0.01 cap only when tokens are honored.
// Message verified: "No candidate fits within the configured budget cap…" (engine.ts:59-62).
assert.throws(
() =>
selectProvider(
config({ budgetCap: 0.01, budgetFallback: "strict", estimatedInputTokens: 32000 }),
c
),
/budget cap/i
);
});