"""Authentication, protected-endpoint, and per-user isolation tests.""" def test_register_returns_token_and_no_password_hash(anon_client): res = anon_client.post( "/auth/register", json={"email": "a@b.com", "password": "password123"} ) assert res.status_code == 201 body = res.json() assert body["access_token"] assert body["user"]["email"] == "a@b.com" assert "password_hash" not in body["user"] def test_register_duplicate_returns_409(anon_client): payload = {"email": "dup@b.com", "password": "password123"} assert anon_client.post("/auth/register", json=payload).status_code == 201 assert anon_client.post("/auth/register", json=payload).status_code == 409 def test_register_rejects_bad_email(anon_client): res = anon_client.post( "/auth/register", json={"email": "not-an-email", "password": "password123"} ) assert res.status_code == 422 def test_register_rejects_short_password(anon_client): res = anon_client.post( "/auth/register", json={"email": "c@b.com", "password": "short"} ) assert res.status_code == 422 def test_login_good_bad_and_missing(anon_client): anon_client.post( "/auth/register", json={"email": "login@b.com", "password": "password123"} ) ok = anon_client.post( "/auth/login", json={"email": "login@b.com", "password": "password123"} ) assert ok.status_code == 200 and ok.json()["access_token"] wrong = anon_client.post( "/auth/login", json={"email": "login@b.com", "password": "wrongpassword"} ) assert wrong.status_code == 401 missing = anon_client.post( "/auth/login", json={"email": "nobody@b.com", "password": "password123"} ) assert missing.status_code == 401 def test_me_returns_current_user(client): res = client.get("/auth/me") assert res.status_code == 200 assert res.json()["email"] == "user@example.com" assert "password_hash" not in res.json() def test_protected_endpoints_require_a_token(anon_client): assert anon_client.get("/documents").status_code == 401 assert anon_client.post( "/ask", json={"document_id": 1, "question": "hello?"} ).status_code == 401 assert anon_client.get("/auth/me").status_code == 401 def test_invalid_token_is_rejected(anon_client): anon_client.headers.update({"Authorization": "Bearer not.a.real.jwt"}) assert anon_client.get("/documents").status_code == 401 def test_only_health_is_public(anon_client): assert anon_client.get("/health").status_code == 200 # demo/seed and evaluate now require auth (resource-amplification guard). assert anon_client.post("/demo/seed").status_code == 401 assert anon_client.post("/evaluate").status_code == 401 def test_health_exposes_google_client_id(anon_client): body = anon_client.get("/health").json() assert "google_client_id" in body assert body["google_client_id"] is None # unset in tests -> Google off def test_google_login_disabled_returns_503(anon_client): assert anon_client.post("/auth/google", json={"credential": "x"}).status_code == 503 def test_google_login_creates_user(anon_client, monkeypatch): from app import config monkeypatch.setattr(config, "GOOGLE_ENABLED", True) monkeypatch.setattr(config, "GOOGLE_CLIENT_ID", "test-client-id") import google.oauth2.id_token as gid monkeypatch.setattr( gid, "verify_oauth2_token", lambda *a, **k: { "iss": "accounts.google.com", "email": "google.user@example.com", "email_verified": True, }, ) res = anon_client.post("/auth/google", json={"credential": "valid-id-token"}) assert res.status_code == 200 body = res.json() assert body["access_token"] assert body["user"]["email"] == "google.user@example.com" def test_history_is_isolated_per_user(client, second_client): client.post("/demo/seed") docs = client.get("/documents").json() did = next(d["id"] for d in docs if d["filename"] == "sample_invoice.txt") client.post("/ask", json={"document_id": did, "question": "What is the total amount due?"}) second_client.post("/ask", json={"document_id": did, "question": "Bob private question?"}) a_hist = client.get("/history").json() assert any("total amount due" in h["question"].lower() for h in a_hist) assert all("bob private question" not in h["question"].lower() for h in a_hist) b_hist = second_client.get("/history").json() assert any("bob private question" in h["question"].lower() for h in b_hist) assert all("total amount due" not in h["question"].lower() for h in b_hist) def test_documents_are_isolated_per_user(client, second_client): content = b"Invoice INV-9. Total Amount Due $2,413.98. Payment terms net 30." doc = client.post( "/documents/upload", files={"file": ("inv.txt", content, "text/plain")} ).json() did = doc["id"] # Owner sees and can use the document. assert any(d["id"] == did for d in client.get("/documents").json()) assert client.get(f"/documents/{did}").status_code == 200 # A different user cannot see it and gets 404 on every direct access. assert all(d["id"] != did for d in second_client.get("/documents").json()) assert second_client.get(f"/documents/{did}").status_code == 404 assert second_client.post( "/ask", json={"document_id": did, "question": "total due?"} ).status_code == 404 assert second_client.delete(f"/documents/{did}").status_code == 404