diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 19:01:37 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-23 19:01:37 +0200 |
| commit | 3a27a5cd3ae1e752b17d27844cee3d18a35d4498 (patch) | |
| tree | 5af139b818db052939e532f475206ad3df68d063 /packages/meshbay-hub/tests | |
| parent | 1f6b67c7bfc8078991a4607bb0419aea15bfd54c (diff) | |
| download | meshbay-3a27a5cd3ae1e752b17d27844cee3d18a35d4498.tar.gz | |
fix(hub): an invitation no longer holds back its invitee's sign-up code
The per-recipient cooldown was shared by every purpose, so the code a
person asked for by registering within two minutes of an invitation
link was refused, silently. The cooldown is now per family (invitations
vs the account's own steps); the daily cap still counts everything.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_mail_is_not_a_relay.py | 50 |
1 files changed, 43 insertions, 7 deletions
diff --git a/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py b/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py index a3f9a21..157dbd5 100644 --- a/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py +++ b/packages/meshbay-hub/tests/test_mail_is_not_a_relay.py @@ -148,21 +148,57 @@ async def test_one_recipient_has_a_cooldown_and_a_daily_allowance(db_session): "a second purpose walked around the cooldown") cap = MailConfig().destination_daily_cap - key = mail_mod.destination_key(victim) + cool = "cool:invitation:" + mail_mod.destination_key(victim)[len("dest:"):] + + async def past_the_cooldown(): + # Step past it without waiting it out. The daily count is deliberately + # left alone. + row = await db_session.get(MailQuota, cool) + if row: + row.last_sent = None + await db_session.commit() + for _ in range(cap - 1): - # Step past the cooldown without waiting it out. The daily count is - # deliberately left alone. - (await db_session.get(MailQuota, key)).last_sent = None - await db_session.commit() + await past_the_cooldown() assert await _charge(db_session, "invite", victim) - (await db_session.get(MailQuota, key)).last_sent = None - await db_session.commit() + await past_the_cooldown() assert not await _charge(db_session, "invite", victim), ( f"the {cap}-a-day allowance for one recipient was not enforced") @pytest.mark.asyncio +async def test_an_invitation_does_not_stop_the_code_its_invitee_asks_for(db_session): + """ + The regression: an invitation link arrives, the invitee registers a minute + later, and the verification code was refused — silently — by the cooldown + the invitation had just armed. Nobody invited by link could sign up without + waiting two minutes and then asking again, with nothing on screen saying so. + + The two are one conversation, so they do not share a cooldown; each family + still keeps its own, and the daily cap still counts both. + """ + async def charge(purpose, addr): + try: + await mail_mod.reserve(db_session, purpose, addr, sender="owner-1") + except mail_mod.MailRefused: + await db_session.rollback() + return False + await db_session.commit() + return True + + for invitation in ("invite", "invite_link"): + addr = f"newcomer-{invitation}@example.test" + assert await charge(invitation, addr) + assert await charge("registration", addr), ( + f"a verification code was refused after an {invitation} mail") + assert not await charge("password_reset", addr), ( + "within the account family the cooldown must still hold") + assert not await charge("invite", addr), ( + "a second invitation in the same burst must still wait") + + +@pytest.mark.asyncio async def test_the_same_recipient_is_one_recipient_however_it_is_written(db_session): """Case and surrounding space are not a new person.""" assert await _charge(db_session, "registration", "Victim@Example.Test") |