aboutsummaryrefslogtreecommitdiffstats
path: root/test_llamachat.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-09 16:57:48 +0200
committerDanilo M. <danix@danix.xyz>2026-08-09 16:57:48 +0200
commit4a22fe71f8414328d8cc1071619182468c3443e4 (patch)
treedc60e2a5df48c5d0fd0ff4742a54be3ad7b12768 /test_llamachat.py
parentfb8bec8aafb8d050fca4dc02af4fe2a04fdb690d (diff)
downloadllamachat-4a22fe71f8414328d8cc1071619182468c3443e4.tar.gz
llamachat-4a22fe71f8414328d8cc1071619182468c3443e4.zip
feat: send bearer auth when a provider needs a key
The local router needs none, so the header is omitted entirely rather than sent empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'test_llamachat.py')
-rwxr-xr-xtest_llamachat.py100
1 files changed, 98 insertions, 2 deletions
diff --git a/test_llamachat.py b/test_llamachat.py
index 4d68cc6..0251c78 100755
--- a/test_llamachat.py
+++ b/test_llamachat.py
@@ -1820,6 +1820,88 @@ def test_token_column_migration():
print("ok token column migration")
+def test_client_auth_header():
+ """A client with a key sends Bearer auth; one without sends no header."""
+ sent = {}
+
+ class _HttpxResponse:
+ """Enough of an httpx response for Client.models().
+
+ The existing _FakeResponse in this file wraps bytes for urlopen and
+ has neither .json() nor .raise_for_status(), so it cannot stand in
+ for an httpx call.
+ """
+
+ status_code = 200
+
+ def __init__(self, payload):
+ self._payload = payload
+
+ def raise_for_status(self):
+ return None
+
+ def json(self):
+ return self._payload
+
+ def _recorder(url, timeout=None, headers=None):
+ sent["url"] = url
+ sent["headers"] = headers or {}
+ return _HttpxResponse({"data": [{"id": "m1"}]})
+
+ import httpx
+ original = httpx.get
+ try:
+ httpx.get = _recorder
+
+ assert backend.Client("http://x.example.org").models() == ["m1"]
+ assert "Authorization" not in sent["headers"]
+
+ backend.Client(
+ "http://x.example.org", api_key="sk-test-not-a-real-key"
+ ).models()
+ assert sent["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key"
+ finally:
+ httpx.get = original
+
+ # The streaming path is the one that carries every real turn, and no
+ # other test reaches it: the search tests all stub _stream_once out.
+ class _Stream:
+ status_code = 200
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc):
+ return False
+
+ def iter_lines(self):
+ return iter(['data: {"choices":[{"delta":{"content":"hi"}}]}'])
+
+ def _stream_recorder(method, url, json=None, timeout=None, headers=None):
+ sent["headers"] = headers or {}
+ return _Stream()
+
+ original_stream = httpx.stream
+ try:
+ httpx.stream = _stream_recorder
+
+ client = backend.Client("http://x.example.org")
+ assert list(client._stream_once("m", [], tools=None)) == [("content", "hi")]
+ assert "Authorization" not in sent["headers"]
+
+ keyed = backend.Client(
+ "http://x.example.org", api_key="sk-test-not-a-real-key"
+ )
+ list(keyed._stream_once("m", [], tools=None))
+ assert sent["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key"
+ finally:
+ httpx.stream = original_stream
+
+ # The key is not on the client's repr, which reaches logs and tracebacks.
+ assert "sk-test-not-a-real-key" not in repr(keyed)
+ print("ok client authorization header")
+
+
class _FakeResponse:
"""Enough of an http.client response for urlopen's context manager."""
@@ -2455,9 +2537,10 @@ def test_title_request():
def json(self):
return {"choices": [{"message": {"content": " A Title "}}]}
- def fake_post(url, json=None, timeout=None):
+ def fake_post(url, json=None, timeout=None, headers=None):
seen["url"] = url
seen["body"] = json
+ seen["headers"] = headers or {}
return FakeResponse()
import httpx
@@ -2479,13 +2562,25 @@ def test_title_request():
assert "tools" not in seen["body"]
# Thinking off, or a reasoning model spends the budget and returns "".
assert seen["body"]["chat_template_kwargs"] == {"enable_thinking": False}
+ # The local router needs no key, so the side errand carries no header.
+ assert "Authorization" not in seen["headers"]
+
+ # A keyed provider authenticates on this path too, not only on models().
+ # It runs after the body assertions above because it overwrites `seen`.
+ httpx.post = fake_post
+ try:
+ keyed = backend.Client("http://router:8181", api_key="sk-test-not-a-real-key")
+ assert keyed.complete("m", []) == " A Title "
+ finally:
+ httpx.post = original
+ assert seen["headers"]["Authorization"] == "Bearer sk-test-not-a-real-key"
# A reply with no choices is empty rather than an exception.
class Empty(FakeResponse):
def json(self):
return {}
- httpx.post = lambda url, json=None, timeout=None: Empty()
+ httpx.post = lambda url, json=None, timeout=None, headers=None: Empty()
try:
assert backend.Client("http://x").complete("m", []) == ""
finally:
@@ -2566,6 +2661,7 @@ if __name__ == "__main__":
test_models_store()
test_metadata_and_cost()
test_token_column_migration()
+ test_client_auth_header()
test_search_tool_schema()
test_search_results_sanitising()
test_tool_call_accumulation()