mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-29 13:10:17 +00:00
Add tests for list-format custom_providers.models
Covers 9 scenarios: plain string list, dict list, unnamed provider, mixed formats, dedup, empty list, fallback keys, @-prefix routing, and mixed list/dict items from multiple providers.
This commit is contained in:
@@ -241,3 +241,161 @@ class TestCustomProvidersModelsDict:
|
||||
ids = [m["id"] for m in group["models"]]
|
||||
assert "@custom:sub2api:gpt-5.4-mini" in ids
|
||||
assert "@custom:sub2api:gpt-5.4" in ids
|
||||
|
||||
|
||||
class TestCustomProvidersModelsList:
|
||||
"""custom_providers entries with a 'models' list should also populate the dropdown."""
|
||||
|
||||
def test_models_list_of_strings_appear_in_dropdown(self):
|
||||
"""Each entry in custom_providers[].models list of strings should appear as a selectable model."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "MultiModel",
|
||||
"base_url": "http://multi:8080/v1",
|
||||
"model": "base-v1",
|
||||
"models": ["model-a", "model-b", "model-c"],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
for expected in ["base-v1", "model-a", "model-b", "model-c"]:
|
||||
assert expected in ids, f"Expected '{expected}' in model IDs, got {ids}"
|
||||
|
||||
def test_models_list_of_dicts_with_id_appear_in_dropdown(self):
|
||||
"""Each dict entry in models list with 'id' key should appear."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "ApiHub",
|
||||
"models": [
|
||||
{"id": "gpt-5", "label": "GPT-5"},
|
||||
{"id": "claude-4", "label": "Claude Opus 4"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
assert "gpt-5" in ids
|
||||
assert "claude-4" in ids
|
||||
|
||||
def test_list_of_dicts_falls_back_to_model_name_when_no_id(self):
|
||||
"""Dict entries in models list should fall back to 'model' or 'name' when 'id' is absent."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "FlexAPI",
|
||||
"models": [
|
||||
{"model": "via-model-key", "label": "From Model"},
|
||||
{"name": "via-name-key", "label": "From Name"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
assert "via-model-key" in ids
|
||||
assert "via-name-key" in ids
|
||||
|
||||
def test_model_plus_list_dedup(self):
|
||||
"""When singular 'model' also appears in 'models' list, it should not be duplicated."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "MyServer",
|
||||
"model": "shared-model",
|
||||
"models": ["shared-model", "unique-model"],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
assert ids.count("shared-model") == 1, f"'shared-model' should appear exactly once, got {ids.count('shared-model')}"
|
||||
assert "unique-model" in ids
|
||||
|
||||
def test_empty_models_list_is_ignored(self):
|
||||
"""An empty 'models' list should not break anything."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "TestServer",
|
||||
"model": "only-model",
|
||||
"models": [],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
assert "only-model" in ids
|
||||
|
||||
def test_unnamed_provider_models_list_works(self):
|
||||
"""custom_providers without 'name' and with a 'models' list should still populate 'Custom' group."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"models": ["anon-a", "anon-b"],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids(result)
|
||||
for expected in ["anon-a", "anon-b"]:
|
||||
assert expected in ids, f"Expected '{expected}' in model IDs, got {ids}"
|
||||
|
||||
def test_list_and_dict_providers_together(self):
|
||||
"""A mix of list-format and dict-format custom providers should all contribute models."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "ListProv",
|
||||
"models": ["list-m1", "list-m2"],
|
||||
},
|
||||
{
|
||||
"name": "DictProv",
|
||||
"models": {"dict-m1": {}, "dict-m2": {}},
|
||||
},
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
for expected in ["list-m1", "list-m2", "dict-m1", "dict-m2"]:
|
||||
assert expected in ids, f"Expected '{expected}' in model IDs, got {ids}"
|
||||
|
||||
def test_mixed_list_items_string_and_dict(self):
|
||||
"""A single models list mixing strings and dicts should produce all entries."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "custom"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "MixedProv",
|
||||
"models": [
|
||||
"plain-string",
|
||||
{"id": "dict-id", "label": "Dict Label"},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
ids = _all_model_ids_bare(result)
|
||||
assert "plain-string" in ids
|
||||
assert "dict-id" in ids
|
||||
|
||||
def test_named_custom_list_models_prefixed_when_not_active_provider(self):
|
||||
"""List-format custom provider models must carry routing prefix when another provider is active."""
|
||||
result = _models_with_cfg(
|
||||
model_cfg={"provider": "deepseek", "default": "deepseek-v4-pro"},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "sub2api",
|
||||
"base_url": "http://127.0.0.1:8080/v1",
|
||||
"models": ["gpt-5.4-mini", "gpt-5.4"],
|
||||
}
|
||||
],
|
||||
)
|
||||
group = _group_for(result, "sub2api")
|
||||
assert group is not None, "sub2api group missing"
|
||||
assert group["provider_id"] == "custom:sub2api"
|
||||
ids = [m["id"] for m in group["models"]]
|
||||
assert "@custom:sub2api:gpt-5.4-mini" in ids, f"Expected @-prefixed ID, got {ids}"
|
||||
assert "@custom:sub2api:gpt-5.4" in ids, f"Expected @-prefixed ID, got {ids}"
|
||||
|
||||
Reference in New Issue
Block a user