Files
OmniRoute/tests/unit/pricing-sync.test.mjs
T
Regis 192c06cadf feat(api): add external pricing sync with LiteLLM source
Add a 3-tier pricing resolution system: user overrides > synced external > hardcoded defaults.

New files:
- src/lib/pricingSync.ts: sync engine (fetch LiteLLM, transform, store in pricing_synced namespace)
- src/app/api/pricing/sync/route.ts: POST (trigger sync), GET (status), DELETE (clear synced)
- tests/unit/pricing-sync.test.mjs: 12 unit tests for transform logic
- open-sse/mcp-server/__tests__/pricingSync.test.ts: 11 vitest tests for MCP schema

Modified files:
- src/lib/db/settings.ts: getPricing() now merges 3 layers (defaults → synced → user)
- src/server-init.ts: init pricing sync on startup when PRICING_SYNC_ENABLED=true
- src/lib/localDb.ts: re-export pricing sync functions
- open-sse/mcp-server/schemas/tools.ts: add omniroute_sync_pricing tool definition
- open-sse/mcp-server/tools/advancedTools.ts: add handleSyncPricing handler
- open-sse/mcp-server/server.ts: register omniroute_sync_pricing tool

Opt-in (PRICING_SYNC_ENABLED=false by default), user overrides are never touched,
graceful fallback on fetch failure, zero new dependencies.
2026-03-14 18:49:35 +01:00

227 lines
7.0 KiB
JavaScript

import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { transformToOmniRoute } from "../../src/lib/pricingSync.ts";
// ─── transformToOmniRoute ────────────────────────────────
describe("transformToOmniRoute", () => {
test("converts LiteLLM per-token pricing to OmniRoute per-million format", () => {
const raw = {
"openai/gpt-4o": {
input_cost_per_token: 0.0000025,
output_cost_per_token: 0.00001,
litellm_provider: "openai",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.openai, "Should have openai provider");
assert.ok(result.openai["gpt-4o"], "Should have gpt-4o model");
assert.strictEqual(result.openai["gpt-4o"].input, 2.5);
assert.strictEqual(result.openai["gpt-4o"].output, 10);
});
test("maps anthropic provider to cc alias", () => {
const raw = {
"anthropic/claude-sonnet-4-20250514": {
input_cost_per_token: 0.000003,
output_cost_per_token: 0.000015,
litellm_provider: "anthropic",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.cc, "Should map to cc alias");
assert.ok(result.cc["claude-sonnet-4-20250514"]);
assert.strictEqual(result.cc["claude-sonnet-4-20250514"].input, 3);
assert.strictEqual(result.cc["claude-sonnet-4-20250514"].output, 15);
});
test("maps vertex_ai provider to gemini and gc aliases", () => {
const raw = {
"vertex_ai/gemini-2.5-flash": {
input_cost_per_token: 0.0000003,
output_cost_per_token: 0.0000025,
litellm_provider: "vertex_ai",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.gemini, "Should map to gemini alias");
assert.ok(result.gc, "Should map to gc alias");
assert.strictEqual(result.gemini["gemini-2.5-flash"].input, 0.3);
assert.strictEqual(result.gc["gemini-2.5-flash"].input, 0.3);
});
test("skips non-chat models (embedding, image, audio)", () => {
const raw = {
"openai/text-embedding-3-small": {
input_cost_per_token: 0.00000002,
output_cost_per_token: 0,
litellm_provider: "openai",
mode: "embedding",
},
"openai/dall-e-3": {
input_cost_per_token: 0,
output_cost_per_token: 0,
litellm_provider: "openai",
mode: "image_generation",
},
};
const result = transformToOmniRoute(raw);
// openai key should not exist since all models were filtered
const openaiModels = result.openai || {};
assert.strictEqual(Object.keys(openaiModels).length, 0, "Should skip non-chat models");
});
test("includes cache pricing when available", () => {
const raw = {
"anthropic/claude-sonnet-4-20250514": {
input_cost_per_token: 0.000003,
output_cost_per_token: 0.000015,
cache_read_input_token_cost: 0.0000003,
cache_creation_input_token_cost: 0.00000375,
litellm_provider: "anthropic",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
const model = result.anthropic["claude-sonnet-4-20250514"];
assert.ok(model, "Should have model");
assert.strictEqual(model.cached, 0.3);
assert.strictEqual(model.cache_creation, 3.75);
});
test("handles models without explicit mode (treated as chat)", () => {
const raw = {
"deepseek/deepseek-chat": {
input_cost_per_token: 0.00000014,
output_cost_per_token: 0.00000028,
litellm_provider: "deepseek",
},
};
const result = transformToOmniRoute(raw);
// deepseek maps to "if" alias
assert.ok(result.if, "Should map deepseek to if alias");
assert.ok(result.if["deepseek-chat"]);
});
test("skips entries without input cost", () => {
const raw = {
"unknown/model": {
litellm_provider: "unknown",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
const unknownModels = result.unknown || {};
assert.strictEqual(Object.keys(unknownModels).length, 0);
});
test("handles zero-cost (free) models", () => {
const raw = {
"groq/llama-3.3-70b-versatile": {
input_cost_per_token: 0,
output_cost_per_token: 0,
litellm_provider: "groq",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.groq, "Should have groq provider");
assert.strictEqual(result.groq["llama-3.3-70b-versatile"].input, 0);
assert.strictEqual(result.groq["llama-3.3-70b-versatile"].output, 0);
});
test("uses litellm_provider as-is for unmapped providers", () => {
const raw = {
"newprovider/some-model": {
input_cost_per_token: 0.000001,
output_cost_per_token: 0.000002,
litellm_provider: "newprovider",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.newprovider, "Should use litellm_provider as-is");
assert.ok(result.newprovider["some-model"]);
});
test("strips provider prefix from model key", () => {
const raw = {
"openai/gpt-4o-mini": {
input_cost_per_token: 0.00000015,
output_cost_per_token: 0.0000006,
litellm_provider: "openai",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
assert.ok(result.openai["gpt-4o-mini"], "Should strip openai/ prefix");
assert.strictEqual(result.openai["gpt-4o-mini"].input, 0.15);
});
test("rounds pricing to 3 decimal places", () => {
const raw = {
"test/model": {
input_cost_per_token: 0.00000033333,
output_cost_per_token: 0.00000066666,
litellm_provider: "openai",
mode: "chat",
},
};
const result = transformToOmniRoute(raw);
// 0.00000033333 * 1e6 = 0.33333 → rounded to 0.333
assert.strictEqual(result.openai.model.input, 0.333);
assert.strictEqual(result.openai.model.output, 0.667);
});
});
// ─── Merge precedence ────────────────────────────────────
describe("pricing merge precedence", () => {
test("user overrides > synced > defaults conceptual order", () => {
// This test validates the conceptual model.
// The actual merge is tested via integration with settings.ts.
// Here we verify transform doesn't lose data needed for merge.
const raw = {
"openai/gpt-4o": {
input_cost_per_token: 0.0000025,
output_cost_per_token: 0.00001,
litellm_provider: "openai",
mode: "chat",
},
};
const synced = transformToOmniRoute(raw);
const userOverride = { openai: { "gpt-4o": { input: 999 } } };
// Simulate merge: synced then user
const merged = { ...synced.openai["gpt-4o"], ...userOverride.openai["gpt-4o"] };
assert.strictEqual(merged.input, 999, "User override should win");
assert.strictEqual(merged.output, 10, "Non-overridden fields from synced should remain");
});
});