fix(proxy): fail fast when general_settings is not a mapping

A non-dict general_settings (e.g. a bare string from a malformed YAML
block) reached general_settings.get(...) in run_server and crashed at
startup with an opaque AttributeError: 'str' object has no attribute
'get' (see #38190). Guard the value: None still coerces to {}, and any
other non-mapping now raises a ValueError that points at the config block.

Adds regression tests covering the None (tolerated) and string (fails
fast) cases.
This commit is contained in:
linhongyu510 2026-08-25 17:34:31 +08:00
parent 4d7144160a
commit 08d05d5213
2 changed files with 38 additions and 0 deletions

View file

@ -1151,6 +1151,16 @@ def run_server(
general_settings = _config.get("general_settings", {})
if general_settings is None:
general_settings = {}
elif not isinstance(general_settings, dict):
# A non-dict `general_settings` (e.g. a bare string from a
# malformed YAML block) otherwise surfaces as a cryptic
# `AttributeError: 'str' object has no attribute 'get'` deep in
# startup. Fail fast with a message that points at the config.
raise ValueError(
"`general_settings` in the proxy config must be a mapping "
f"(got {type(general_settings).__name__}). Check the "
"`general_settings:` block in your config file."
)
### LOAD KEY MANAGEMENT SETTINGS FIRST (needed for custom secret manager) ###
key_management_settings: Final = general_settings.get("key_management_settings", None)
if key_management_settings is not None:

View file

@ -2532,3 +2532,31 @@ class TestTokenAuthCliFlags:
assert result.exit_code == 0, f"exit_code={result.exit_code}, output={result.output}"
assert "ENTRA_TOKEN" not in (database_url or "")
assert toggle is None
@pytest.mark.xdist_group("proxy_cli")
class TestGeneralSettingsShape:
"""`general_settings` is read straight from user YAML. A non-mapping value
(a bare string when the block is malformed, or `null` from an empty block)
used to reach `general_settings.get(...)` and blow up at startup with an
opaque `AttributeError: 'str' object has no attribute 'get'`. Startup must
either tolerate it (None -> {}) or fail with a message pointing at the config.
"""
def test_null_general_settings_is_tolerated(self, tmp_path):
"""An empty `general_settings:` block parses as None and must not crash."""
config_path = tmp_path / "config.yaml"
config_path.write_text(yaml.dump({"model_list": [], "general_settings": None}))
captured = _run_server_and_capture_urls(str(config_path))
assert captured["DATABASE_URL"].startswith("postgresql://t:t@localhost:5432/t")
def test_string_general_settings_fails_with_actionable_error(self, tmp_path):
"""A non-mapping value must fail fast, not as a cryptic AttributeError."""
config_path = tmp_path / "config.yaml"
config_path.write_text(
yaml.dump({"model_list": [], "general_settings": "database_url"})
)
with pytest.raises(ValueError, match=r"general_settings.*must be a mapping"):
_run_server_and_capture_urls(str(config_path))