fix: validate named custom provider endpoints

This commit is contained in:
chengyongru
2026-06-13 00:04:13 +08:00
committed by Xubin Ren
parent a9308eb8e2
commit 09d24e6c25
6 changed files with 164 additions and 12 deletions
+35
View File
@@ -688,6 +688,41 @@ def test_make_provider_treats_dynamic_custom_provider_as_direct():
assert kwargs["base_url"] == "https://example.com/v1"
def test_make_provider_rejects_dynamic_custom_provider_without_api_base():
config = Config.model_validate(
{
"agents": {"defaults": {"provider": "my-company-api", "model": "gpt-4o-mini"}},
"providers": {
"my-company-api": {
"apiKey": "sk-test",
}
},
}
)
with pytest.raises(ValueError, match="Provider 'my-company-api' requires api_base"):
make_provider(config)
def test_make_provider_rejects_auto_dynamic_custom_prefix_without_api_base():
config = Config.model_validate(
{
"agents": {"defaults": {"provider": "auto", "model": "companyProxy/gpt-4o"}},
"providers": {
"otherProxy": {
"apiBase": "https://other.example.test/v1",
},
"companyProxy": {
"apiKey": "sk-company",
},
},
}
)
with pytest.raises(ValueError, match="Provider 'companyProxy' requires api_base"):
make_provider(config)
@pytest.fixture
def mock_agent_runtime(tmp_path):
"""Mock agent command dependencies for focused CLI tests."""
+44
View File
@@ -79,6 +79,50 @@ def test_custom_provider_fallback_uses_model_extra_without_pydantic_warnings() -
assert config.get_provider_name() == "my-company-api"
def test_dynamic_custom_provider_prefix_matches_camel_case_key() -> None:
config = Config.model_validate({
"agents": {
"defaults": {
"provider": "auto",
"model": "companyProxy/gpt-4o-mini",
}
},
"providers": {
"otherProxy": {
"apiBase": "https://other.example.test/v1",
},
"companyProxy": {
"apiBase": "https://company.example.test/v1",
},
},
})
assert config.get_provider_name() == "companyProxy"
assert config.get_api_base() == "https://company.example.test/v1"
def test_dynamic_custom_provider_prefix_does_not_fall_through_when_base_missing() -> None:
config = Config.model_validate({
"agents": {
"defaults": {
"provider": "auto",
"model": "companyProxy/gpt-4o-mini",
}
},
"providers": {
"otherProxy": {
"apiBase": "https://other.example.test/v1",
},
"companyProxy": {
"apiKey": "sk-company",
},
},
})
assert config.get_provider_name() == "companyProxy"
assert config.get_api_base() is None
def test_legacy_defaults_config_without_presets_still_resolves() -> None:
config = Config.model_validate({
"agents": {
+48
View File
@@ -113,6 +113,31 @@ def test_create_model_configuration_accepts_dynamic_custom_provider(
assert saved.model_presets["tenant-model"].model == "gpt-4o-mini"
def test_create_model_configuration_rejects_dynamic_custom_provider_without_api_base(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
config_path = tmp_path / "config.json"
config = Config.model_validate({
"providers": {
DYNAMIC_PROVIDER_NAME: {
"apiKey": "sk-test",
}
}
})
save_config(config, config_path)
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
with pytest.raises(WebUISettingsError, match="provider is not configured"):
create_model_configuration(
{
"label": ["Tenant model"],
"provider": [DYNAMIC_PROVIDER_NAME],
"model": ["gpt-4o-mini"],
}
)
def test_create_model_configuration_rejects_unconfigured_provider(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
@@ -315,6 +340,29 @@ def test_settings_payload_includes_dynamic_custom_provider(
assert providers[DYNAMIC_PROVIDER_NAME]["api_base"] == DYNAMIC_PROVIDER_API_BASE
def test_settings_payload_marks_dynamic_custom_provider_without_api_base_unconfigured(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
config_path = tmp_path / "config.json"
config = Config.model_validate({
"providers": {
DYNAMIC_PROVIDER_NAME: {
"apiKey": "sk-test",
}
}
})
save_config(config, config_path)
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
payload = settings_payload()
providers = {row["name"]: row for row in payload["providers"]}
assert providers[DYNAMIC_PROVIDER_NAME]["configured"] is False
assert providers[DYNAMIC_PROVIDER_NAME]["api_key_hint"] == "••••"
assert providers[DYNAMIC_PROVIDER_NAME]["api_base"] is None
def test_settings_payload_includes_network_safety_fields(
tmp_path,
monkeypatch: pytest.MonkeyPatch,