mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 03:00:23 +00:00
173 lines
5.4 KiB
Python
173 lines
5.4 KiB
Python
"""Regression coverage for #749 profile creation model/provider selection."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
import api.profiles as profiles
|
|
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
PANELS_JS = (REPO / "static" / "panels.js").read_text(encoding="utf-8")
|
|
ROUTES_PY = (REPO / "api" / "routes.py").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_profile_create_form_exposes_model_picker():
|
|
assert 'id="profileFormModel"' in PANELS_JS
|
|
assert "_populateProfileFormModelSelect" in PANELS_JS
|
|
assert "profile_model_label" in PANELS_JS
|
|
assert "profile_model_hint" in PANELS_JS
|
|
|
|
|
|
def test_profile_create_payload_preserves_provider_context():
|
|
fn_start = PANELS_JS.find("async function saveProfileForm()")
|
|
assert fn_start != -1
|
|
fn_body = PANELS_JS[fn_start : PANELS_JS.find("\n}", fn_start) + 2]
|
|
assert "profileFormModel" in fn_body
|
|
assert "_modelStateForSelect(modelEl, selectedModel)" in fn_body
|
|
assert "payload.default_model" in fn_body
|
|
assert "payload.model_provider" in fn_body
|
|
|
|
|
|
def test_profile_create_route_passes_model_fields_to_profile_api():
|
|
route_start = ROUTES_PY.find('if parsed.path == "/api/profile/create":')
|
|
assert route_start != -1
|
|
route_body = ROUTES_PY[route_start : ROUTES_PY.find('if parsed.path == "/api/profile/delete":', route_start)]
|
|
assert 'default_model = body.get("default_model"' in route_body
|
|
assert 'model_provider = body.get("model_provider"' in route_body
|
|
assert "default_model=default_model" in route_body
|
|
assert "model_provider=model_provider" in route_body
|
|
|
|
|
|
def test_profile_model_config_writer_persists_default_and_provider(tmp_path):
|
|
profile_dir = tmp_path / "profiles" / "research"
|
|
profile_dir.mkdir(parents=True)
|
|
|
|
profiles._write_model_defaults_to_config(
|
|
profile_dir,
|
|
default_model="anthropic/claude-opus-4.6",
|
|
model_provider="nous",
|
|
)
|
|
|
|
saved = yaml.safe_load((profile_dir / "config.yaml").read_text(encoding="utf-8"))
|
|
assert saved["model"]["default"] == "anthropic/claude-opus-4.6"
|
|
assert saved["model"]["provider"] == "nous"
|
|
|
|
|
|
def test_profile_model_config_writer_preserves_existing_model_settings(tmp_path):
|
|
profile_dir = tmp_path / "profiles" / "research"
|
|
profile_dir.mkdir(parents=True)
|
|
(profile_dir / "config.yaml").write_text(
|
|
"model:\n base_url: https://gateway.example/v1\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
profiles._write_model_defaults_to_config(
|
|
profile_dir,
|
|
default_model="gpt-5.5",
|
|
model_provider="openai-codex",
|
|
)
|
|
|
|
saved = yaml.safe_load((profile_dir / "config.yaml").read_text(encoding="utf-8"))
|
|
assert saved["model"]["base_url"] == "https://gateway.example/v1"
|
|
assert saved["model"]["default"] == "gpt-5.5"
|
|
assert saved["model"]["provider"] == "openai-codex"
|
|
|
|
|
|
def test_profile_model_selection_accepts_catalog_model_with_provider():
|
|
catalog = {
|
|
"groups": [
|
|
{
|
|
"provider": "OpenAI Codex",
|
|
"provider_id": "openai-codex",
|
|
"models": [{"id": "gpt-5.5", "label": "GPT-5.5"}],
|
|
}
|
|
]
|
|
}
|
|
|
|
profiles._validate_profile_model_selection(
|
|
"gpt-5.5",
|
|
"openai-codex",
|
|
available_models=catalog,
|
|
)
|
|
|
|
|
|
def test_profile_model_selection_accepts_provider_qualified_picker_value():
|
|
catalog = {
|
|
"groups": [
|
|
{
|
|
"provider": "Research Gateway",
|
|
"provider_id": "custom:research-gateway",
|
|
"models": [
|
|
{
|
|
"id": "@custom:research-gateway:claude-opus-4.6",
|
|
"label": "claude-opus-4.6",
|
|
}
|
|
],
|
|
}
|
|
]
|
|
}
|
|
|
|
default_model, model_provider = profiles._split_webui_provider_model_value(
|
|
"@custom:research-gateway:claude-opus-4.6",
|
|
"custom:research-gateway",
|
|
)
|
|
|
|
profiles._validate_profile_model_selection(
|
|
default_model,
|
|
model_provider,
|
|
available_models=catalog,
|
|
)
|
|
|
|
|
|
def test_profile_model_selection_rejects_unknown_model_provider_pair():
|
|
catalog = {
|
|
"groups": [
|
|
{
|
|
"provider": "OpenAI Codex",
|
|
"provider_id": "openai-codex",
|
|
"models": [{"id": "gpt-5.5", "label": "GPT-5.5"}],
|
|
}
|
|
]
|
|
}
|
|
|
|
with pytest.raises(ValueError, match="not available for provider"):
|
|
profiles._validate_profile_model_selection(
|
|
"missing-model",
|
|
"openai-codex",
|
|
available_models=catalog,
|
|
)
|
|
|
|
|
|
def test_profile_create_rejects_unknown_model_before_creating_profile(monkeypatch):
|
|
calls = []
|
|
|
|
monkeypatch.setattr(
|
|
profiles,
|
|
"_get_available_models_for_profile_validation",
|
|
lambda: {
|
|
"groups": [
|
|
{
|
|
"provider": "OpenAI Codex",
|
|
"provider_id": "openai-codex",
|
|
"models": [{"id": "gpt-5.5", "label": "GPT-5.5"}],
|
|
}
|
|
]
|
|
},
|
|
)
|
|
monkeypatch.setattr(
|
|
profiles,
|
|
"_create_profile_fallback",
|
|
lambda *args, **kwargs: calls.append((args, kwargs)),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Selected model 'missing-model'"):
|
|
profiles.create_profile_api(
|
|
"research",
|
|
default_model="missing-model",
|
|
model_provider="openai-codex",
|
|
)
|
|
|
|
assert calls == []
|