aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_invite_links.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-25 17:10:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-25 17:10:15 +0200
commit90c69477d5f701158112b3c294eff26312f89da6 (patch)
tree2691ca096da5cf93482bc750c7a03599fbc44dc8 /packages/meshbay-hub/tests/test_invite_links.py
parent8bb94a39609f57be2c486f579849eebb04606808 (diff)
downloadmeshbay-main.tar.gz
feat: invitation links no longer bound to an e-mail addressHEADmain
A link is redeemable by whoever opens it first, so it can be sent by any messaging app. The address is optional (mail + label only); a link lives 7 days, fixed. Adds a Share button; see MESHBAY_DESIGN.md §3.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_invite_links.py')
-rw-r--r--packages/meshbay-hub/tests/test_invite_links.py45
1 files changed, 29 insertions, 16 deletions
diff --git a/packages/meshbay-hub/tests/test_invite_links.py b/packages/meshbay-hub/tests/test_invite_links.py
index 461cf8a..2217cfe 100644
--- a/packages/meshbay-hub/tests/test_invite_links.py
+++ b/packages/meshbay-hub/tests/test_invite_links.py
@@ -63,22 +63,16 @@ def sent(monkeypatch):
# ── Who gets in ──────────────────────────────────────────────────────────────
@pytest.mark.asyncio
-async def test_the_addressed_account_joins_and_nobody_else(client, db_session):
+async def test_whoever_opens_it_first_joins_and_nobody_after(client, db_session):
+ # A link travels by any messaging app, so the address the owner typed binds
+ # nothing: an account registered with another one redeems it.
owner = await _account(client, "link_owner")
gid = await _group(client, owner)
r = await _link(client, owner, gid, email="Invitee@Example.test")
assert r.status_code == 201, r.text
ticket = r.json()["ticket"]
- mallory = await _account(client, "link_mallory")
- for route in ("preview", "redeem"):
- r = await client.post(f"/v1/invite-links/{route}", json={"ticket": ticket},
- headers=mallory["h"])
- assert r.status_code == 403 and r.json()["detail"] == "invite_other_account"
- assert "invitee" not in r.text.lower(), "the refusal must not name the address"
-
- # Registered with the address the owner typed, case aside.
- invitee = await _account(client, "link_invitee", email="invitee@example.test")
+ invitee = await _account(client, "link_invitee", email="elsewhere@example.test")
r = await client.post("/v1/invite-links/preview", json={"ticket": ticket},
headers=invitee["h"])
assert r.status_code == 200, r.text
@@ -102,11 +96,30 @@ async def test_the_addressed_account_joins_and_nobody_else(client, db_session):
GroupMember.group_id == gid))).scalars().all()
assert len(rows) == 2
- # And the one who comes after, even with the right address, gets nothing:
- # a twin account cannot exist (addresses are unique), so try the other.
- r = await client.post("/v1/invite-links/redeem", json={"ticket": ticket},
- headers=mallory["h"])
- assert r.status_code == 404
+ # Whoever comes after, even with the address the owner typed, gets nothing.
+ late = await _account(client, "link_late", email="invitee@example.test")
+ for route in ("preview", "redeem"):
+ r = await client.post(f"/v1/invite-links/{route}", json={"ticket": ticket},
+ headers=late["h"])
+ assert r.status_code == 404 and r.json()["detail"] == "invite_not_valid"
+
+
+@pytest.mark.asyncio
+async def test_a_link_needs_no_address_unless_it_is_mailed(client, db_session, sent):
+ owner = await _account(client, "noaddr_owner")
+ gid = await _group(client, owner)
+ r = await _link(client, owner, gid, email="")
+ assert r.status_code == 201, r.text
+ assert r.json()["email_status"] == "not_requested"
+ row = (await db_session.execute(select(GroupInviteLink))).scalar_one()
+ assert row.email_masked is None
+ listed = (await client.get(f"/v1/groups/{gid}/invite-links",
+ headers=owner["h"])).json()["links"]
+ assert [link["email"] for link in listed] == [""]
+
+ r = await _link(client, owner, gid, email="", send_email=True,
+ node_pk=NODE_PK, code=CODE)
+ assert r.status_code == 422 and sent == []
@pytest.mark.asyncio
@@ -115,7 +128,7 @@ async def test_a_ticket_is_stored_only_as_a_hash(client, db_session):
gid = await _group(client, owner)
ticket = (await _link(client, owner, gid)).json()["ticket"]
row = (await db_session.execute(select(GroupInviteLink))).scalar_one()
- assert ticket not in (row.ticket_hash, row.email_masked, row.email_hash)
+ assert ticket not in (row.ticket_hash, row.email_masked)
assert row.ticket_hash == invite_links.ticket_hash(ticket)
assert "invitee@" not in row.email_masked