From 4a22fe71f8414328d8cc1071619182468c3443e4 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 9 Aug 2026 16:57:48 +0200 Subject: 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 --- test_llamachat.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 2 deletions(-) (limited to 'test_llamachat.py') 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() -- cgit v1.2.3