2026-06-16 22:11:14 +00:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
2026-07-09 02:52:54 +02:00
|
|
|
import pytest
|
|
|
|
|
from pydantic import ValidationError
|
|
|
|
|
|
2026-06-16 22:11:14 +00:00
|
|
|
from devplacepy.services.openai_gateway import routing as r
|
|
|
|
|
from devplacepy.services.openai_gateway.usage import pricing_from_cfg
|
2026-09-09 07:38:24 +02:00
|
|
|
from tests.conftest import run_async
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _FakeModelsResponse:
|
|
|
|
|
def __init__(self, status_code, payload=None):
|
|
|
|
|
self.status_code = status_code
|
|
|
|
|
self._payload = payload
|
|
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
|
return self._payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _FakeModelsClient:
|
|
|
|
|
def __init__(self, status_code, payload=None, error=None):
|
|
|
|
|
self.status_code = status_code
|
|
|
|
|
self.payload = payload
|
|
|
|
|
self.error = error
|
|
|
|
|
self.requested_url = None
|
|
|
|
|
self.headers = None
|
|
|
|
|
|
|
|
|
|
async def __aenter__(self):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
async def __aexit__(self, *exc_info):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
async def get(self, url):
|
|
|
|
|
self.requested_url = url
|
|
|
|
|
if self.error is not None:
|
|
|
|
|
raise self.error
|
|
|
|
|
return _FakeModelsResponse(self.status_code, self.payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _patch_stealth_client(monkeypatch, fake_client):
|
|
|
|
|
def _factory(**kwargs):
|
|
|
|
|
fake_client.headers = kwargs.get("headers")
|
|
|
|
|
return fake_client
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("devplacepy.stealth.stealth_async_client", _factory)
|
2026-06-16 22:11:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cleanup(providers, models):
|
|
|
|
|
for name in providers:
|
|
|
|
|
r.provider_store.remove(name)
|
|
|
|
|
for source in models:
|
|
|
|
|
r.model_store.remove(source)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_routes_pass_through(local_db):
|
|
|
|
|
assert r.chat_overlay("ghost-model-xyz", {}) is None
|
|
|
|
|
assert r.embed_overlay("ghost-embed-xyz", {}) is None
|
2026-07-09 02:52:54 +02:00
|
|
|
assert r.image_overlay("ghost-image-xyz", {}) is None
|
2026-06-16 22:11:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_chat_route_overlay_and_economy(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="utor",
|
|
|
|
|
base_url="https://up.example/v1/chat/completions",
|
|
|
|
|
api_key="k1",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-chat",
|
|
|
|
|
provider="utor",
|
|
|
|
|
target_model="vendor/big",
|
|
|
|
|
kind="chat",
|
|
|
|
|
context_window=64000,
|
|
|
|
|
price_cache_hit_per_m=1.0,
|
|
|
|
|
price_cache_miss_per_m=2.0,
|
|
|
|
|
price_output_per_m=8.0,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-chat", {"gateway_model_context_map": "{}"})
|
|
|
|
|
assert overlay["gateway_force_model"] is True
|
|
|
|
|
assert overlay["gateway_model"] == "vendor/big"
|
|
|
|
|
assert overlay["gateway_upstream_url"] == "https://up.example/v1/chat/completions"
|
|
|
|
|
assert overlay["gateway_api_key"] == "k1"
|
|
|
|
|
assert overlay["gateway_price_output_per_m"] == 8.0
|
|
|
|
|
assert overlay["gateway_model_context_map"]["vendor/big"] == 64000
|
|
|
|
|
pricing = pricing_from_cfg(overlay)
|
|
|
|
|
assert pricing.chat_output_per_m == 8.0
|
|
|
|
|
assert pricing.chat_cache_miss_per_m == 2.0
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["utor"], ["ut-chat"])
|
|
|
|
|
|
|
|
|
|
|
2026-09-03 00:38:15 +02:00
|
|
|
def test_resolve_fallback_returns_none_without_a_configured_fallback(local_db):
|
|
|
|
|
r.model_store.set(r.ModelRouteIn(source_model="fb-primary", target_model="v/a"))
|
|
|
|
|
try:
|
|
|
|
|
assert r.resolve_fallback("fb-primary", "chat") is None
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["fb-primary"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_fallback_returns_none_for_an_unknown_source(local_db):
|
|
|
|
|
assert r.resolve_fallback("ghost-fb-source", "chat") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_fallback_rejects_self_reference(local_db):
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-self", target_model="v/a", fallback_model="fb-self"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_fallback_returns_the_configured_route(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-backup", target_model="v/backup", kind="chat"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-primary",
|
|
|
|
|
target_model="v/primary",
|
|
|
|
|
kind="chat",
|
|
|
|
|
fallback_model="fb-backup",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
fallback = r.resolve_fallback("fb-primary", "chat")
|
|
|
|
|
assert fallback is not None
|
|
|
|
|
assert fallback.source_model == "fb-backup"
|
|
|
|
|
assert fallback.target_model == "v/backup"
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["fb-primary", "fb-backup"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_fallback_ignores_an_inactive_fallback_route(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-backup-off",
|
|
|
|
|
target_model="v/backup",
|
|
|
|
|
kind="chat",
|
|
|
|
|
is_active=False,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-primary-2",
|
|
|
|
|
target_model="v/primary",
|
|
|
|
|
kind="chat",
|
|
|
|
|
fallback_model="fb-backup-off",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
assert r.resolve_fallback("fb-primary-2", "chat") is None
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["fb-primary-2", "fb-backup-off"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_fallback_ignores_a_fallback_of_a_different_kind(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-backup-embed", target_model="v/e", kind="embed"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="fb-primary-3",
|
|
|
|
|
target_model="v/primary",
|
|
|
|
|
kind="chat",
|
|
|
|
|
fallback_model="fb-backup-embed",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
assert r.resolve_fallback("fb-primary-3", "chat") is None
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["fb-primary-3", "fb-backup-embed"])
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 22:11:14 +00:00
|
|
|
def test_vision_merge_overlay(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="utvis",
|
|
|
|
|
base_url="https://vis.example/v1/chat/completions",
|
|
|
|
|
api_key="vk",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-vision",
|
|
|
|
|
provider="utvis",
|
|
|
|
|
target_model="vendor/vlm",
|
|
|
|
|
kind="chat",
|
|
|
|
|
vision_model="vendor/describe",
|
|
|
|
|
price_input_per_m=0.5,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-vision", {})
|
|
|
|
|
assert overlay["gateway_vision_enabled"] is True
|
|
|
|
|
assert overlay["gateway_vision_model"] == "vendor/describe"
|
|
|
|
|
assert overlay["gateway_vision_url"] == "https://vis.example/v1/chat/completions"
|
|
|
|
|
assert overlay["gateway_vision_key"] == "vk"
|
|
|
|
|
assert overlay["gateway_vision_price_input_per_m"] == 0.5
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["utvis"], ["ut-vision"])
|
|
|
|
|
|
|
|
|
|
|
2026-07-09 02:52:54 +02:00
|
|
|
def test_image_route_and_kind_isolation(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="utimg",
|
|
|
|
|
base_url="https://img.example/v1/chat/completions",
|
|
|
|
|
api_key="ik",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-image",
|
|
|
|
|
provider="utimg",
|
|
|
|
|
target_model="vendor/flux",
|
|
|
|
|
kind="image",
|
|
|
|
|
price_input_per_m=0.05,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.image_overlay("ut-image", {})
|
|
|
|
|
assert overlay["gateway_image_model"] == "vendor/flux"
|
|
|
|
|
assert overlay["gateway_image_url"] == "https://img.example/v1/images"
|
|
|
|
|
assert overlay["gateway_image_key"] == "ik"
|
|
|
|
|
assert overlay["gateway_image_price_per_call"] == 0.05
|
|
|
|
|
assert r.chat_overlay("ut-image", {}) is None
|
|
|
|
|
assert r.embed_overlay("ut-image", {}) is None
|
|
|
|
|
pricing = pricing_from_cfg(overlay)
|
|
|
|
|
assert pricing.image_per_call == 0.05
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["utimg"], ["ut-image"])
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 22:11:14 +00:00
|
|
|
def test_embed_route_and_kind_isolation(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="utemb",
|
|
|
|
|
base_url="https://emb.example/v1/chat/completions",
|
|
|
|
|
api_key="ek",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-embed",
|
|
|
|
|
provider="utemb",
|
|
|
|
|
target_model="vendor/embed",
|
|
|
|
|
kind="embed",
|
|
|
|
|
price_input_per_m=0.13,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.embed_overlay("ut-embed", {})
|
|
|
|
|
assert overlay["gateway_embed_model"] == "vendor/embed"
|
|
|
|
|
assert overlay["gateway_embed_url"] == "https://emb.example/v1/embeddings"
|
|
|
|
|
assert overlay["gateway_embed_key"] == "ek"
|
|
|
|
|
assert overlay["gateway_embed_price_input_per_m"] == 0.13
|
|
|
|
|
assert r.chat_overlay("ut-embed", {}) is None
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["utemb"], ["ut-embed"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_inactive_route_ignored(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-off",
|
|
|
|
|
target_model="vendor/x",
|
|
|
|
|
kind="chat",
|
|
|
|
|
is_active=False,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
assert r.chat_overlay("ut-off", {}) is None
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["ut-off"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_blank_provider_uses_default_upstream(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-default",
|
|
|
|
|
target_model="vendor/def",
|
|
|
|
|
kind="chat",
|
|
|
|
|
price_output_per_m=3.0,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-default", {})
|
|
|
|
|
assert overlay["gateway_model"] == "vendor/def"
|
|
|
|
|
assert "gateway_upstream_url" not in overlay
|
|
|
|
|
assert "gateway_api_key" not in overlay
|
|
|
|
|
assert overlay["gateway_price_output_per_m"] == 3.0
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["ut-default"])
|
2026-07-09 02:52:54 +02:00
|
|
|
|
|
|
|
|
|
2026-09-09 07:38:24 +02:00
|
|
|
def test_provider_with_a_client_profile_gets_extra_headers(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="zen",
|
|
|
|
|
base_url="https://opencode.ai/zen/v1/chat/completions",
|
|
|
|
|
api_key="public",
|
|
|
|
|
client_profile="opencode",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(source_model="ut-zen", provider="zen", target_model="kimi-k3")
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-zen", {})
|
|
|
|
|
headers = overlay["gateway_extra_request_headers"]
|
|
|
|
|
assert headers["x-opencode-client"]
|
|
|
|
|
assert "User-Agent" in headers
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["zen"], ["ut-zen"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_provider_without_a_client_profile_gets_no_extra_headers(local_db):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(name="plainprov", base_url="https://up.example/v1/chat/completions")
|
|
|
|
|
)
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(source_model="ut-plain", provider="plainprov", target_model="vendor/x")
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-plain", {})
|
|
|
|
|
assert "gateway_extra_request_headers" not in overlay
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup(["plainprov"], ["ut-plain"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_client_profile_rejects_unknown_value():
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
r.ProviderIn(name="badprofile", client_profile="not-a-real-profile")
|
|
|
|
|
|
|
|
|
|
|
2026-07-09 02:52:54 +02:00
|
|
|
def test_tier2_and_off_peak_fields_propagate_through_overlay(local_db):
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-tiered",
|
|
|
|
|
target_model="vendor/tiered",
|
|
|
|
|
kind="chat",
|
|
|
|
|
price_cache_hit_per_m=0.0028,
|
|
|
|
|
price_cache_miss_per_m=0.14,
|
|
|
|
|
price_output_per_m=0.28,
|
|
|
|
|
context_tier_threshold_tokens=128_000,
|
|
|
|
|
price_output_per_m_tier2=0.56,
|
|
|
|
|
off_peak_start_minute=990,
|
|
|
|
|
off_peak_end_minute=30,
|
|
|
|
|
off_peak_discount_pct=25.0,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
overlay = r.chat_overlay("ut-tiered", {})
|
|
|
|
|
assert overlay["gateway_context_tier_threshold_tokens"] == 128_000
|
|
|
|
|
assert overlay["gateway_price_output_per_m_tier2"] == 0.56
|
|
|
|
|
assert overlay["gateway_price_cache_hit_per_m_tier2"] is None
|
|
|
|
|
assert overlay["gateway_off_peak_start_minute"] == 990
|
|
|
|
|
assert overlay["gateway_off_peak_end_minute"] == 30
|
|
|
|
|
assert overlay["gateway_off_peak_discount_pct"] == 25.0
|
|
|
|
|
pricing = pricing_from_cfg(overlay)
|
|
|
|
|
assert pricing.context_tier_threshold_tokens == 128_000
|
|
|
|
|
assert pricing.chat_output_per_m_tier2 == 0.56
|
|
|
|
|
assert pricing.chat_cache_hit_per_m_tier2 is None
|
|
|
|
|
assert pricing.off_peak_start_minute == 990
|
|
|
|
|
finally:
|
|
|
|
|
_cleanup([], ["ut-tiered"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_unrouted_request_never_gains_tier_or_off_peak_keys(local_db):
|
|
|
|
|
assert r.chat_overlay("ghost-model-untiered", {}) is None
|
|
|
|
|
pricing = pricing_from_cfg({})
|
|
|
|
|
assert pricing.context_tier_threshold_tokens == 0
|
|
|
|
|
assert pricing.off_peak_start_minute is None
|
|
|
|
|
assert pricing.off_peak_end_minute is None
|
|
|
|
|
assert pricing.chat_output_per_m_tier2 is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_off_peak_window_requires_both_start_and_end():
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-bad-window",
|
|
|
|
|
target_model="vendor/x",
|
|
|
|
|
off_peak_start_minute=60,
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="ut-bad-window",
|
|
|
|
|
target_model="vendor/x",
|
|
|
|
|
off_peak_end_minute=120,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_seed_default_image_routes_is_idempotent(local_db):
|
|
|
|
|
r.model_store.remove(r.IMAGE_SOURCE_MODEL)
|
|
|
|
|
r.seed_default_image_routes()
|
|
|
|
|
route = r.model_store.get(r.IMAGE_SOURCE_MODEL)
|
|
|
|
|
assert route is not None
|
|
|
|
|
assert route.kind == "image"
|
|
|
|
|
assert route.target_model == r.FLUX_TARGET_MODEL
|
|
|
|
|
r.seed_default_image_routes()
|
|
|
|
|
assert r.model_store.get(r.IMAGE_SOURCE_MODEL).target_model == r.FLUX_TARGET_MODEL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_seed_default_deepseek_routes_is_idempotent_and_preserves_customization(
|
|
|
|
|
local_db,
|
|
|
|
|
):
|
|
|
|
|
r.seed_default_deepseek_routes()
|
|
|
|
|
flash = r.model_store.get("deepseek-v4-flash")
|
|
|
|
|
pro = r.model_store.get("deepseek-v4-pro")
|
|
|
|
|
assert flash is not None and pro is not None
|
|
|
|
|
assert flash.price_cache_hit_per_m == 0.0028
|
|
|
|
|
assert flash.price_cache_miss_per_m == 0.14
|
|
|
|
|
assert flash.price_output_per_m == 0.28
|
|
|
|
|
assert flash.context_window == 1_048_576
|
|
|
|
|
assert pro.price_cache_hit_per_m == 0.003625
|
|
|
|
|
assert pro.price_cache_miss_per_m == 0.435
|
|
|
|
|
assert pro.price_output_per_m == 0.87
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="deepseek-v4-pro",
|
|
|
|
|
target_model="deepseek-v4-pro",
|
|
|
|
|
price_output_per_m=1.23,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
r.seed_default_deepseek_routes()
|
|
|
|
|
assert r.model_store.get("deepseek-v4-pro").price_output_per_m == 1.23
|
|
|
|
|
finally:
|
|
|
|
|
r.model_store.set(
|
|
|
|
|
r.ModelRouteIn(
|
|
|
|
|
source_model="deepseek-v4-pro",
|
|
|
|
|
target_model="deepseek-v4-pro",
|
|
|
|
|
price_cache_hit_per_m=0.003625,
|
|
|
|
|
price_cache_miss_per_m=0.435,
|
|
|
|
|
price_output_per_m=0.87,
|
|
|
|
|
context_window=1_048_576,
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-07-19 18:57:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_seed_publishes_molodetz_aliases(local_db):
|
|
|
|
|
r.seed_default_deepseek_routes()
|
|
|
|
|
try:
|
|
|
|
|
molodetz = r.model_store.resolve("molodetz", "chat")
|
|
|
|
|
molodetz_pro = r.model_store.resolve("molodetz-pro", "chat")
|
|
|
|
|
assert molodetz is not None
|
|
|
|
|
assert molodetz.target_model == "deepseek-v4-flash"
|
|
|
|
|
assert molodetz_pro is not None
|
|
|
|
|
assert molodetz_pro.target_model == "deepseek-v4-pro"
|
|
|
|
|
sources = {row["source_model"] for row in r.model_store.list()}
|
|
|
|
|
assert {"molodetz", "molodetz-pro"} <= sources
|
|
|
|
|
finally:
|
|
|
|
|
r.model_store.remove("molodetz")
|
|
|
|
|
r.model_store.remove("molodetz-pro")
|
2026-09-09 07:38:24 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_models_url_from_base_swaps_chat_completions():
|
|
|
|
|
assert (
|
|
|
|
|
r._models_url_from_base("https://x.example/v1/chat/completions")
|
|
|
|
|
== "https://x.example/v1/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_models_url_from_base_appends_when_no_chat_completions_suffix():
|
|
|
|
|
assert r._models_url_from_base("https://x.example/v1") == "https://x.example/v1/models"
|
|
|
|
|
assert r._models_url_from_base("https://x.example/v1/") == "https://x.example/v1/models"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_models_url_from_base_blank_is_blank():
|
|
|
|
|
assert r._models_url_from_base("") == ""
|
|
|
|
|
assert r._models_url_from_base(" ") == ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_returns_ids_on_success(local_db, monkeypatch):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(
|
|
|
|
|
name="probeprov",
|
|
|
|
|
base_url="https://x.example/v1/chat/completions",
|
|
|
|
|
api_key="sk-probe",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(200, {"data": [{"id": "vendor/a"}, {"id": "vendor/b"}]})
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
try:
|
|
|
|
|
models = run_async(r.fetch_provider_models("probeprov"))
|
|
|
|
|
assert models == ["vendor/a", "vendor/b"]
|
|
|
|
|
assert fake_client.requested_url == "https://x.example/v1/models"
|
|
|
|
|
assert fake_client.headers == {"authorization": "Bearer sk-probe"}
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprov")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_uses_default_provider_when_blank(local_db, monkeypatch):
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
|
r,
|
|
|
|
|
"_default_provider_credentials",
|
|
|
|
|
lambda: ("https://default.example/v1/chat/completions", "sk-default"),
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(200, {"data": [{"id": "default/model"}]})
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
models = run_async(r.fetch_provider_models(""))
|
|
|
|
|
assert models == ["default/model"]
|
|
|
|
|
assert fake_client.requested_url == "https://default.example/v1/models"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_for_unknown_provider(local_db):
|
|
|
|
|
assert run_async(r.fetch_provider_models("no-such-provider")) is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_on_non_200(local_db, monkeypatch):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(name="probeprov404", base_url="https://x.example/v1/chat/completions")
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(404, {})
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
try:
|
|
|
|
|
assert run_async(r.fetch_provider_models("probeprov404")) is None
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprov404")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_on_malformed_payload(local_db, monkeypatch):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(name="probeprovbad", base_url="https://x.example/v1/chat/completions")
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(200, {"not_data": []})
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
try:
|
|
|
|
|
assert run_async(r.fetch_provider_models("probeprovbad")) is None
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprovbad")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_on_empty_list(local_db, monkeypatch):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(name="probeprovempty", base_url="https://x.example/v1/chat/completions")
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(200, {"data": []})
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
try:
|
|
|
|
|
assert run_async(r.fetch_provider_models("probeprovempty")) is None
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprovempty")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_on_network_error(local_db, monkeypatch):
|
|
|
|
|
r.provider_store.set(
|
|
|
|
|
r.ProviderIn(name="probeprovdown", base_url="https://x.example/v1/chat/completions")
|
|
|
|
|
)
|
|
|
|
|
fake_client = _FakeModelsClient(200, error=RuntimeError("connection refused"))
|
|
|
|
|
_patch_stealth_client(monkeypatch, fake_client)
|
|
|
|
|
try:
|
|
|
|
|
assert run_async(r.fetch_provider_models("probeprovdown")) is None
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprovdown")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fetch_provider_models_none_when_provider_has_no_base_url(local_db):
|
|
|
|
|
r.provider_store.set(r.ProviderIn(name="probeprovnourl", base_url=""))
|
|
|
|
|
try:
|
|
|
|
|
assert run_async(r.fetch_provider_models("probeprovnourl")) is None
|
|
|
|
|
finally:
|
|
|
|
|
r.provider_store.remove("probeprovnourl")
|