mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
test(admin): add red tests for the upstream provider report endpoint
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QS9ws7DWUM9ryiroWSWwo1
This commit is contained in:
committed by
9qeklajc
co-authored by
Claude Sonnet 5
parent
84c1c9e0a7
commit
0d7fc34381
@@ -0,0 +1,603 @@
|
||||
"""Certification report for a configured upstream provider.
|
||||
|
||||
Covers ``GET /admin/api/upstream-providers/{provider_id}/report``: the row
|
||||
contract shape, and the four pricing rows it carries —
|
||||
``pricing.served_matches_configured``, ``pricing.sats_pricing_present``,
|
||||
``pricing.enabled_models_served`` and ``pricing.cache_rate``. Each row is
|
||||
computed from the DB row plus the in-process served map; none of them make a
|
||||
network call.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from routstr.core.admin import admin_sessions
|
||||
from routstr.core.db import ModelRow, UpstreamProviderRow
|
||||
from routstr.proxy import reinitialize_upstreams
|
||||
|
||||
PRICING_ROW_IDS = (
|
||||
"pricing.served_matches_configured",
|
||||
"pricing.sats_pricing_present",
|
||||
"pricing.enabled_models_served",
|
||||
"pricing.cache_rate",
|
||||
)
|
||||
|
||||
ARCHITECTURE = {
|
||||
"modality": "text",
|
||||
"input_modalities": ["text"],
|
||||
"output_modalities": ["text"],
|
||||
"tokenizer": "unknown",
|
||||
"instruct_type": None,
|
||||
}
|
||||
|
||||
|
||||
def _admin_headers() -> dict[str, str]:
|
||||
token = "test-admin-upstream-report-token"
|
||||
admin_sessions[token] = int(
|
||||
(datetime.now(timezone.utc) + timedelta(minutes=5)).timestamp()
|
||||
)
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
async def _make_provider(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
slug: str | None = None,
|
||||
provider_fee: float = 1.0,
|
||||
base_url: str = "https://report-upstream.example/v1",
|
||||
api_key: str = "test-key",
|
||||
) -> UpstreamProviderRow:
|
||||
provider = UpstreamProviderRow(
|
||||
provider_type="generic",
|
||||
base_url=base_url,
|
||||
api_key=api_key,
|
||||
provider_fee=provider_fee,
|
||||
slug=slug,
|
||||
)
|
||||
session.add(provider)
|
||||
await session.commit()
|
||||
await session.refresh(provider)
|
||||
assert provider.id is not None
|
||||
return provider
|
||||
|
||||
|
||||
def _pricing(**overrides: object) -> dict[str, object]:
|
||||
pricing: dict[str, object] = {
|
||||
"prompt": 1.4e-7,
|
||||
"completion": 2.8e-7,
|
||||
"request": 0.0,
|
||||
"image": 0.0,
|
||||
"web_search": 0.0,
|
||||
"internal_reasoning": 0.0,
|
||||
"input_cache_read": 0.0,
|
||||
"input_cache_write": 0.0,
|
||||
}
|
||||
pricing.update(overrides)
|
||||
return pricing
|
||||
|
||||
|
||||
def _model_row(
|
||||
provider_id: int,
|
||||
*,
|
||||
model_id: str,
|
||||
pricing: dict[str, object],
|
||||
enabled: bool = True,
|
||||
) -> ModelRow:
|
||||
return ModelRow(
|
||||
id=model_id,
|
||||
name=model_id,
|
||||
description="d",
|
||||
created=0,
|
||||
context_length=8192,
|
||||
architecture=json.dumps(ARCHITECTURE),
|
||||
pricing=json.dumps(pricing),
|
||||
upstream_provider_id=provider_id,
|
||||
enabled=enabled,
|
||||
# A self-alias, same as the admin write edge stores by default —
|
||||
# ``get_effective_forwarded_model_id`` treats this as "no distinct
|
||||
# forwarded id" so it does not register a second routable alias.
|
||||
forwarded_model_id=model_id,
|
||||
)
|
||||
|
||||
|
||||
def _row_ids(rows: list[dict[str, Any]]) -> list[str]:
|
||||
return [row["id"] for row in rows]
|
||||
|
||||
|
||||
def _find_row(rows: list[dict[str, Any]], row_id: str) -> dict[str, Any]:
|
||||
for row in rows:
|
||||
if row["id"] == row_id:
|
||||
return row
|
||||
raise AssertionError(f"row {row_id!r} not found in {_row_ids(rows)!r}")
|
||||
|
||||
|
||||
def _pid(provider: UpstreamProviderRow) -> int:
|
||||
"""Narrow a persisted row's optional primary key for typed call sites."""
|
||||
assert provider.id is not None
|
||||
return provider.id
|
||||
|
||||
|
||||
async def _get_report(client: AsyncClient, provider_ref: str | int) -> Any:
|
||||
return await client.get(
|
||||
f"/admin/api/upstream-providers/{provider_ref}/report",
|
||||
headers=_admin_headers(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_requires_admin_auth(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""Sanity check: the report sits behind the same gate as the rest of
|
||||
``core/admin.py``. This already passes against the not-implemented stub
|
||||
because ``require_admin_api`` runs as a dependency before the route body
|
||||
— it is included for completeness, not as a red proof.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
|
||||
resp = await integration_client.get(
|
||||
f"/admin/api/upstream-providers/{_pid(provider)}/report"
|
||||
)
|
||||
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_unknown_provider_returns_404(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
resp = await _get_report(integration_client, 999_999_999)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_row_contract_shape_and_order(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""The row contract from the report-contract spec: stable top-level keys,
|
||||
a fixed row order with the four pricing rows first, and every row
|
||||
carrying id/status/title/detail/evidence with status in {ok, warn, fail}.
|
||||
"""
|
||||
provider = await _make_provider(integration_session, slug="report-shape-provider")
|
||||
integration_session.add(
|
||||
_model_row(_pid(provider), model_id="shape-model", pricing=_pricing())
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
assert provider.slug is not None
|
||||
resp = await _get_report(integration_client, provider.slug)
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
|
||||
# The numeric id, not an echo of whatever ref (slug, here) the request
|
||||
# used to look the provider up — matches ``_serialize_provider``'s "id".
|
||||
assert body["provider_id"] == provider.id
|
||||
generated_at = body["generated_at"]
|
||||
# Must parse as an ISO-8601 timestamp; a trailing "Z" is not accepted by
|
||||
# ``fromisoformat`` on its own.
|
||||
parsed_generated_at = datetime.fromisoformat(generated_at.replace("Z", "+00:00"))
|
||||
# Freshly generated, not a stale cached/hardcoded value.
|
||||
assert abs((datetime.now(timezone.utc) - parsed_generated_at).total_seconds()) < 60
|
||||
|
||||
rows = body["rows"]
|
||||
assert _row_ids(rows)[:4] == list(PRICING_ROW_IDS)
|
||||
for row in rows:
|
||||
assert set(row) >= {"id", "status", "title", "detail", "evidence"}
|
||||
assert row["status"] in {"ok", "warn", "fail"}
|
||||
assert isinstance(row["title"], str) and row["title"]
|
||||
assert isinstance(row["detail"], str) and row["detail"]
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_served_matches_configured_ok_when_prices_agree(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
provider = await _make_provider(integration_session, provider_fee=1.05)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="agree-model",
|
||||
pricing=_pricing(prompt=2e-7, completion=4e-7),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.served_matches_configured")
|
||||
assert row["status"] == "ok", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_served_matches_configured_zero_vs_zero_is_ok(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""A price of zero on both sides is agreement, not a legitimacy check."""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="free-model",
|
||||
pricing=_pricing(prompt=0.0, completion=0.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.served_matches_configured")
|
||||
assert row["status"] == "ok", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_served_matches_configured_fails_on_stale_served_map(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""Drift the DB row without refreshing the served map — the same shape
|
||||
of staleness a writer that bypasses ``core/admin.py`` would leave behind.
|
||||
"Configured" (built fresh from the row) must then disagree with "served"
|
||||
(built earlier, still in-process) with no epsilon.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="drift-model",
|
||||
pricing=_pricing(prompt=1e-7, completion=2e-7),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
stored = await integration_session.get(ModelRow, ("drift-model", _pid(provider)))
|
||||
assert stored is not None
|
||||
stored.pricing = json.dumps(_pricing(prompt=9e-7, completion=2e-7))
|
||||
integration_session.add(stored)
|
||||
await integration_session.commit()
|
||||
# Deliberately no reinitialize_upstreams() here: the served map must stay
|
||||
# stale for this to be a meaningful drift case.
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.served_matches_configured")
|
||||
assert row["status"] == "fail", row
|
||||
assert row["evidence"] is not None
|
||||
assert "drift-model" in json.dumps(row["evidence"])
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_sats_pricing_present_ok_when_conversion_succeeds(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(_pid(provider), model_id="sats-ok-model", pricing=_pricing())
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.sats_pricing_present")
|
||||
assert row["status"] == "ok", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_sats_pricing_present_fails_when_btc_feed_is_swallowed(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""``_update_model_sats_pricing`` swallows every exception and leaves the
|
||||
served model with ``sats_pricing=None``. This must surface here rather
|
||||
than silently advertising models with no sats price.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(_pid(provider), model_id="sats-fail-model", pricing=_pricing())
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch(
|
||||
"routstr.payment.models.sats_usd_price",
|
||||
side_effect=RuntimeError("btc feed unavailable"),
|
||||
):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.sats_pricing_present")
|
||||
assert row["status"] == "fail", row
|
||||
assert "sats-fail-model" in json.dumps(row["evidence"])
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_enabled_models_served_ok_when_all_enabled_models_are_served(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(_pid(provider), model_id="served-model", pricing=_pricing())
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.enabled_models_served")
|
||||
assert row["status"] == "ok", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_enabled_models_served_fails_when_enabled_model_has_unusable_pricing(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""A negative rate makes ``has_usable_pricing`` false, so the algorithm
|
||||
withholds the model from the served map even though the DB row is
|
||||
enabled — exactly the "enabled but never served" case this row exists
|
||||
to catch, and it must not require an upstream that stopped listing the
|
||||
model to reproduce.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="unusable-price-model",
|
||||
pricing=_pricing(prompt=-1.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.enabled_models_served")
|
||||
assert row["status"] == "fail", row
|
||||
assert "unusable-price-model" in json.dumps(row["evidence"])
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_survives_a_model_row_that_fails_to_parse(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
""" "A row never throws": a stored row even malformed enough that
|
||||
``_build_model_from_row`` raises on it (bad JSON, in this case — the same
|
||||
shape of corruption a legacy writer can leave) must become a ``fail`` row
|
||||
with the exception described, not a 500 that takes out the whole report.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(_pid(provider), model_id="good-model", pricing=_pricing())
|
||||
)
|
||||
broken = _model_row(_pid(provider), model_id="broken-model", pricing=_pricing())
|
||||
broken.pricing = "{not valid json"
|
||||
integration_session.add(broken)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert _row_ids(body["rows"])[:4] == list(PRICING_ROW_IDS)
|
||||
row = _find_row(body["rows"], "pricing.served_matches_configured")
|
||||
assert row["status"] == "fail", row
|
||||
assert "broken-model" in json.dumps(row["evidence"])
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_cache_rate_ignores_an_enabled_model_that_is_not_served(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""A negative price holds a model back from the served map even though
|
||||
its row is enabled (see ``test_enabled_models_served_fails_when_...``).
|
||||
``pricing.cache_rate`` must not certify a cache rate for a model that
|
||||
isn't actually being served — it should skip it, not count it, and
|
||||
certainly not report ``ok`` for a model nothing will ever bill through.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="unserved-model",
|
||||
pricing=_pricing(prompt=-1.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.cache_rate")
|
||||
assert row["status"] == "ok", row
|
||||
assert row["evidence"]["checked"] == 0
|
||||
assert "unserved-model" not in json.dumps(row["evidence"])
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("model_id", ["gpt-4o", "deepseek-chat"])
|
||||
async def test_cache_rate_warns_when_backfill_only_supplies_the_read_rate(
|
||||
integration_client: AsyncClient,
|
||||
integration_session: AsyncSession,
|
||||
model_id: str,
|
||||
) -> None:
|
||||
"""The row is computed from ``backfill_cache_pricing(row.id, pricing)`` at
|
||||
serve time, not from the raw DB row. Both ``gpt-4o`` and DeepSeek chat
|
||||
models are stored with ``input_cache_read=0`` (the OpenRouter feed omits
|
||||
it) and litellm's cost map fills that in — reading the raw row instead
|
||||
would falsely flag the read rate as unknown, which is the defect this
|
||||
row's spec was corrected to avoid.
|
||||
|
||||
litellm's cost map has no ``cache_creation_input_token_cost`` entry for
|
||||
either model, so the write rate stays unbackfilled: the row must still
|
||||
``warn`` (a real, if partial, gap) rather than call this ``ok``.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id=model_id,
|
||||
pricing=_pricing(prompt=2.5e-6, completion=1e-5, input_cache_read=0.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.cache_rate")
|
||||
assert row["status"] == "warn", row
|
||||
evidence_text = json.dumps(row["evidence"])
|
||||
assert model_id in evidence_text
|
||||
assert "input_cache_write" in evidence_text
|
||||
assert "input_cache_read" not in evidence_text
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_cache_rate_ok_when_backfill_supplies_both_rates(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""``claude-sonnet-4-5`` has both a cache-read and a cache-creation
|
||||
(write) rate in litellm's cost map, so once both are backfilled the row
|
||||
must be ``ok`` — this is the counterpart to the partial-coverage case
|
||||
above, proving ``ok`` is reachable and not just a status the row never
|
||||
returns once both rates are checked.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="claude-sonnet-4-5",
|
||||
pricing=_pricing(prompt=3e-6, completion=1.5e-5, input_cache_read=0.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.cache_rate")
|
||||
assert row["status"] == "ok", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_cache_rate_warns_when_rate_missing_and_unknown_to_litellm(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""No cache rate, and litellm has never heard of the model: the report
|
||||
has no persisted probe result yet (that lands with the cost probe), so
|
||||
this must be ``warn``, never ``fail`` — ``fail`` needs the probe to know
|
||||
the upstream is token-billed.
|
||||
"""
|
||||
provider = await _make_provider(integration_session)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider),
|
||||
model_id="totally-custom-self-hosted-model",
|
||||
pricing=_pricing(),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.cache_rate")
|
||||
assert row["status"] == "warn", row
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_report_rows_are_scoped_to_the_requested_provider(
|
||||
integration_client: AsyncClient, integration_session: AsyncSession
|
||||
) -> None:
|
||||
"""A second provider's broken model must not leak into this provider's
|
||||
aggregate row — each row is scoped to the provider named in the URL.
|
||||
"""
|
||||
provider_a = await _make_provider(integration_session, slug="scope-provider-a")
|
||||
provider_b = await _make_provider(
|
||||
integration_session,
|
||||
slug="scope-provider-b",
|
||||
base_url="https://report-upstream-b.example/v1",
|
||||
api_key="test-key-b",
|
||||
)
|
||||
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider_a),
|
||||
model_id="scope-a-model",
|
||||
pricing=_pricing(prompt=1e-7, completion=2e-7),
|
||||
)
|
||||
)
|
||||
integration_session.add(
|
||||
_model_row(
|
||||
_pid(provider_b),
|
||||
model_id="scope-b-model",
|
||||
pricing=_pricing(prompt=-1.0),
|
||||
)
|
||||
)
|
||||
await integration_session.commit()
|
||||
with patch("routstr.payment.models.sats_usd_price", return_value=0.0005):
|
||||
await reinitialize_upstreams()
|
||||
|
||||
resp = await _get_report(integration_client, _pid(provider_a))
|
||||
|
||||
assert resp.status_code == 200, resp.text
|
||||
row = _find_row(resp.json()["rows"], "pricing.enabled_models_served")
|
||||
assert row["status"] == "ok", row
|
||||
# Evidence must actually be inspectable here, not merely absent — an "ok"
|
||||
# row that reports ``evidence: None`` would make the leak check below
|
||||
# vacuously true (``"x" not in json.dumps(None)`` is always True) instead
|
||||
# of proving provider_b's model never entered provider_a's row.
|
||||
assert row["evidence"] is not None
|
||||
assert "scope-b-model" not in json.dumps(row["evidence"])
|
||||
Reference in New Issue
Block a user