diff options
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_incoming_membership.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_incoming_membership.py b/packages/meshbay-hub/tests/test_incoming_membership.py new file mode 100644 index 0000000..708e327 --- /dev/null +++ b/packages/meshbay-hub/tests/test_incoming_membership.py @@ -0,0 +1,76 @@ +"""The NAT-punch signal is not a liveness oracle, and only a member reaches it. + +`POST /v1/nodes/{id}/incoming` used to check nothing but the caller's own +address, then reveal whether the node was connected (404 vs 504) and, with QUIC +on, make it punch. Any authenticated account could poll it for a node's liveness +and make a stranger's node emit a UDP probe. It now requires a shared active +group with the node first — the same gate the offer relay uses — checked before +anything depends on the node's connection state, so a non-member gets one uniform +403 whether the node is connected or not. +""" + +import pytest +from test_availability_between_members import ( + _add_member, + _announce_node, + _make_group, + _make_user, +) + + +def _incoming(client, node_id, user, peer_ip="1.2.3.4", peer_port=5000): + return client.post(f"/v1/nodes/{node_id}/incoming", + json={"peer_ip": peer_ip, "peer_port": peer_port}, + headers={"Authorization": f"Bearer {user['token']}"}) + + +@pytest.mark.asyncio +async def test_a_non_member_is_refused_whether_the_node_is_connected_or_not(client): + from meshbay_hub.api import revocation as rev + + owner = await _make_user(client, "inc_owner") + stranger = await _make_user(client, "inc_stranger") + group_id = await _make_group(client, owner, "inc-group") + node_id = await _announce_node(client, owner) + + # Node NOT in the connected registry — the stranger gets the membership 403 + # (not the connection 404), so the answer says nothing about whether the node + # is up. The detail is what distinguishes it from the peer_ip refusal that a + # request without the gate would give. + r_off = await _incoming(client, node_id, stranger) + assert r_off.status_code == 403 + assert "member" in r_off.json()["detail"] + + # Node connected and serving the group — the stranger, not a member, still gets + # the membership 403, and never reaches the punch or the connection-state answer. + rev._connected_nodes[node_id] = object() + rev._node_groups[node_id] = [group_id] + try: + r_on = await _incoming(client, node_id, stranger) + assert r_on.status_code == 403 + assert "member" in r_on.json()["detail"] + finally: + rev._connected_nodes.pop(node_id, None) + rev._node_groups.pop(node_id, None) + + +@pytest.mark.asyncio +async def test_a_member_passes_the_membership_gate(client): + """A member is not turned away by the gate. (It then reaches the connection + check — 404 here, since no real node socket is registered — never 403.)""" + from meshbay_hub.api import revocation as rev + + owner = await _make_user(client, "inc2_owner") + member = await _make_user(client, "inc2_member") + group_id = await _make_group(client, owner, "inc2-group") + await _add_member(client, owner, group_id, member) + node_id = await _announce_node(client, owner) + + rev._node_groups[node_id] = [group_id] # registered/hosted, but no live socket + try: + r = await _incoming(client, node_id, member) + # Past the membership gate: the refusal, if any, is about the connection + # or the peer address, never "not a member of any group on this node". + assert r.status_code != 403 or "member" not in r.json().get("detail", "") + finally: + rev._node_groups.pop(node_id, None) |