"""End-to-end tests for v0.13.0 / roadmap item #11 — cookie / privacy consent. Covers the §17 endpoints (`GET` / `PUT /api/users/me/cookie-consent`) and the §14.5 storage contract: * GET on a fresh user returns no-choice-yet (recorded_at is None, essential=True, analytics=False, other=False). * PUT writes a row, stamps recorded_at, and the choice survives. * PUT with `analytics=true, other=false` round-trips faithfully. * `essential` is permanently true at the API surface — a PUT that requests essential=false is still persisted with essential=true. * The endpoint requires authentication (401 for anon). * A second PUT updates the existing row in place (single row per user, recorded_at re-stamps). * Choice persists across sign-out / sign-in. """ from __future__ import annotations import pytest from test_propose_vertical import ( # noqa: F401 FakeGitea, app_with_fake_gitea, provision_user_row, sign_in_as, tmp_env, ) def test_get_cookie_consent_fresh_user_has_no_choice(app_with_fake_gitea): from fastapi.testclient import TestClient app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/users/me/cookie-consent") assert r.status_code == 200, r.text body = r.json() assert body["essential"] is True assert body["analytics"] is False assert body["other"] is False assert body["recorded_at"] is None def test_put_cookie_consent_records_choice(app_with_fake_gitea): from fastapi.testclient import TestClient app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": True, "other": False}, ) assert r.status_code == 200, r.text body = r.json() assert body["ok"] is True assert body["essential"] is True assert body["analytics"] is True assert body["other"] is False assert body["recorded_at"] is not None # Round-trip the read endpoint. r = client.get("/api/users/me/cookie-consent") body = r.json() assert body["essential"] is True assert body["analytics"] is True assert body["other"] is False assert body["recorded_at"] is not None def test_put_cookie_consent_forces_essential_true(app_with_fake_gitea): """§14.5: `essential` is permanently true at the API surface. A request that sets it to false is accepted (for symmetry with the other two flags) but persisted as true. """ from fastapi.testclient import TestClient from app import db app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.put( "/api/users/me/cookie-consent", json={"essential": False, "analytics": False, "other": False}, ) assert r.status_code == 200, r.text assert r.json()["essential"] is True # Confirm at the schema layer too — the persisted row has essential=1. row = db.conn().execute( "SELECT essential FROM cookie_consent WHERE user_id = ?", (2,), ).fetchone() assert row["essential"] == 1 def test_cookie_consent_requires_auth(app_with_fake_gitea): from fastapi.testclient import TestClient app, _fake = app_with_fake_gitea with TestClient(app) as client: r = client.get("/api/users/me/cookie-consent") assert r.status_code == 401, r.text r = client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": True, "other": True}, ) assert r.status_code == 401, r.text def test_put_cookie_consent_upserts_in_place(app_with_fake_gitea): """A second PUT updates the existing row rather than inserting a new one. Verifies the §14.5 single-row-per-user shape. """ from fastapi.testclient import TestClient from app import db app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": True, "other": False}, ) client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": False, "other": True}, ) rows = db.conn().execute( "SELECT analytics, other_cookies FROM cookie_consent WHERE user_id = ?", (2,), ).fetchall() assert len(rows) == 1 assert rows[0]["analytics"] == 0 assert rows[0]["other_cookies"] == 1 def test_cookie_consent_persists_across_sign_out_in(app_with_fake_gitea): """§14.5 precedence: the server row survives sign-out / sign-in. """ from fastapi.testclient import TestClient app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": True, "other": True}, ) # Simulate sign-out by clearing the session cookie. client.cookies.clear() # Anonymous viewer cannot read. r = client.get("/api/users/me/cookie-consent") assert r.status_code == 401 # Sign back in as Alice. The server row is still there. sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/users/me/cookie-consent") body = r.json() assert body["analytics"] is True assert body["other"] is True assert body["recorded_at"] is not None def test_two_users_have_independent_rows(app_with_fake_gitea): from fastapi.testclient import TestClient app, _fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") provision_user_row(user_id=3, login="bob", role="contributor") sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": True, "other": False}, ) sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob", role="contributor") client.put( "/api/users/me/cookie-consent", json={"essential": True, "analytics": False, "other": False}, ) # Each user reads their own row. r = client.get("/api/users/me/cookie-consent").json() assert r["analytics"] is False # Bob's sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/users/me/cookie-consent").json() assert r["analytics"] is True # Alice's