The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
255 lines
9.6 KiB
Python
255 lines
9.6 KiB
Python
"""One mail, one click — the branching welcome/invitation mail, DB-lane.
|
|
|
|
The funnel's common ordering makes a person open TWO messages: one saying their access is approved,
|
|
another asking them to confirm their address. The C option carries the confirmation link inside the
|
|
mail that is already going out, so the usual case is one mail and one click.
|
|
|
|
Also here: the silent no-op that is the most likely explanation for the 2026-09-03 report that a
|
|
confirmation mail "never arrived". Re-registering an address that already had an unconfirmed account
|
|
sent nothing at all, while answering with the same cheerful "check your inbox".
|
|
"""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.models import AuditLog, AuthToken, Invite, User
|
|
|
|
_PW = "correct horse battery"
|
|
|
|
|
|
def _capture(monkeypatch, name):
|
|
"""Record the arguments a given email_mod.send_* was called with."""
|
|
from app import email as email_mod
|
|
|
|
calls: list[tuple] = []
|
|
monkeypatch.setattr(email_mod, name, lambda *a, **kw: calls.append((a, kw)) or True)
|
|
return calls
|
|
|
|
|
|
def _smtp_on(monkeypatch):
|
|
from app import email as email_mod
|
|
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: True)
|
|
|
|
|
|
def _approve(client, invite_id):
|
|
r = client.post(f"/api/admin/invites/{invite_id}/approve")
|
|
assert r.status_code == 200
|
|
return r
|
|
|
|
|
|
# --- the approval mail branches ------------------------------------------------------------------
|
|
|
|
|
|
def test_an_unconfirmed_account_gets_the_confirmation_link_in_the_approval_mail(
|
|
db, make_user, as_user, monkeypatch
|
|
):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_access_approved")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="waiting@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
make_user(email="waiting@example.com", password=_PW, email_verified=False, is_active=False)
|
|
|
|
_approve(as_user(admin), inv.id)
|
|
|
|
(email, _lang, link, has_account, hours), _ = sent[0]
|
|
assert email == "waiting@example.com" and has_account is True
|
|
assert link and "#verify=" in link
|
|
# The whole point: this is the ONLY message they now have to open.
|
|
assert hours == 24 * 7
|
|
|
|
|
|
def test_the_link_in_that_mail_actually_confirms_the_address(db, make_user, as_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_access_approved")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="waiting@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
user = make_user(
|
|
email="waiting@example.com", password=_PW, email_verified=False, is_active=False
|
|
)
|
|
_approve(as_user(admin), inv.id)
|
|
token = sent[0][0][2].split("#verify=")[1]
|
|
|
|
r = as_user(admin).post("/auth/verify", json={"token": token})
|
|
|
|
assert r.status_code == 200
|
|
db.expire_all()
|
|
assert db.get(User, user.id).email_verified is True
|
|
|
|
|
|
def test_that_link_outlives_a_weekend(db, make_user, as_user, monkeypatch):
|
|
# An approval is not a mail anyone sits waiting for; the standard 24h would routinely expire
|
|
# before it is read, and the one-mail promise with it.
|
|
_smtp_on(monkeypatch)
|
|
_capture(monkeypatch, "send_access_approved")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="waiting@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
user = make_user(
|
|
email="waiting@example.com", password=_PW, email_verified=False, is_active=False
|
|
)
|
|
|
|
_approve(as_user(admin), inv.id)
|
|
|
|
tok = db.execute(select(AuthToken).where(AuthToken.user_id == user.id)).scalars().all()[-1]
|
|
assert tok.expires_at > datetime.now(timezone.utc) + timedelta(days=6)
|
|
|
|
|
|
def test_an_already_confirmed_account_is_just_told_to_sign_in(db, make_user, as_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_access_approved")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="ready@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
make_user(email="ready@example.com", password=_PW, is_active=False)
|
|
|
|
_approve(as_user(admin), inv.id)
|
|
|
|
(_, _lang, link, has_account, _), _ = sent[0]
|
|
assert link is None and has_account is True # no link to offer, and none needed
|
|
|
|
|
|
def test_someone_with_no_account_yet_is_told_a_second_mail_is_coming(
|
|
db, make_user, as_user, monkeypatch
|
|
):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_access_approved")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="stranger@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
|
|
_approve(as_user(admin), inv.id)
|
|
|
|
(_, _lang, link, has_account, _), _ = sent[0]
|
|
# Nothing to link to yet. Saying a confirmation mail follows is what makes its ABSENCE
|
|
# noticeable — the failure that went unnoticed on 2026-09-03.
|
|
assert link is None and has_account is False
|
|
|
|
|
|
# --- the deliberate invitation --------------------------------------------------------------------
|
|
|
|
|
|
def test_adding_an_address_to_the_whitelist_mails_nobody(db, make_user, as_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
approved = _capture(monkeypatch, "send_access_approved")
|
|
invited = _capture(monkeypatch, "send_access_invitation")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
|
|
r = as_user(admin).post("/api/admin/invites", json={"email": "maybe@example.com"})
|
|
|
|
# Whitelisting opens a door as a possibility — often already agreed in person — and an address
|
|
# can sit approved for weeks. A mail fired automatically here would be nudging someone who never
|
|
# asked to be nudged.
|
|
assert r.status_code == 200 and r.json()["invited_at"] is None
|
|
assert approved == [] and invited == []
|
|
|
|
|
|
def test_the_admin_can_then_invite_them_on_purpose(db, make_user, as_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
invited = _capture(monkeypatch, "send_access_invitation")
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="maybe@example.com", status="approved", decided_by=admin.email)
|
|
db.add(inv)
|
|
db.commit()
|
|
|
|
r = as_user(admin).post(f"/api/admin/invites/{inv.id}/invite")
|
|
|
|
assert r.status_code == 200 and r.json()["invited_at"] is not None
|
|
assert invited[0][0][0] == "maybe@example.com"
|
|
# Recorded on the row, so "did I already write to them?" has an answer where the decision is
|
|
# made — the wrong answer means mailing a real person twice.
|
|
db.expire_all()
|
|
assert db.get(Invite, inv.id).invited_at is not None
|
|
assert "invite_send" in db.execute(select(AuditLog.action)).scalars().all()
|
|
|
|
|
|
def test_an_undecided_request_cannot_be_invited(db, make_user, as_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="asking@example.com", status="pending")
|
|
db.add(inv)
|
|
db.commit()
|
|
|
|
r = as_user(admin).post(f"/api/admin/invites/{inv.id}/invite")
|
|
|
|
assert r.status_code == 409 and r.json()["detail"]["reason"] == "not_approved"
|
|
|
|
|
|
def test_the_invitation_says_so_when_the_instance_cannot_send_mail(
|
|
db, make_user, as_user, monkeypatch
|
|
):
|
|
from app import email as email_mod
|
|
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: False)
|
|
admin = make_user(email="admin@example.com", role="admin")
|
|
inv = Invite(email="maybe@example.com", status="approved", decided_by=admin.email)
|
|
db.add(inv)
|
|
db.commit()
|
|
|
|
r = as_user(admin).post(f"/api/admin/invites/{inv.id}/invite")
|
|
|
|
assert r.status_code == 409 and r.json()["detail"]["reason"] == "email_off"
|
|
db.expire_all()
|
|
assert db.get(Invite, inv.id).invited_at is None # nothing sent, nothing recorded
|
|
|
|
|
|
# --- the silent register no-op --------------------------------------------------------------------
|
|
|
|
|
|
def test_registering_again_re_sends_the_link_instead_of_sending_nothing(db, client, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_verify_email")
|
|
assert (
|
|
client.post(
|
|
"/auth/register", json={"email": "twice@example.com", "password": _PW}
|
|
).status_code
|
|
== 200
|
|
)
|
|
assert len(sent) == 1
|
|
|
|
r = client.post("/auth/register", json={"email": "twice@example.com", "password": _PW})
|
|
|
|
# The old behaviour was to return in silence: same cheerful "check your inbox", no mail. That is
|
|
# the most likely explanation for the 2026-09-03 confirmation mail that "never arrived" — none
|
|
# was ever sent. The response stays uniform; only the dead end is gone.
|
|
assert r.status_code == 200 and r.json() == {"ok": True, "status": "ok"}
|
|
assert len(sent) == 2
|
|
assert sent[1][0][0] == "twice@example.com"
|
|
|
|
|
|
def test_registering_again_does_not_create_a_second_account(db, client, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
_capture(monkeypatch, "send_verify_email")
|
|
for _ in range(2):
|
|
assert (
|
|
client.post(
|
|
"/auth/register", json={"email": "twice@example.com", "password": _PW}
|
|
).status_code
|
|
== 200
|
|
)
|
|
|
|
db.expire_all()
|
|
rows = db.execute(select(User).where(User.email == "twice@example.com")).scalars().all()
|
|
assert len(rows) == 1
|
|
|
|
|
|
def test_a_confirmed_account_still_gets_nothing(db, client, make_user, monkeypatch):
|
|
_smtp_on(monkeypatch)
|
|
sent = _capture(monkeypatch, "send_verify_email")
|
|
make_user(email="done@example.com", password=_PW) # already verified
|
|
|
|
r = client.post("/auth/register", json={"email": "done@example.com", "password": _PW})
|
|
|
|
# There is nothing to confirm, so there is nothing to send — and mailing "confirm your address"
|
|
# to someone who already did would be a lie.
|
|
assert r.status_code == 200
|
|
assert sent == []
|