"""Smoke-Tests fuer alle API-Endpoints: Auth-Coverage. Pruef, dass jeder geschuetzte Endpoint ohne Auth-Header 401/403 liefert - verhindert, dass jemand versehentlich einen Endpoint ohne `get_current_admin` schreibt und ihn oeffentlich macht. """ import pytest from fastapi.testclient import TestClient @pytest.fixture(scope="module") def client(): from main import app # raise_server_exceptions=False -> Exceptions werden als 500 ausgeliefert. # Wir testen nur, dass Auth korrekt vor DB-Aufrufen greift. return TestClient(app, raise_server_exceptions=False) # (method, path, expected_status) # Auth-geschuetzte Endpoints -> 401 (HTTPBearer ohne credentials wirft 403, # aber FastAPI HTTPBearer auto_error=True liefert 403; wir akzeptieren beides). AUTH_PROTECTED = [ ("GET", "/api/orgs"), ("POST", "/api/orgs"), ("GET", "/api/orgs/1"), ("PUT", "/api/orgs/1"), ("DELETE", "/api/orgs/1"), ("GET", "/api/licenses"), ("POST", "/api/licenses"), ("PUT", "/api/licenses/1/revoke"), ("PUT", "/api/licenses/1/extend"), ("GET", "/api/licenses/expiring"), ("GET", "/api/users"), ("POST", "/api/users"), ("PUT", "/api/users/1/deactivate"), ("PUT", "/api/users/1/activate"), ("PUT", "/api/users/1/globe-access"), ("PUT", "/api/users/1/network-access"), ("PUT", "/api/users/1/role"), ("DELETE", "/api/users/1"), ("GET", "/api/dashboard/stats"), ("GET", "/api/sources/meta"), ("GET", "/api/sources/global"), ("POST", "/api/sources/global"), ("PUT", "/api/sources/global/1"), ("DELETE", "/api/sources/global/1"), ("GET", "/api/sources/global/stats"), ("GET", "/api/sources/tenant"), ("POST", "/api/sources/tenant/1/promote"), ("POST", "/api/sources/tenant/bulk-promote"), ("POST", "/api/sources/discover"), ("POST", "/api/sources/discover/add"), ("GET", "/api/sources/health"), ("GET", "/api/sources/suggestions"), ("PUT", "/api/sources/suggestions/1"), ("POST", "/api/sources/health/run"), ("POST", "/api/sources/health/run-stream"), ("POST", "/api/sources/health/search-fix/1"), ("GET", "/api/token-usage/overview"), ("GET", "/api/token-usage/1"), ("GET", "/api/token-usage/1/current"), ("PUT", "/api/token-usage/budget/1"), ("GET", "/api/audit-log"), ("GET", "/api/audit-log/distinct"), ] @pytest.mark.parametrize("method,path", AUTH_PROTECTED) def test_endpoint_requires_auth(client, method, path): """Ohne Authorization-Header muss jeder Endpoint 401 oder 403 liefern.""" r = client.request(method, path, json={}) assert r.status_code in (401, 403), ( f"{method} {path} sollte 401/403 sein, war {r.status_code}: {r.text[:200]}" ) def test_magic_link_endpoint_is_public(client): """/api/auth/magic-link ist absichtlich oeffentlich (sonst kann sich keiner einloggen).""" r = client.post("/api/auth/magic-link", json={"email": "stranger@example.com"}) # Mit gueltigem JSON -> 200 generische Antwort, ohne Auth-Header. # 200 erwartet (Anti-Enumeration), aber DB-Aufruf koennte mit /tmp/x.db failen -> # akzeptieren wir auch 500. Wir wollen nur sicherstellen, dass NICHT 401/403 kommt. assert r.status_code != 401 and r.status_code != 403 def test_verify_endpoint_is_public(client): """/api/auth/verify ist auch oeffentlich (ohne Token koennen wir keinen JWT haben).""" r = client.post("/api/auth/verify", json={"token": "x" * 20}) assert r.status_code != 401 and r.status_code != 403 def test_static_routes_public(client): """/ und /dashboard liefern HTML ohne Auth (Frontend regelt Login-Redirect).""" r = client.get("/") assert r.status_code == 200 r = client.get("/dashboard") assert r.status_code == 200