diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-09 15:21:32 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-09 15:21:32 +0200 |
| commit | 86c5cdb4bb94b8d6da93c42ac48ec1abb6fa87ef (patch) | |
| tree | 47a68f23f5597b732589b8670be86998a50fbc8f /test_llamachat.py | |
| parent | 009496882033fa9f01e0faaffe9d6d797650b621 (diff) | |
| download | llamachat-86c5cdb4bb94b8d6da93c42ac48ec1abb6fa87ef.tar.gz llamachat-86c5cdb4bb94b8d6da93c42ac48ec1abb6fa87ef.zip | |
fix: warn when [[providers.local]] discards its fields
A malformed local entry was the one skip that said nothing, on the
reasoning that local still ends up working. It does, but the merge
rebuilds it from the bare base_url, so an api_key, filter or ctx_size
set on that entry is dropped without a word. The app then comes up
looking healthy, which is the strongest possible signal that nothing is
wrong, making this the case that most needs saying, not least.
Local surviving at all depends on DEFAULTS supplying base_url, since
that is what the rebuild reads. Noted in config.py so the key is not
removed as redundant, and pinned by a test.
The not-a-table wording now follows what was written: a list gets the
single-bracket fix by name, while a scalar entry does not, since telling
someone to fix a [[...]] they never typed points at the wrong line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176tzAW6H1i2Kz8vm2XXVGV
Diffstat (limited to 'test_llamachat.py')
| -rwxr-xr-x | test_llamachat.py | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/test_llamachat.py b/test_llamachat.py index ed50995..29f305f 100755 --- a/test_llamachat.py +++ b/test_llamachat.py @@ -1054,6 +1054,48 @@ def test_provider_malformed_shapes(): messages = err.getvalue() assert "listy" in messages and "urlless" in messages and "a:b" in messages assert "base_url" in messages # the missing-URL case names what is missing + # A list is the double-bracket slip, so the message names the fix. Any + # other scalar was not written that way, and must not be told to change a + # bracket it never had. + assert "not [[providers.listy]]" in messages + scalar = io.StringIO() + with redirect_stderr(scalar): + providers.parse({"providers": {"n": 5}}) + assert "[[" not in scalar.getvalue(), scalar.getvalue() + + # A malformed [[providers.local]] is the loudest case that needs saying, + # not the quietest: the merge below it rebuilds local from the bare URL, + # so the app comes up working and every field the user set is discarded + # silently. The skip is reported at the merge site because the loop never + # sees this entry. + err = io.StringIO() + with redirect_stderr(err): + clobbered = providers.parse( + { + "base_url": "http://localhost:8181", + "providers": {"local": [{"api_key": "env:SOME_VAR", + "filter": ["qwen"], + "ctx_size": 32768}]}, + } + ) + assert "local" in err.getvalue() + assert "not [[providers.local]]" in err.getvalue() + # The local provider survives, which is the non-negotiable. + assert clobbered["local"].base_url == "http://localhost:8181" + # Pinning the loss rather than only the survival: these fields are gone, + # and the warning above is the only thing that tells the user so. + assert clobbered["local"].api_key == "" + assert clobbered["local"].filter == [] + assert clobbered["local"].ctx_size is None + + # Without a bare base_url there is nothing to rebuild local from, so it + # vanishes entirely. config.DEFAULTS always supplies one, which is what + # keeps the real app safe; this pins that the safety net is that default + # and not something parse() does on its own. + assert providers.parse( + {"providers": {"local": [{"base_url": "http://y.example.org"}]}} + ) == {} + assert "base_url" in config.DEFAULTS print("ok malformed provider shapes are skipped, not raised") |
