ai / backend /tests /test_auth.py
Aissugur Kurganbekov
Claude Opus 4.8
Audit-driven hardening + Google sign-in
90adb95
Raw History Blame Contribute Delete
5.56 kB
"""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