Files
hermes-webui/tests/test_issue2540_models_endpoint_error.py
T
2026-05-20 03:12:33 -07:00

125 lines
4.4 KiB
Python

import urllib.error
import urllib.request
from email.message import Message
from api import config
class _ConfigState:
def __enter__(self):
self.old_cfg = config.cfg
self.old_mtime = config._cfg_mtime
self.old_cache = config._available_models_cache
self.old_cache_ts = config._available_models_cache_ts
self.old_cache_fp = config._available_models_cache_source_fingerprint
config._cfg_mtime = 0.0
config._available_models_cache = None
config._available_models_cache_ts = 0.0
config._available_models_cache_source_fingerprint = None
return self
def __exit__(self, exc_type, exc, tb):
config.cfg = self.old_cfg
config._cfg_mtime = self.old_mtime
config._available_models_cache = self.old_cache
config._available_models_cache_ts = self.old_cache_ts
config._available_models_cache_source_fingerprint = self.old_cache_fp
return False
def _configure_named_custom_provider(tmp_path, monkeypatch, *, model=None):
monkeypatch.setattr(config, "_models_cache_path", tmp_path / "models_cache.json")
monkeypatch.setattr(config, "_get_auth_store_path", lambda: tmp_path / "auth.json")
entry = {
"name": "Broken Proxy",
"base_url": "https://broken.example/v1",
"api_key": "bad-key",
}
if model:
entry["model"] = model
config.cfg = {
"model": {"provider": "openai-codex", "default": "gpt-5.5"},
"providers": {},
"fallback_providers": [],
"custom_providers": [entry],
}
def _groups_by_provider(data):
return {group["provider_id"]: group for group in data["groups"]}
def test_named_custom_provider_models_endpoint_401_surfaces_error(monkeypatch, tmp_path):
def fake_urlopen(req, timeout=10):
raise urllib.error.HTTPError(
getattr(req, "full_url", "https://broken.example/v1/models"),
401,
"Unauthorized",
hdrs=Message(),
fp=None,
)
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
with _ConfigState():
_configure_named_custom_provider(tmp_path, monkeypatch, model="broken/manual")
data = config.get_available_models()
group = _groups_by_provider(data)["custom:broken-proxy"]
error = group["models_endpoint_error"]
assert error["kind"] == "auth"
assert error["code"] == 401
assert "check the API key" in error["message"]
assert "broken-proxy" in error["message"]
assert "@custom:broken-proxy:broken/manual" in {m["id"] for m in group["models"]}
def test_named_custom_provider_models_endpoint_network_error_surfaces_empty_group(monkeypatch, tmp_path):
def fake_urlopen(req, timeout=10):
raise urllib.error.URLError("connection refused")
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
with _ConfigState():
_configure_named_custom_provider(tmp_path, monkeypatch)
data = config.get_available_models()
group = _groups_by_provider(data)["custom:broken-proxy"]
assert group["models"] == []
assert group["models_endpoint_error"]["kind"] == "network"
assert group["models_endpoint_error"]["code"] is None
assert "verify base_url" in group["models_endpoint_error"]["message"]
def test_named_custom_provider_models_endpoint_5xx_preserves_status(monkeypatch, tmp_path):
def fake_urlopen(req, timeout=10):
raise urllib.error.HTTPError(
getattr(req, "full_url", "https://broken.example/v1/models"),
502,
"Bad Gateway",
hdrs=Message(),
fp=None,
)
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
with _ConfigState():
_configure_named_custom_provider(tmp_path, monkeypatch, model="broken/manual")
data = config.get_available_models()
error = _groups_by_provider(data)["custom:broken-proxy"]["models_endpoint_error"]
assert error["kind"] == "http"
assert error["code"] == 502
assert "returned 502" in error["message"]
def test_frontend_model_picker_renders_provider_endpoint_hint():
ui = open("static/ui.js", encoding="utf-8").read()
css = open("static/style.css", encoding="utf-8").read()
assert "models_endpoint_error" in ui
assert "dataset.modelsEndpointError" in ui
assert "model-provider-hint" in ui
assert "entry.modelsEndpointError.message" in ui
assert ".model-provider-hint" in css