From b48e44a24a4eb2837cdc5fe7ae173b0a7f06d40d Mon Sep 17 00:00:00 2001 From: BonyFish Date: Sat, 16 May 2026 12:44:25 +0800 Subject: [PATCH] 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. --- .../test_issue1106_custom_providers_models.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/tests/test_issue1106_custom_providers_models.py b/tests/test_issue1106_custom_providers_models.py index 2be22e88..9b0fd16c 100644 --- a/tests/test_issue1106_custom_providers_models.py +++ b/tests/test_issue1106_custom_providers_models.py @@ -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}"