|
| 1 | +import threading |
| 2 | +from http.server import BaseHTTPRequestHandler, HTTPServer |
| 3 | + |
| 4 | +import pytest |
| 5 | + |
| 6 | +from openrouter import OpenRouter |
| 7 | + |
| 8 | + |
| 9 | +@pytest.fixture(name="server") |
| 10 | +def _server(): |
| 11 | + """A stub API that records the Authorization header it was sent.""" |
| 12 | + received = [] |
| 13 | + |
| 14 | + class Handler(BaseHTTPRequestHandler): |
| 15 | + def do_GET(self): # noqa: N802 - name fixed by BaseHTTPRequestHandler |
| 16 | + received.append(self.headers.get("authorization")) |
| 17 | + self.send_response(200) |
| 18 | + self.send_header("Content-Type", "application/json") |
| 19 | + self.end_headers() |
| 20 | + self.wfile.write(b'{"data": 0}') |
| 21 | + |
| 22 | + def log_message(self, *args): |
| 23 | + pass |
| 24 | + |
| 25 | + httpd = HTTPServer(("127.0.0.1", 0), Handler) |
| 26 | + threading.Thread(target=httpd.serve_forever, daemon=True).start() |
| 27 | + try: |
| 28 | + yield f"http://127.0.0.1:{httpd.server_address[1]}", received |
| 29 | + finally: |
| 30 | + httpd.shutdown() |
| 31 | + |
| 32 | + |
| 33 | +def _authorization(url, received, **kwargs): |
| 34 | + try: |
| 35 | + OpenRouter(server_url=url, **kwargs).models.count() |
| 36 | + except Exception: # the stub body does not satisfy the response schema |
| 37 | + pass |
| 38 | + assert received, "no request reached the server" |
| 39 | + return received.pop() |
| 40 | + |
| 41 | + |
| 42 | +def test_env_var_is_used_when_no_api_key_is_passed(server, monkeypatch): |
| 43 | + url, received = server |
| 44 | + monkeypatch.setenv("OPENROUTER_API_KEY", "from-env") |
| 45 | + |
| 46 | + assert _authorization(url, received) == "Bearer from-env" |
| 47 | + |
| 48 | + |
| 49 | +def test_explicit_api_key_wins_over_the_env_var(server, monkeypatch): |
| 50 | + url, received = server |
| 51 | + monkeypatch.setenv("OPENROUTER_API_KEY", "from-env") |
| 52 | + |
| 53 | + assert _authorization(url, received, api_key="explicit") == "Bearer explicit" |
| 54 | + |
| 55 | + |
| 56 | +def test_blank_api_key_falls_back_to_the_env_var(server, monkeypatch): |
| 57 | + # The documented pattern api_key=os.getenv("OPENROUTER_API_KEY", "") produces |
| 58 | + # "" when the variable is unset. It used to short-circuit the fallback and |
| 59 | + # fail in httpx with LocalProtocolError: Illegal header value b'Bearer '. |
| 60 | + url, received = server |
| 61 | + monkeypatch.setenv("OPENROUTER_API_KEY", "from-env") |
| 62 | + |
| 63 | + assert _authorization(url, received, api_key="") == "Bearer from-env" |
| 64 | + assert _authorization(url, received, api_key=" ") == "Bearer from-env" |
| 65 | + |
| 66 | + |
| 67 | +def test_no_credentials_anywhere_sends_no_authorization_header(server, monkeypatch): |
| 68 | + url, received = server |
| 69 | + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) |
| 70 | + |
| 71 | + assert _authorization(url, received) is None |
| 72 | + assert _authorization(url, received, api_key="") is None |
| 73 | + |
| 74 | + |
| 75 | +def test_callable_api_key_is_still_resolved_per_request(server, monkeypatch): |
| 76 | + url, received = server |
| 77 | + monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) |
| 78 | + keys = iter(["first", "second"]) |
| 79 | + |
| 80 | + client = OpenRouter(server_url=url, api_key=lambda: next(keys)) |
| 81 | + for expected in ["Bearer first", "Bearer second"]: |
| 82 | + try: |
| 83 | + client.models.count() |
| 84 | + except Exception: |
| 85 | + pass |
| 86 | + assert received.pop() == expected |
0 commit comments