"""E2E: Authentication enforcement. Tests that protected endpoints require valid auth, and public endpoints work without auth. Uses the `client` fixture (which has the correct auth header set), and helpers to make unauthenticated requests by temporarily removing the header. """ import pytest from tests.e2e.conftest import API_KEY def _unauth_get(client, url): """Make a GET request without the Authorization header.""" saved = client.headers.pop("Authorization", None) try: return client.get(url) finally: if saved is not None: client.headers["Authorization"] = saved def _unauth_request(client, method, url, **kwargs): """Make a request without the Authorization header.""" saved = client.headers.pop("Authorization", None) try: return client.request(method, url, **kwargs) finally: if saved is not None: client.headers["Authorization"] = saved def _with_header(client, method, url, auth_value, **kwargs): """Make a request with a custom Authorization header.""" saved = client.headers.get("Authorization") client.headers["Authorization"] = auth_value try: return client.request(method, url, **kwargs) finally: if saved is not None: client.headers["Authorization"] = saved else: client.headers.pop("Authorization", None) class TestAuthEnforcement: """Verify API key authentication is enforced correctly.""" def test_request_without_auth_returns_401(self, client): """Protected endpoint without Authorization header returns 401.""" resp = _unauth_get(client, "/api/v1/devices") assert resp.status_code == 401 def test_request_with_wrong_key_returns_401(self, client): """Protected endpoint with an incorrect API key returns 401.""" resp = _with_header( client, "GET", "/api/v1/devices", auth_value="Bearer wrong-key-12345", ) assert resp.status_code == 401 def test_request_with_correct_key_returns_200(self, client): """Protected endpoint with valid API key succeeds.""" resp = client.get("/api/v1/devices") assert resp.status_code == 200 def test_health_endpoint_is_public(self, client): """Health check does not require authentication.""" resp = _unauth_get(client, "/health") assert resp.status_code == 200 data = resp.json() assert data["status"] == "healthy" def test_version_endpoint_is_public(self, client): """Version endpoint does not require authentication.""" resp = _unauth_get(client, "/api/v1/version") assert resp.status_code == 200 data = resp.json() assert "version" in data assert "api_version" in data def test_post_without_auth_returns_401(self, client): """Creating a device without auth fails.""" resp = _unauth_request( client, "POST", "/api/v1/devices", json={ "name": "Unauthorized Device", "url": "mock://test", "device_type": "mock", "led_count": 10, }, ) assert resp.status_code == 401 def test_delete_without_auth_returns_401(self, client): """Deleting a device without auth fails.""" resp = _unauth_request(client, "DELETE", "/api/v1/devices/some_id") assert resp.status_code == 401 def test_backup_without_auth_returns_401(self, client): """Backup endpoint requires authentication.""" resp = _unauth_get(client, "/api/v1/system/backup") assert resp.status_code == 401 def test_color_strip_sources_without_auth_returns_401(self, client): """Color strip source listing requires authentication.""" resp = _unauth_get(client, "/api/v1/color-strip-sources") assert resp.status_code == 401 def test_output_targets_without_auth_returns_401(self, client): """Output target listing requires authentication.""" resp = _unauth_get(client, "/api/v1/output-targets") assert resp.status_code == 401 def test_malformed_bearer_token_returns_401_or_403(self, client): """A malformed Authorization header is rejected.""" resp = _with_header( client, "GET", "/api/v1/devices", auth_value="just-a-key", ) # FastAPI's HTTPBearer returns 403 for malformed format, # or 401 depending on auto_error setting. Accept either. assert resp.status_code in (401, 403)