"""The shared demo account's refusal says WHY, in the reader's language (deep-review round 6). `require_human` answered `403 "Not available in the demo account."` — a plain string. `req` leaves 403 to the caller, and round 5 taught `notifyActionError` to discard a plain-string detail in favour of the caller's already-translated fallback, so from that commit on the demo user got a generic "couldn't do that" and no hint that the demo account was the reason. Not every affordance behind this gate is UI-gated on `is_demo` either — the comfort shelf is not, so the pin button is live. A structured `{code: "demo"}` is what `localizeDetail` can turn into `errors.demo` in both locales. Pinned at all three sites: the shared dependency and the two auth routes that hand-roll the same check, because an asymmetric fix is the smell this repo bans. Needs the DB. """ from app.models import User def test_every_demo_refusal_carries_the_same_structured_reason(db, make_user, as_user): demo = make_user(email="demo@example.com", is_demo=True) client = as_user(demo) for method, url, body in [ # `require_human` itself, through an affordance the UI does NOT gate on is_demo. ("post", "/api/discover/pins", {"kind": "video", "ref": "v1"}), # The two routes that repeat the check inline rather than depending on the gate. ("post", "/auth/logout-others", None), ("post", "/auth/set-password", {"password": "a long enough password"}), ]: r = getattr(client, method)(url, **({"json": body} if body is not None else {})) assert r.status_code == 403, (url, r.text) assert r.json()["detail"] == {"code": "demo"}, url def test_a_real_account_is_not_refused_by_the_gate(db, make_user, as_user): """The other half: the structured detail must not come from having broken the gate open the other way. A non-demo account gets past `require_human` and fails (or succeeds) on its own merits — anything but a 403 demo refusal.""" client = as_user(make_user(email="real@example.com")) r = client.post("/auth/logout-others") assert r.status_code == 200, r.text assert not isinstance(r.json().get("detail"), dict) def test_the_gate_reads_the_flag_and_not_the_email(db): """`is_demo` is a column, not a convention about the address — a real account that happens to be called demo@ must not be refused.""" from app.auth import require_human assert require_human(User(email="demo@example.com", is_demo=False)) is not None