aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py44
-rw-r--r--packages/meshbay-node/tests/test_webrtc_transport.py25
2 files changed, 68 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
diff --git a/packages/meshbay-node/tests/test_webrtc_transport.py b/packages/meshbay-node/tests/test_webrtc_transport.py
index 159ba63..a4c7f61 100644
--- a/packages/meshbay-node/tests/test_webrtc_transport.py
+++ b/packages/meshbay-node/tests/test_webrtc_transport.py
@@ -1105,6 +1105,23 @@ def x25519_keypair():
return sk_raw, pk_raw
+class _InviteHub:
+ """Just enough hub for `ops.create_invite`: a live session, and a member
+ registration that records what it was asked to do."""
+
+ class _S:
+ user_id = "node-user"
+
+ _session = _S()
+
+ def __init__(self):
+ self.added = []
+
+ async def add_group_member(self, group_id, username):
+ self.added.append((group_id, username))
+ return {"status": "stored"}
+
+
@pytest.mark.asyncio
async def test_invite_then_join_delivers_the_gek(sk_node, sk_hub, gek, shared_dir,
tmp_path, x25519_keypair):
@@ -1134,9 +1151,17 @@ async def test_invite_then_join_delivers_the_gek(sk_node, sk_hub, gek, shared_di
transport._ctx["groups"] = {
TEST_GROUP: {"gek": gek, "roots": shared_dir, "index": indexer.index},
}
+ # `create_invite` registers the invitee as a hub member *before* writing the
+ # invite, and fails the whole operation if it cannot: `/v1/groups/mine`
+ # joins `GroupMember`, so someone never registered does not see the group
+ # and could never redeem the code. Without a hub here the operation is
+ # correctly refused — this test used to have none, and passed only because
+ # the registration failure was swallowed and the unredeemable code returned
+ # anyway.
transport._ctx["daemon_state"] = {
"roster": roster,
"groups_ctx": transport._ctx["groups"],
+ "hub": _InviteHub(),
}
# A paired operator, as `meshbay-node operator pair` would have left it.