diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 17:01:07 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-23 17:01:07 +0200 |
| commit | cd85808c13926c89a97987d320ac26391eae3267 (patch) | |
| tree | e3e1b12a8ceb8a5ed5b9c9790b6971a2fa2d9d00 /packages/meshbay-hub/tests/test_invite_email_choice.py | |
| parent | 3d8c1acf785ff7389a68cc515a5c9324dee8de41 (diff) | |
| download | meshbay-cd85808c13926c89a97987d320ac26391eae3267.tar.gz | |
feat(hub): make mailing an invitation a remembered choice
A "Send the invitation by e-mail" box under the Invite member field,
checked by default and stored as the invite_email preference. Unchecked,
invite-notify is never called and the hub never sees the code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_invite_email_choice.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_invite_email_choice.py | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_invite_email_choice.py b/packages/meshbay-hub/tests/test_invite_email_choice.py new file mode 100644 index 0000000..e04c31f --- /dev/null +++ b/packages/meshbay-hub/tests/test_invite_email_choice.py @@ -0,0 +1,67 @@ +""" +Whether the hub mails an invitation is the inviter's choice, and it is remembered. + +Mailing it hands the hub the code — `invite-notify` writes it into the message — +which is exactly what §3.4 says the code is for not doing. So the Members tab +offers it as a box, checked by default, and an unchecked box must mean the hub +is never asked. The choice lives in an account preference; a key the hub does +not list is refused, and the box would snap back on every click with nothing on +screen to say why. +""" + +import base64 +import re +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" +SETTINGS = ROOT / "static" / "group-settings.js" +USERS = ROOT / "api" / "users.py" + + +def _pref_key() -> str: + m = re.search(r"^export const INVITE_EMAIL_PREF = '([^']+)';", + SETTINGS.read_text(encoding="utf-8"), re.M) + assert m, "group-settings.js no longer declares INVITE_EMAIL_PREF" + return m.group(1) + + +def test_hub_accepts_the_key_the_client_writes(): + allowed = re.search(r"ALLOWED_PREF_KEYS = frozenset\(\[(.*?)\]\)", + USERS.read_text(encoding="utf-8"), re.S).group(1) + assert f'"{_pref_key()}"' in allowed + + +def test_an_unchecked_box_never_reaches_invite_notify(): + """The only call to `invite-notify` sits inside the branch the box opens.""" + src = SETTINGS.read_text(encoding="utf-8") + calls = [m.start() for m in re.finditer(r"/invite-notify`", src)] + assert len(calls) == 1, "expected exactly one invite-notify call in group-settings.js" + guard = src.rfind("if (inviteByEmail) {", 0, calls[0]) + assert guard != -1, "invite-notify is called without checking the box" + # The guarded block must still be open where the call is: no closing brace + # at the guard's own indentation between the two. + indent = src[src.rfind("\n", 0, guard) + 1:guard] + assert f"\n{indent}}}" not in src[guard:calls[0]], ( + "the box's branch closes before the invite-notify call") + + +@pytest.mark.asyncio +async def test_the_choice_is_remembered(client): + auth_key = base64.b64encode(b"k" * 32).decode() + r = await client.post("/v1/users/register", json={ + "username": "invite_mailer", "email": "invite_mailer@example.test", + "auth_key": auth_key}) + assert r.status_code == 201, r.text + r = await client.post("/v1/users/login", json={ + "username": "invite_mailer", "auth_key": auth_key}) + assert r.status_code == 200, r.text + headers = {"Authorization": f"Bearer {r.json()['access_token']}"} + + key = _pref_key() + r = await client.put(f"/v1/users/me/preferences/{key}", + headers=headers, json={"value": "false"}) + assert r.status_code == 200, r.text + r = await client.get("/v1/users/me/preferences", headers=headers) + assert r.json().get(key) == "false" |