aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_invite_email_choice.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-23 17:01:07 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-23 17:01:07 +0200
commitcd85808c13926c89a97987d320ac26391eae3267 (patch)
treee3e1b12a8ceb8a5ed5b9c9790b6971a2fa2d9d00 /packages/meshbay-hub/tests/test_invite_email_choice.py
parent3d8c1acf785ff7389a68cc515a5c9324dee8de41 (diff)
downloadmeshbay-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.py67
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"