aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_roster_pairing.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_roster_pairing.py')
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py44
1 files changed, 43 insertions, 1 deletions
diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py
index e1b8a38..8bedfad 100644
--- a/packages/meshbay-node/tests/test_roster_pairing.py
+++ b/packages/meshbay-node/tests/test_roster_pairing.py
@@ -751,18 +751,31 @@ async def test_cli_invite_asks_the_hub_for_an_account_never_a_key(tmp_path, rost
"""
class _Hub:
_session = object()
+ added = []
async def get_user_pubkeys(self, username):
return {"user_id": f"id-of-{username}",
"pk_x25519": "SHOULD-NOT-BE-USED",
"pk_ed25519": "SHOULD-NOT-BE-USED"}
- client, _ = _ui_client(tmp_path, roster, hub=_Hub())
+ async def add_group_member(self, group_id, username):
+ self.added.append((group_id, username))
+ return {"status": "stored"}
+
+ hub = _Hub()
+ client, _ = _ui_client(tmp_path, roster, hub=hub)
resp = client.post(f"/api/groups/{GROUP}/invites?username=bob&t=tok")
assert resp.status_code == 200
body = resp.json()
assert body["user_id"] == "id-of-bob"
+ # The CLI path is the one with no browser to register the membership, so
+ # the node must do it — `/v1/groups/mine` joins `GroupMember`, and without
+ # a row there the invitee never sees the group. Nothing checked this when
+ # the registration was added, which is how it came to be skipped whenever
+ # the hub was merely absent.
+ assert hub.added == [(GROUP, "bob")]
+
invites = await roster.list_invites()
assert [i["user_id"] for i in invites] == ["id-of-bob"]
# Whatever the hub said about keys was never stored anywhere.
@@ -770,6 +783,35 @@ async def test_cli_invite_asks_the_hub_for_an_account_never_a_key(tmp_path, rost
assert await roster.get_identity("id-of-bob") is None
+async def test_an_unreachable_hub_leaves_no_invite_behind(tmp_path, roster):
+ """
+ A code the invitee could never redeem must not exist.
+
+ `create_invite` used to write the invite to the roster and *then* ask for
+ the hub, so an unreachable hub raised `Hub not connected` after the code was
+ already stored: the operator saw an error, no code, and a valid invitation
+ sat in the roster that nobody had been given. Every retry left another.
+
+ The registration now happens first, so a hub that is down costs nothing.
+ """
+ class _DeadHub:
+ _session = object()
+
+ async def get_user_pubkeys(self, username):
+ return {"user_id": f"id-of-{username}"}
+
+ async def add_group_member(self, group_id, username):
+ raise ConnectionError("hub is down")
+
+ client, _ = _ui_client(tmp_path, roster, hub=_DeadHub())
+ resp = client.post(f"/api/groups/{GROUP}/invites?username=bob&t=tok")
+
+ assert resp.status_code != 200, "an invite was issued that cannot be redeemed"
+ assert await roster.list_invites() == [], (
+ "the hub was unreachable and an invitation was left in the roster "
+ "anyway — a code nobody was handed, and nobody can use")
+
+
def _run_cli(monkeypatch, tmp_path, argv, responses):
"""Drive the real CLI with the daemon API stubbed, capturing the calls."""
import sys as _sys