Files
routstr-core/tests/unit/test_upstream_generic.py
T
redshift bc5b41b3bd fix(generic): carry Venice's native cache_input price into input_cache_read
The generic provider's _native_pricing parses Venice's bespoke model_spec
schema but read only pricing.input/output.usd, dropping cache_input.usd
(e.g. deepseek-v4-1-flash: $0.0075/1M cache reads vs $0.375/1M input).
With input_cache_read left at 0, billing's fallback priced cache reads at
the FULL input rate — a ~50x overcharge on cache hits compared to what the
upstream charges.

cache_input.usd is now coerced with the same rules as the token rates;
a malformed/negative cache rate is treated as absent (never carried),
mirroring the OpenRouter rung's drop-don't-carry behaviour.

Adds regression tests: cache rate carried through, and malformed cache
rates (-, Infinity, non-numeric) dropped while the model still resolves.
2026-09-21 16:26:01 +03:00

806 lines
30 KiB
Python

"""Unit tests for ``GenericUpstreamProvider.fetch_models`` price/metadata resolution.
A generic upstream is any OpenAI-compatible API. Most (DeepSeek, OpenAI,
Groq, ...) answer ``/models`` with bare ``{id, object, owned_by}`` entries that
carry *no* pricing. The provider must not fabricate a price for those: it
resolves through native ``model_spec`` (Venice's bespoke schema) → litellm's
bundled cost map → the OpenRouter feed, and only when every source misses does
it import the model **disabled** with a warning rather than invent a number.
These tests drive that behaviour through the public ``fetch_models`` API. The
``/models`` HTTP call is faked at ``httpx.AsyncClient``; the OpenRouter feed is
patched at its source (``routstr.payment.models.async_fetch_openrouter_models``)
so the resolver's lazy import picks up the stub. litellm's real bundled cost map
is used unmocked — the DeepSeek rates it ships are the assertion's ground truth.
"""
from __future__ import annotations
import logging
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from routstr.upstream.generic import GenericUpstreamProvider
class _FakeResponse:
def __init__(self, payload: dict[str, Any]) -> None:
self._payload = payload
def raise_for_status(self) -> None:
return None
def json(self) -> dict[str, Any]:
return self._payload
class _FakeAsyncClient:
"""Stand-in for ``httpx.AsyncClient`` returning a canned ``/models`` body."""
def __init__(self, payload: dict[str, Any]) -> None:
self._payload = payload
async def __aenter__(self) -> "_FakeAsyncClient":
return self
async def __aexit__(self, *exc: object) -> bool:
return False
async def get(self, url: str, headers: dict[str, str] | None = None) -> _FakeResponse:
return _FakeResponse(self._payload)
def _patch_models_endpoint(payload: dict[str, Any]) -> Any:
"""Patch the provider's ``httpx.AsyncClient`` to serve ``payload``."""
return patch(
"routstr.upstream.generic.httpx.AsyncClient",
lambda *args, **kwargs: _FakeAsyncClient(payload),
)
def _model_by_id(models: list[Any], model_id: str) -> Any:
return next(m for m in models if m.id == model_id)
# ---------------------------------------------------------------------------
# native model_spec (Venice) — must keep resolving, and capture its metadata
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_native_model_spec_resolves_and_captures_metadata() -> None:
"""A Venice-shaped ``model_spec`` is authoritative: prices/context come
straight from it and vision capability becomes an image input modality."""
payload = {
"data": [
{
"id": "venice-llama",
"owned_by": "venice",
"model_spec": {
"name": "Venice Llama",
"availableContextTokens": 65536,
"pricing": {
"input": {"usd": 0.5},
"output": {"usd": 1.5},
},
"capabilities": {"supportsVision": True},
},
}
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "venice-llama")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(0.5 / 1_000_000)
assert model.pricing.completion == pytest.approx(1.5 / 1_000_000)
assert model.context_length == 65536
assert "image" in model.architecture.input_modalities
# Vision capability must be reflected in the combined modality string, not
# flattened to "text->text".
assert model.architecture.modality == "text+image->text"
# A native price never needs the OpenRouter feed.
or_feed.assert_not_awaited()
@pytest.mark.asyncio
async def test_native_model_spec_carries_cache_read_price() -> None:
"""Venice's ``cache_input`` rate must reach ``input_cache_read``.
Regression: the native parser read only ``input``/``output`` and dropped
``cache_input``, leaving ``input_cache_read`` at 0 — which billing reads
as "no cache rate" and falls back to the FULL input rate, a ~50x
overcharge on cache hits (deepseek-v4-1-flash: $0.0075 vs $0.375 per 1M).
"""
payload = {
"data": [
{
"id": "venice-flash",
"owned_by": "venice",
"model_spec": {
"name": "Venice Flash",
"availableContextTokens": 1000000,
"pricing": {
"input": {"usd": 0.375},
"cache_input": {"usd": 0.0075},
"output": {"usd": 1.5},
},
},
}
]
}
with _patch_models_endpoint(payload):
with patch(
"routstr.payment.models.async_fetch_openrouter_models",
AsyncMock(return_value=[]),
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "venice-flash")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(0.375 / 1_000_000)
assert model.pricing.input_cache_read == pytest.approx(0.0075 / 1_000_000)
@pytest.mark.asyncio
@pytest.mark.parametrize("bad_rate", [-0.0075, "Infinity", "not-a-number"])
async def test_native_model_spec_drops_malformed_cache_rate(bad_rate: object) -> None:
"""A malformed ``cache_input`` costs the cache rate, not the model.
The token prices still resolve; the cache rate falls back to 0 (absent),
mirroring the OpenRouter rung's drop-don't-carry rule.
"""
payload = {
"data": [
{
"id": "venice-badcache",
"owned_by": "venice",
"model_spec": {
"name": "Venice Bad Cache",
"availableContextTokens": 65536,
"pricing": {
"input": {"usd": 0.375},
"cache_input": {"usd": bad_rate},
"output": {"usd": 1.5},
},
},
}
]
}
with _patch_models_endpoint(payload):
with patch(
"routstr.payment.models.async_fetch_openrouter_models",
AsyncMock(return_value=[]),
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "venice-badcache")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(0.375 / 1_000_000)
assert model.pricing.input_cache_read == 0.0
# ---------------------------------------------------------------------------
# native model_spec validation — a bogus native price is not authoritative
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_native_both_zero_price_falls_through_to_litellm() -> None:
"""A native ``model_spec`` that prices both tokens at 0 is not a real price
(the same free-tier trap the litellm/OpenRouter rungs already reject). It
must not be treated as authoritative and served free; the resolver falls
through, so a litellm-known model lands on litellm's real rate instead."""
payload = {
"data": [
{
"id": "deepseek-chat",
"owned_by": "deepseek",
"model_spec": {
"pricing": {"input": {"usd": 0}, "output": {"usd": 0}},
},
}
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "deepseek-chat")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(2.8e-07)
assert model.pricing.completion == pytest.approx(4.2e-07)
@pytest.mark.asyncio
async def test_native_negative_price_falls_through_to_litellm() -> None:
"""A negative native price would credit the caller's balance on every
request (a fund drain, not a discount). Reject it like any other unusable
price and fall through to the chain."""
payload = {
"data": [
{
"id": "deepseek-chat",
"owned_by": "deepseek",
"model_spec": {
"pricing": {"input": {"usd": -0.5}, "output": {"usd": -1.5}},
},
}
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "deepseek-chat")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(2.8e-07)
assert model.pricing.completion == pytest.approx(4.2e-07)
@pytest.mark.asyncio
async def test_native_non_numeric_price_does_not_break_catalog() -> None:
"""A non-numeric native price (``"free"``) must not raise while parsing —
an unguarded ``"free" / 1_000_000`` throws and the outer catch drops the
*entire* provider catalog. It has to fail closed for that one model while
every other model in the same response still resolves."""
payload = {
"data": [
{
"id": "broken-price",
"owned_by": "mystery",
"model_spec": {
"pricing": {"input": {"usd": "free"}, "output": {"usd": "free"}},
},
},
{"id": "deepseek-chat", "object": "model", "owned_by": "deepseek"},
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
# One malformed entry must not empty the catalog.
assert {m.id for m in models} == {"broken-price", "deepseek-chat"}
broken = _model_by_id(models, "broken-price")
assert broken.enabled is False
healthy = _model_by_id(models, "deepseek-chat")
assert healthy.enabled is True
assert healthy.pricing.prompt == pytest.approx(2.8e-07)
# ---------------------------------------------------------------------------
# litellm rescue — the money-critical case (DeepSeek bare /models)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_bare_deepseek_resolves_via_litellm() -> None:
"""DeepSeek's ``/models`` carries no pricing. The old code fabricated
``$0.001`` + ctx 4096; the resolver must instead pull DeepSeek's real
rates from litellm's bundled cost map (``$0.28``/``$0.42`` per 1M, ctx
131072) and keep the model enabled."""
payload = {
"data": [
{"id": "deepseek-chat", "object": "model", "owned_by": "deepseek"},
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "deepseek-chat")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(2.8e-07)
assert model.pricing.completion == pytest.approx(4.2e-07)
assert model.context_length == 131072
# Richer metadata than the two base prices is captured too.
assert model.pricing.input_cache_read == pytest.approx(2.8e-08)
assert model.top_provider is not None
assert model.top_provider.max_completion_tokens == 8192
# litellm answered, so the OpenRouter feed is never consulted.
or_feed.assert_not_awaited()
@pytest.mark.asyncio
async def test_litellm_zero_price_entry_fails_closed(
caplog: pytest.LogCaptureFixture,
) -> None:
"""A litellm entry that lists a model but prices it at 0/0 (free-tier
moderation/rerank models do this) is not a real price — treating it as one
would silently serve the model for free. The resolver must reject a both-zero
litellm hit and fall through, so the model imports disabled, not at $0."""
payload = {
"data": [
{"id": "omni-moderation-latest", "object": "model", "owned_by": "openai"},
]
}
gen_logger = logging.getLogger("routstr.upstream.generic")
gen_logger.addHandler(caplog.handler)
try:
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(
base_url="http://x"
).fetch_models()
finally:
gen_logger.removeHandler(caplog.handler)
model = _model_by_id(models, "omni-moderation-latest")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0
assert any(
"omni-moderation-latest" in rec.getMessage()
for rec in caplog.records
if rec.levelno >= logging.WARNING
)
@pytest.mark.asyncio
async def test_litellm_output_cap_not_used_as_context() -> None:
"""litellm's ``max_tokens`` is the completion cap, not the context window
(it tracks ``max_output_tokens`` for ~94% of models). When a model reports
no ``max_input_tokens``, the resolver must not smuggle the output cap in as
the context window; it falls back to the id-based estimate instead, while
``max_tokens`` still feeds the completion limit."""
payload = {
"data": [
{
"id": "gemini/gemini-gemma-2-9b-it",
"object": "model",
"owned_by": "google",
},
]
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "gemini/gemini-gemma-2-9b-it")
assert model.enabled is True
# litellm gives this model max_input_tokens=None, max_tokens=8192 (an output
# cap). Context must come from the estimate (4096), never the 8192 cap.
assert model.context_length == 4096
# The 8192 output cap still lands where it belongs: the completion limit.
assert model.top_provider is not None
assert model.top_provider.max_completion_tokens == 8192
# ---------------------------------------------------------------------------
# OpenRouter fallback — litellm misses, OR carries a full payload
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_unknown_to_litellm_resolves_via_openrouter() -> None:
"""A model litellm has never heard of still resolves if the OpenRouter
feed lists it, pulling price + context from that entry."""
payload = {
"data": [
{"id": "exotic/model-9000", "object": "model", "owned_by": "exotic"},
]
}
or_entry = {
"id": "exotic/model-9000",
"name": "Exotic 9000",
"context_length": 65536,
"architecture": {
"modality": "text+image->text",
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
"tokenizer": "Other",
"instruct_type": None,
},
"pricing": {"prompt": "0.000005", "completion": "0.000010"},
"top_provider": {
"context_length": 65536,
"max_completion_tokens": 4096,
"is_moderated": False,
},
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[or_entry])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "exotic/model-9000")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(5e-06)
assert model.pricing.completion == pytest.approx(1e-05)
assert model.context_length == 65536
# The feed's own modality string is carried through verbatim, not recomputed.
assert model.architecture.modality == "text+image->text"
or_feed.assert_awaited()
@pytest.mark.asyncio
async def test_openrouter_bare_tail_collision_picks_highest_price() -> None:
"""When a bare model id matches several OpenRouter entries by tail
(``model`` ↔ ``a/model``, ``b/model``), the match must be deterministic and
money-safe: pick the highest-priced candidate regardless of feed order, so
ordering can never leave the node charging below true cost. (The live feed
has zero such collisions today; this guards the latent case.)"""
payload = {
"data": [
{"id": "zzz-phantom-model", "object": "model", "owned_by": "mystery"},
]
}
# Same bare tail, different resellers; the pricier one is listed *second*
# so a first-wins match would pick the cheaper (undercharging) entry.
or_feed = AsyncMock(
return_value=[
{
"id": "cheapco/zzz-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000001", "completion": "0.000002"},
},
{
"id": "premiumco/zzz-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000009", "completion": "0.000010"},
},
]
)
with _patch_models_endpoint(payload):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "zzz-phantom-model")
assert model.pricing.prompt == pytest.approx(9e-06)
assert model.pricing.completion == pytest.approx(1e-05)
@pytest.mark.asyncio
async def test_openrouter_bare_tail_tie_breaks_on_combined_cost() -> None:
"""The bare-tail tie-break must weigh *both* rates, not prompt alone.
Given two colliding entries where one is cheaper on prompt but far dearer
on completion, ranking by prompt would pick the entry that undercharges
output-heavy traffic. Pick the highest *combined* per-token cost so the
money-safe choice holds whichever way the traffic leans."""
payload = {
"data": [
{"id": "yyy-phantom-model", "object": "model", "owned_by": "mystery"},
]
}
# dear-overall is listed first with the *lower* prompt, so a prompt-only max
# would wrongly pick the second (cheaper-overall) entry.
or_feed = AsyncMock(
return_value=[
{
"id": "dearco/yyy-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000001", "completion": "0.000100"},
},
{
"id": "cheapco/yyy-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000009", "completion": "0.000002"},
},
]
)
with _patch_models_endpoint(payload):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "yyy-phantom-model")
assert model.pricing.prompt == pytest.approx(1e-06)
assert model.pricing.completion == pytest.approx(1e-04)
@pytest.mark.asyncio
async def test_openrouter_feed_fetched_once_per_discovery() -> None:
"""Two models both missing litellm must share a single OpenRouter fetch —
the feed is not re-downloaded per model."""
payload = {
"data": [
{"id": "exotic/model-a", "object": "model", "owned_by": "exotic"},
{"id": "exotic/model-b", "object": "model", "owned_by": "exotic"},
]
}
or_feed = AsyncMock(
return_value=[
{
"id": "exotic/model-a",
"context_length": 8192,
"pricing": {"prompt": "0.000001", "completion": "0.000002"},
},
{
"id": "exotic/model-b",
"context_length": 8192,
"pricing": {"prompt": "0.000003", "completion": "0.000004"},
},
]
)
with _patch_models_endpoint(payload):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
assert {m.id for m in models} == {"exotic/model-a", "exotic/model-b"}
assert or_feed.await_count == 1
# ---------------------------------------------------------------------------
# fail closed — no source resolves → disabled + warned, never fabricated
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_unresolvable_model_fails_closed(
caplog: pytest.LogCaptureFixture,
) -> None:
"""When native, litellm and OpenRouter all miss, the model is imported
disabled with a warning naming it — and no price is invented (the old
``$0.001`` placeholder is gone)."""
payload = {
"data": [
{
"id": "nobody-has-priced-this-xyz",
"object": "model",
"owned_by": "mystery",
},
]
}
# routstr loggers set propagate=False, so caplog's root handler misses
# them; attach its handler to the provider logger directly.
gen_logger = logging.getLogger("routstr.upstream.generic")
gen_logger.addHandler(caplog.handler)
try:
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch(
"routstr.payment.models.async_fetch_openrouter_models", or_feed
):
models = await GenericUpstreamProvider(
base_url="http://x"
).fetch_models()
finally:
gen_logger.removeHandler(caplog.handler)
model = _model_by_id(models, "nobody-has-priced-this-xyz")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0
assert any(
"nobody-has-priced-this-xyz" in rec.getMessage()
for rec in caplog.records
if rec.levelno >= logging.WARNING
)
# ---------------------------------------------------------------------------
# rate validation — a malformed rate is not a resolved price, at any rung
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_non_finite_litellm_rate_is_not_a_resolved_price() -> None:
"""A non-finite entry in the cost map must not answer the resolution chain.
The litellm rung rejects negatives and a both-zero entry, but every
comparison with ``NaN`` is False and ``inf`` reads as a large positive, so
either would be reported as a resolved price — enabling the model at a rate
the node cannot bill on. Fail closed instead: the model imports disabled,
which is what "no source knows this price" already means here.
"""
payload = {
"data": [
{"id": "nan-priced-model", "object": "model", "owned_by": "mystery"},
]
}
cost_entry = {
"input_cost_per_token": float("nan"),
"output_cost_per_token": float("inf"),
"max_input_tokens": 8192,
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch("routstr.payment.models.litellm_cost_entry", lambda _id: cost_entry):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(
base_url="http://x"
).fetch_models()
model = _model_by_id(models, "nan-priced-model")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0
@pytest.mark.asyncio
async def test_non_finite_openrouter_rate_is_not_a_resolved_price() -> None:
"""``float("Infinity")`` parses happily from a feed string, so the
OpenRouter rung's coercion accepted it and reported an infinite rate as a
resolved price. It is not a price; the model must import disabled."""
payload = {
"data": [
{"id": "or-nonfinite-xyz", "object": "model", "owned_by": "mystery"},
]
}
feed = [
{
"id": "or-nonfinite-xyz",
"pricing": {"prompt": "Infinity", "completion": "0.000002"},
"context_length": 8192,
}
]
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=feed)
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "or-nonfinite-xyz")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0
@pytest.mark.asyncio
async def test_non_finite_openrouter_cache_rate_is_dropped_not_carried() -> None:
"""A malformed *cache* rate must cost the cache rate, not the model.
The catalog import filter only inspects prompt and completion, so an entry
with two sound token rates and an unusable ``input_cache_read`` reaches the
resolver intact. Carrying that rate through would price every cached input
token at ``inf``; dropping it falls back to the full input rate, which is
what a missing cache rate already means.
"""
payload = {
"data": [
{"id": "or-badcache-xyz", "object": "model", "owned_by": "mystery"},
]
}
feed = [
{
"id": "or-badcache-xyz",
"pricing": {
"prompt": "0.000001",
"completion": "0.000002",
"input_cache_read": "Infinity",
},
"context_length": 8192,
}
]
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=feed)
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "or-badcache-xyz")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(1e-06)
assert model.pricing.input_cache_read == 0.0
@pytest.mark.asyncio
async def test_negative_openrouter_cache_rate_is_dropped_not_carried() -> None:
"""A negative rate is as unusable as a non-finite one, and arrives the same way.
The coercion that reads a feed price rejected ``NaN``/``inf`` but returned a
negative unchanged, and the catalog import filter that would have caught one
inspects only prompt and completion. So a negative cache rate was the single
malformed value that still reached a stored price — where it prices cached
input tokens at a credit rather than a charge.
"""
payload = {
"data": [
{"id": "or-negcache-xyz", "object": "model", "owned_by": "mystery"},
]
}
feed = [
{
"id": "or-negcache-xyz",
"pricing": {
"prompt": "0.000001",
"completion": "0.000002",
"input_cache_read": "-0.0000005",
},
"context_length": 8192,
}
]
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=feed)
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "or-negcache-xyz")
assert model.enabled is True
assert model.pricing.prompt == pytest.approx(1e-06)
assert model.pricing.input_cache_read == 0.0
@pytest.mark.asyncio
async def test_boolean_litellm_rate_is_not_a_resolved_price() -> None:
"""``isinstance(True, int)`` is True, so a boolean passed the cost map's own
numeric check and resolved as a rate of ``1.0`` — a dollar per token."""
payload = {
"data": [
{"id": "bool-priced-model", "object": "model", "owned_by": "mystery"},
]
}
cost_entry = {
"input_cost_per_token": True,
"output_cost_per_token": 2e-06,
"max_input_tokens": 8192,
}
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=[])
with patch("routstr.payment.models.litellm_cost_entry", lambda _id: cost_entry):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(
base_url="http://x"
).fetch_models()
model = _model_by_id(models, "bool-priced-model")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0
@pytest.mark.asyncio
async def test_boolean_openrouter_rate_is_not_a_resolved_price() -> None:
"""The same coercion reads a feed's ``true`` as a rate of ``1.0``; the model
must import disabled rather than priced at a dollar per token."""
payload = {
"data": [
{"id": "or-bool-xyz", "object": "model", "owned_by": "mystery"},
]
}
feed = [
{
"id": "or-bool-xyz",
"pricing": {"prompt": True, "completion": "0.000002"},
"context_length": 8192,
}
]
with _patch_models_endpoint(payload):
or_feed = AsyncMock(return_value=feed)
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "or-bool-xyz")
assert model.enabled is False
assert model.pricing.prompt == 0.0
assert model.pricing.completion == 0.0