""" Offline unit tests: every tenant-data endpoint must REQUIRE an explicit user. A request without a user (query param or body field) must be rejected with 422 before any service is touched. Empty/whitespace users are also rejected. No external services are contacted: validation failures short-circuit the request before the endpoint body executes. """ import pytest from fastapi.testclient import TestClient from src.main import app from src.core.dependencies import verify_api_key, verify_browser_request @pytest.fixture(scope="module") def client(): """TestClient with auth stubbed out (no lifespan startup).""" app.dependency_overrides[verify_api_key] = lambda: "test-key" app.dependency_overrides[verify_browser_request] = lambda: "test-user" try: # No context manager: startup/lifespan events are NOT triggered, # so no connections to external services are attempted. yield TestClient(app) finally: app.dependency_overrides.pop(verify_api_key, None) app.dependency_overrides.pop(verify_browser_request, None) QUERY_PARAM_ENDPOINTS = [ ("GET", "/stats"), ("POST", "/query/semantic?query=test"), ("POST", "/query/graph?query=MATCH%20(n)%20RETURN%20n"), ("GET", "/wiki/pages"), ("GET", "/wiki/pages/1"), ("PUT", "/wiki/pages/1"), ("DELETE", "/wiki/pages/1"), ("GET", "/wiki/search?q=test"), ("GET", "/wiki/dossiers"), ("POST", "/vector/update-from-page/1"), ("DELETE", "/vector/pages/1"), ("GET", "/graph/nodes"), ("POST", "/graph/update-from-page/1"), ("POST", "/graph/generate-entity-pages"), ("POST", "/ingest/all"), ("GET", "/volatile/stats"), ("GET", "/volatile/search?q=test"), ("POST", "/volatile/store?namespace=weather&key=test"), ("GET", "/volatile/weather/rotterdam"), ("DELETE", "/volatile/weather/rotterdam"), ("POST", "/documents/webhook-simple?doc_url=http://x/documents/1/"), ] @pytest.mark.unit class TestUserQueryParamRequired: """Endpoints with a user query parameter must 422 without it.""" @pytest.mark.parametrize("method,path", QUERY_PARAM_ENDPOINTS) def test_missing_user_is_422(self, client, method, path): response = client.request(method, path, json={}) assert response.status_code == 422, ( f"{method} {path} returned {response.status_code}, expected 422" ) @pytest.mark.parametrize("blank", ["", " ", "%20%20"]) def test_blank_user_is_422(self, client, blank): response = client.get(f"/wiki/pages?user={blank}") assert response.status_code == 422 def test_hybrid_query_missing_user_is_422(self, client): response = client.post("/query/hybrid", json={"query": "test"}) assert response.status_code == 422 def test_hybrid_query_whitespace_user_is_422(self, client): response = client.post("/query/hybrid?user=%20", json={"query": "test"}) assert response.status_code == 422 @pytest.mark.unit class TestUserBodyFieldRequired: """Request models with a user field must reject missing/blank values.""" def test_ingest_page_missing_user_is_422(self, client): response = client.post("/ingest/page", json={"page_id": 1}) assert response.status_code == 422 def test_ingest_page_blank_user_is_422(self, client): response = client.post("/ingest/page", json={"page_id": 1, "user": " "}) assert response.status_code == 422 def test_ingest_batch_missing_user_is_422(self, client): response = client.post("/ingest/batch", json={"page_ids": [1]}) assert response.status_code == 422 def test_wiki_create_page_missing_user_is_422(self, client): response = client.post( "/wiki/pages", json={"title": "T", "path": "/t", "content": "c"}, ) assert response.status_code == 422 def test_wiki_smart_create_missing_user_is_422(self, client): response = client.post("/wiki/pages/smart-create", json={"topic": "T"}) assert response.status_code == 422 def test_vector_search_missing_user_is_422(self, client): response = client.post("/vector/search", json={"query": "test"}) assert response.status_code == 422 def test_graph_query_missing_user_is_422(self, client): response = client.post("/graph/query", json={"query": "MATCH (n) RETURN n"}) assert response.status_code == 422 def test_rag_search_missing_user_is_422(self, client): response = client.post("/rag/search", json={"query": "test"}) assert response.status_code == 422