headroom / tests /test_proxy /test_proxy_api_key_auth.py
tudragon154203
fix: use get_authorization_scheme_param for Bearer auth + skip /visualize and /dashboard
ab357e0
"""Tests for proxy-level API key authentication."""
import pytest
from fastapi.testclient import TestClient
from headroom.proxy.models import ProxyConfig
from headroom.proxy.server import _proxy_config_from_env, create_app
@pytest.fixture()
def api_client():
"""Create a TestClient with API key auth enabled."""
config = ProxyConfig(
proxy_api_key="test-secret-key",
require_proxy_api_key=True,
cache_enabled=False,
rate_limit_enabled=False,
log_requests=False,
)
with TestClient(create_app(config)) as client:
yield client
@pytest.fixture()
def open_client():
"""Create a TestClient with API key auth disabled."""
config = ProxyConfig(
cache_enabled=False,
rate_limit_enabled=False,
log_requests=False,
)
with TestClient(create_app(config)) as client:
yield client
class TestHealthEndpointsAlwaysAccessible:
"""Health endpoints must remain accessible without API key."""
def test_livez_no_key(self, api_client):
resp = api_client.get("/livez")
assert resp.status_code == 200
def test_healthz_no_key(self, api_client):
resp = api_client.get("/healthz")
assert resp.status_code == 200
def test_readyz_no_key(self, api_client):
resp = api_client.get("/readyz")
assert resp.status_code == 200
def test_health_no_key(self, api_client):
resp = api_client.get("/health")
assert resp.status_code == 200
def test_visualize_no_key(self, api_client):
resp = api_client.get("/visualize")
assert resp.status_code == 200
def test_dashboard_no_key(self, api_client):
resp = api_client.get("/dashboard")
assert resp.status_code == 200
class TestProtectedEndpointsRejectWithoutKey:
"""Functional endpoints must reject requests without a valid key."""
def test_stats_no_key(self, api_client):
resp = api_client.get("/stats")
assert resp.status_code == 401
def test_stats_wrong_key(self, api_client):
resp = api_client.get("/stats", headers={"Authorization": "Bearer wrong"})
assert resp.status_code == 401
def test_cache_clear_no_key(self, api_client):
resp = api_client.post("/cache/clear")
assert resp.status_code == 401
def test_quota_no_key(self, api_client):
resp = api_client.get("/quota")
assert resp.status_code == 401
class TestProtectedEndpointsAcceptValidKey:
"""Functional endpoints must accept requests with the correct key."""
def test_stats_with_key(self, api_client):
resp = api_client.get("/stats", headers={"Authorization": "Bearer test-secret-key"})
assert resp.status_code == 200
def test_quota_with_key(self, api_client):
resp = api_client.get("/quota", headers={"Authorization": "Bearer test-secret-key"})
assert resp.status_code == 200
def test_cache_clear_with_key(self, api_client):
resp = api_client.post("/cache/clear", headers={"Authorization": "Bearer test-secret-key"})
assert resp.status_code == 200
class TestAuthDisabledByDefault:
"""When no API key is configured, all endpoints are open."""
def test_stats_open(self, open_client):
resp = open_client.get("/stats")
assert resp.status_code == 200
def test_cache_clear_open(self, open_client):
resp = open_client.post("/cache/clear")
assert resp.status_code == 200
class TestEnvConfig:
"""The proxy should pick up API key auth from the environment."""
def test_env_enables_proxy_key(self, monkeypatch):
monkeypatch.setenv("HEADROOM_PROXY_API_KEY", "env-secret-key")
config = _proxy_config_from_env()
assert config.proxy_api_key == "env-secret-key"
assert config.require_proxy_api_key is True
class TestStartupValidation:
"""Proxy must fail fast if misconfigured."""
def test_raises_when_key_missing(self):
config = ProxyConfig(require_proxy_api_key=True, proxy_api_key=None)
with pytest.raises(ValueError, match="proxy_api_key is required"):
create_app(config)