aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_invite_email_choice.py
blob: e04c31f095cab3186aeaa5cb7527700e94306161 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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"