diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-25 17:10:15 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-25 17:10:15 +0200 |
| commit | 90c69477d5f701158112b3c294eff26312f89da6 (patch) | |
| tree | 2691ca096da5cf93482bc750c7a03599fbc44dc8 /packages/meshbay-hub/tests | |
| parent | 8bb94a39609f57be2c486f579849eebb04606808 (diff) | |
| download | meshbay-90c69477d5f701158112b3c294eff26312f89da6.tar.gz | |
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')
| -rw-r--r-- | packages/meshbay-hub/tests/test_group_purge.py | 2 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_invite_link_client.py | 4 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_invite_links.py | 45 |
3 files changed, 33 insertions, 18 deletions
diff --git a/packages/meshbay-hub/tests/test_group_purge.py b/packages/meshbay-hub/tests/test_group_purge.py index 9c6a664..40d2d54 100644 --- a/packages/meshbay-hub/tests/test_group_purge.py +++ b/packages/meshbay-hub/tests/test_group_purge.py @@ -84,7 +84,7 @@ async def _group_with_everything(client, db, owner_token: str, member: str, name ip_address="192.0.2.1")) owner_id = (await db.execute(select(Group.admin_id).where(Group.id == gid))).scalar_one() db.add(GroupInviteLink(group_id=gid, created_by=owner_id, ticket_hash=gid[:8] * 8, - email_hash="1" * 64, email_masked="m***@e***.com", + email_masked="m***@e***.com", expires_at=datetime.now(UTC) + timedelta(days=1), redeemed_by=member_id, redeemed_at=datetime.now(UTC))) await db.commit() diff --git a/packages/meshbay-hub/tests/test_invite_link_client.py b/packages/meshbay-hub/tests/test_invite_link_client.py index 2223ad8..313e224 100644 --- a/packages/meshbay-hub/tests/test_invite_link_client.py +++ b/packages/meshbay-hub/tests/test_invite_link_client.py @@ -188,7 +188,9 @@ def test_the_members_tab_sends_the_code_only_for_the_mail(): sends = [m.start() for m in re.finditer(r"code: node\.code", settings)] assert len(sends) == 1 before = settings[settings.rfind("\n", 0, sends[0] - 200):sends[0]] - assert "inviteByEmail ?" in before, "the code reaches the hub only when the box asks" + assert "mailIt ?" in before, "the code reaches the hub only when the box asks" + assert "const mailIt = inviteByEmail && Boolean(email);" in settings, ( + "and the box asks only when there is an address to mail") def test_signing_out_forgets_the_invitation(): 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 |