Files
hermes-webui/tests/test_issue7514_openrouter_zai_namespace.py
T
nesquena-hermes 090728c41d test(#7520): teach the #7514 projection tests the one glm-4.5-flash remap slot
The #7514 tests pinned a pure namespace projection (same ids modulo zai/ ->
z-ai/, same labels). #7520 intentionally serves z-ai/glm-4.5-air in the
glm-4.5-flash slot because OpenRouter does not carry glm-4.5-flash. Encode that
single explicit remap in the reference translation; keep order, length and
every other label pinned.
2026-09-23 00:13:11 +00:00

132 lines
4.9 KiB
Python

"""Regression tests for #7514: OpenRouter onboarding must serve Z.AI models
under OpenRouter's canonical ``z-ai/`` namespace, not the direct provider's
``zai/`` namespace.
``_FALLBACK_MODELS`` (api/config.py) is authored for the *direct* provider
endpoints, and its Z.AI entries use that provider's own ``zai/`` prefix.
The OpenRouter onboarding setup reused the list verbatim, so the wizard
offered ids such as ``zai/glm-5.3`` — which 404 on openrouter.ai, where the
canonical slug is ``z-ai/glm-5.3``.
These tests pin the boundary:
1. The ``openrouter`` setup translates ``zai/`` → ``z-ai/`` and never
serves a ``zai/`` id.
2. The translation is namespace-only: same models, same order, same labels.
3. The direct ``zai`` setup keeps ``zai/`` (it talks to the Z.AI endpoint,
not to OpenRouter).
4. ``_FALLBACK_MODELS`` itself is never rewritten — other consumers still
see the direct-provider namespace.
"""
from __future__ import annotations
import api.config as config
import api.onboarding as onboarding
def _models(provider_id: str) -> list[dict]:
return onboarding._SUPPORTED_PROVIDER_SETUPS[provider_id]["models"]
def _ids(provider_id: str) -> list[str]:
return [model["id"] for model in _models(provider_id)]
def _expected_openrouter_id(model_id: str) -> str:
"""Independent reference implementation of the namespace translation.
One Z.AI model has no OpenRouter counterpart: ``glm-4.5-flash`` is not in
OpenRouter's catalog, so #7520 serves Z.AI's nearest light model
``z-ai/glm-4.5-air`` in its place (same slot, so order is unchanged).
"""
if model_id in _OPENROUTER_REMAPS:
return _OPENROUTER_REMAPS[model_id]
if model_id.startswith("zai/"):
return "z-ai/" + model_id[len("zai/") :]
return model_id
# #7520: fallback ids that OpenRouter does not serve, mapped to the id the
# wizard offers instead. Keep this list tiny and explicit.
_OPENROUTER_REMAPS = {"zai/glm-4.5-flash": "z-ai/glm-4.5-air"}
def test_openrouter_setup_never_serves_direct_zai_namespace():
served = [model_id for model_id in _ids("openrouter") if model_id.startswith("zai/")]
assert served == [], (
"OpenRouter onboarding served ids from the direct-provider namespace; "
f"these 404 on the OpenRouter API: {served}"
)
def test_openrouter_setup_exposes_z_ai_models():
ids = _ids("openrouter")
assert "z-ai/glm-5.3" in ids
assert "z-ai/glm-5.3-flash" in ids
expected = {
_expected_openrouter_id(model["id"])
for model in config._FALLBACK_MODELS
if model["id"].startswith("zai/")
}
assert expected, "expected the fallback catalog to carry Z.AI models"
assert expected.issubset(set(ids)), (
"every Z.AI model from the fallback catalog must be offered under the "
f"OpenRouter namespace; missing: {sorted(expected - set(ids))}"
)
def test_openrouter_projection_is_namespace_only():
"""The OpenRouter list stays a 1:1, order-preserving projection.
Labels are preserved for every entry except the #7520 remap slots, whose
label names the model actually served.
"""
fallback = config._FALLBACK_MODELS
openrouter = _models("openrouter")
assert len(openrouter) == len(fallback)
for source, projected in zip(fallback, openrouter, strict=True):
assert projected["id"] == _expected_openrouter_id(source["id"])
if source["id"] not in _OPENROUTER_REMAPS:
assert projected["label"] == source["label"]
def test_direct_zai_setup_keeps_direct_provider_ids():
"""The direct Z.AI setup talks to the Z.AI endpoint (base_url is set on the
setup), so it keeps its own bare provider ids and must not inherit the
OpenRouter translation."""
ids = _ids("zai")
assert ids == [model["id"] for model in config._PROVIDER_MODELS["zai"]]
assert "glm-5.3" in ids
assert [model_id for model_id in ids if model_id.startswith("z-ai/")] == []
def test_fallback_models_are_untouched():
ids = [model["id"] for model in config._FALLBACK_MODELS]
assert "zai/glm-5.3" in ids
assert [model_id for model_id in ids if model_id.startswith("z-ai/")] == []
def test_namespace_helper_is_a_noop_for_other_namespaces_and_bare_ids():
for model_id in (
"anthropic/claude-sonnet-4.6",
"x-ai/grok-4.20",
"minimax/MiniMax-M3",
"openrouter/elephant-alpha",
"gpt-4o",
):
assert onboarding._to_openrouter_namespace(model_id) == model_id
assert onboarding._to_openrouter_namespace("zai/glm-5.3") == "z-ai/glm-5.3"
def test_setup_catalog_serves_the_translated_namespace():
"""The payload the wizard consumes carries the OpenRouter namespace."""
catalog = onboarding._build_setup_catalog({})
entries = {provider["id"]: provider for provider in catalog["providers"]}
ids = [model["id"] for model in entries["openrouter"]["models"]]
assert [model_id for model_id in ids if model_id.startswith("zai/")] == []
assert "z-ai/glm-5.3" in ids