diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_hub_ws_group_claim.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_hub_ws_group_claim.py | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_hub_ws_group_claim.py b/packages/meshbay-node/tests/test_hub_ws_group_claim.py new file mode 100644 index 0000000..49ac776 --- /dev/null +++ b/packages/meshbay-node/tests/test_hub_ws_group_claim.py @@ -0,0 +1,100 @@ +""" +What the node claims to host, on the wire. + +2026-09-11: the node omitted `group_ids` from its WS auth frame whenever it +hosted nothing (`if gids:`), and the hub read an absent claim as "every group +this account belongs to". An unconfigured node was therefore registered as a +source for other people's groups, could serve none of them, and — being listed +first — took them down for every member. + +The hub no longer widens an absent claim. This pins the node's half: the empty +set is stated, not left to be inferred from a missing field. +""" + +import asyncio +import json + +import pytest + +from meshbay_node.hub_client import HubClient, HubConfig, HubSession + + +class _FakeWS: + """Just enough of a websockets connection for one auth round trip.""" + + def __init__(self, sent: list, authed: asyncio.Event): + self._sent = sent + self._authed = authed + + async def __aenter__(self): + return self + + async def __aexit__(self, *_): + return False + + async def send(self, raw: str) -> None: + self._sent.append(json.loads(raw)) + + async def recv(self) -> str: + return json.dumps({"type": "auth_ok", "node_id": "node-1"}) + + def __aiter__(self): + return self + + async def __anext__(self): + # Registered; nothing more to drive. Hold here rather than closing, so + # the reconnect path does not run and muddy what was sent. + self._authed.set() + await asyncio.sleep(3600) + raise StopAsyncIteration + + +async def _auth_frame(monkeypatch, group_ids) -> dict: + import websockets + + sent: list = [] + authed = asyncio.Event() + monkeypatch.setattr( + websockets, "connect", lambda *a, **kw: _FakeWS(sent, authed)) + + client = HubClient(HubConfig(hub_url="https://hub.test", username="u"), + keys=None) + client._session = HubSession( + hub_url="https://hub.test", username="u", user_id="user-1", + access_token="tok", refresh_token="ref", hub_pk_pem=b"", node_id="node-1") + + task = asyncio.create_task(client.maintain_ws(group_ids=group_ids)) + try: + await asyncio.wait_for(authed.wait(), timeout=5) + finally: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + await client.close() + + assert sent, "no auth frame was sent" + return sent[0] + + +@pytest.mark.asyncio +async def test_a_node_hosting_nothing_says_so(monkeypatch): + """The empty set on the wire, rather than a key the hub has to interpret.""" + frame = await _auth_frame(monkeypatch, lambda: []) + assert "group_ids" in frame, ( + "an absent claim is what the hub used to read as 'all of them'") + assert frame["group_ids"] == [] + + +@pytest.mark.asyncio +async def test_a_node_declares_the_groups_it_hosts(monkeypatch): + frame = await _auth_frame(monkeypatch, lambda: ["g-1", "g-2"]) + assert frame["group_ids"] == ["g-1", "g-2"] + + +@pytest.mark.asyncio +async def test_a_caller_with_nothing_to_declare_sends_no_key(monkeypatch): + """`None` is not the same as `[]`: it is "I am not answering that question".""" + frame = await _auth_frame(monkeypatch, None) + assert "group_ids" not in frame |