fix: validate named custom provider endpoints
This commit is contained in:
@@ -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."""
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user