File size: 5,558 Bytes
165a274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90adb95
165a274
90adb95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165a274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""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