Files
devplacepy/tests/api/auth/terms.py
T
retoorandClaude Opus 5 91fac7fd67 Gate blocked actions behind an in-place terms acceptance dialog
A member whose account has not accepted the terms in force now gets one
dialog on the action they attempted instead of a dead-end refusal. The
client handler is the single TermsGate, wired into every Http POST helper
so the four optimistic controllers cannot swallow the gate into an error
flash, and the original request is replayed once the acceptance is
recorded. Reading the site and deleting an account stay unblocked.

apple.md is the source brief the compliance research documents reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:25:57 +02:00

206 lines
7.1 KiB
Python

# retoor <retoor@molodetz.nl>
import time
import requests
from devplacepy.database import get_setting, get_table, refresh_snapshot, set_setting
from devplacepy.routers.auth.terms import TERMS_ACCEPTANCE_CODE
from tests.conftest import BASE_URL
JSON = {"Accept": "application/json"}
_counter = [0]
def _unique(prefix="terms"):
_counter[0] += 1
return f"{prefix}{int(time.time() * 1000)}{_counter[0]}"
def _signup(**overrides):
name = _unique()
payload = {
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
"birth_date": "1990-01-01",
"accept_terms": "1",
}
payload.update(overrides)
session = requests.Session()
response = session.post(
f"{BASE_URL}/auth/signup", data=payload, headers=JSON, allow_redirects=False
)
return session, payload["username"], response
def test_signup_without_accepting_the_terms_is_refused(app_server):
_, name, response = _signup(accept_terms="")
assert response.status_code == 422
assert "accept" in response.text.lower()
refresh_snapshot()
assert get_table("users").find_one(username=name) is None
def test_signup_below_the_minimum_age_is_refused(app_server):
from datetime import date
young = date.today().replace(year=date.today().year - 12).isoformat()
_, name, response = _signup(birth_date=young)
assert response.status_code == 422
assert "years old" in response.text
refresh_snapshot()
assert get_table("users").find_one(username=name) is None
def test_signup_stores_the_band_and_the_acceptance_but_not_the_date(app_server):
_, name, response = _signup(birth_date="1990-06-15")
assert response.status_code == 200, response.text
refresh_snapshot()
row = get_table("users").find_one(username=name)
assert row["age_band"] == "adult"
assert row["terms_version"] == (get_setting("terms_version", "1") or "1")
assert row["terms_accepted_at"]
assert "1990-06-15" not in str(dict(row))
consents = {
entry["kind"]
for entry in get_table("user_consents").find(
owner_kind="user", owner_id=row["uid"], state="granted", deleted_at=None
)
}
assert {"terms", "privacy", "activity_recording"} <= consents
assert "ai_third_party" not in consents
def test_the_acceptance_page_reports_both_versions(app_server):
session, name, response = _signup()
assert response.status_code == 200
page = session.get(f"{BASE_URL}/auth/accept-terms", headers=JSON)
assert page.status_code == 200, page.text
body = page.json()
in_force = get_setting("terms_version", "1") or "1"
assert body["terms_version"] == in_force
assert body["accepted_version"] == in_force
assert "consents" not in body
refresh_snapshot()
assert get_table("users").find_one(username=name)["terms_version"] == in_force
def test_the_acceptance_page_is_signed_in_only(app_server):
assert (
requests.get(f"{BASE_URL}/auth/accept-terms", headers=JSON).status_code == 401
)
def test_the_acceptance_page_shows_the_gap_after_a_bump(app_server):
session, _, response = _signup()
assert response.status_code == 200
original = get_setting("terms_version", "1") or "1"
set_setting("terms_version", f"{original}-gap")
try:
body = {}
for _ in range(40):
body = session.get(f"{BASE_URL}/auth/accept-terms", headers=JSON).json()
if body.get("terms_version") == f"{original}-gap":
break
time.sleep(0.5)
assert body["terms_version"] == f"{original}-gap"
assert body["accepted_version"] == original
finally:
set_setting("terms_version", original)
def test_bumping_the_version_gates_writes_but_never_reads(app_server):
session, name, response = _signup()
assert response.status_code == 200
original = get_setting("terms_version", "1") or "1"
set_setting("terms_version", f"{original}-bumped")
try:
blocked = None
for _ in range(40):
blocked = session.post(
f"{BASE_URL}/posts/create",
data={
"title": "gated",
"content": "This write must be gated by the terms version.",
},
headers=JSON,
)
if blocked.status_code == 403:
break
time.sleep(0.5)
assert blocked.status_code == 403
assert session.get(f"{BASE_URL}/feed", headers=JSON).status_code == 200
assert session.get(f"{BASE_URL}/docs/terms.html").status_code == 200
assert (
session.get(f"{BASE_URL}/profile/{name}/delete", headers=JSON).status_code
== 200
)
accepted = session.post(f"{BASE_URL}/auth/accept-terms", headers=JSON)
assert accepted.status_code == 200
refresh_snapshot()
assert (
get_table("users").find_one(username=name)["terms_version"]
== f"{original}-bumped"
)
allowed = session.post(
f"{BASE_URL}/posts/create",
data={"title": "ok", "content": "Writing works again after acceptance."},
headers=JSON,
)
assert allowed.status_code == 200
finally:
set_setting("terms_version", original)
def test_the_refusal_tells_a_fetch_client_how_to_resolve_it(app_server):
session, _, response = _signup()
assert response.status_code == 200
original = get_setting("terms_version", "1") or "1"
bumped = f"{original}-contract"
set_setting("terms_version", bumped)
try:
blocked = None
for _ in range(40):
blocked = session.post(
f"{BASE_URL}/posts/create",
data={"title": "gated", "content": "This write must be gated."},
headers=JSON,
)
if blocked.status_code == 403:
break
time.sleep(0.5)
assert blocked.status_code == 403
error = blocked.json()["error"]
assert error["code"] == TERMS_ACCEPTANCE_CODE
assert error["redirect"] == "/auth/accept-terms"
assert error["terms_version"] == bumped
assert error["message"]
finally:
set_setting("terms_version", original)
def test_a_browser_form_post_is_redirected_to_the_acceptance_page(app_server):
session, _, response = _signup()
assert response.status_code == 200
original = get_setting("terms_version", "1") or "1"
set_setting("terms_version", f"{original}-redirect")
try:
blocked = None
for _ in range(40):
blocked = session.post(
f"{BASE_URL}/posts/create",
data={"title": "gated", "content": "This write must be gated."},
headers={"Accept": "text/html"},
allow_redirects=False,
)
if blocked.status_code == 303:
break
time.sleep(0.5)
assert blocked.status_code == 303
assert blocked.headers["location"] == "/auth/accept-terms"
finally:
set_setting("terms_version", original)