summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_hub_api.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_hub_api.py')
-rw-r--r--packages/meshbay-hub/tests/test_hub_api.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_hub_api.py b/packages/meshbay-hub/tests/test_hub_api.py
index a9c97a3..f7c499e 100644
--- a/packages/meshbay-hub/tests/test_hub_api.py
+++ b/packages/meshbay-hub/tests/test_hub_api.py
@@ -351,6 +351,131 @@ async def test_jwt_contains_groups_claim(client):
assert group_id in decoded_alice["groups"]
+# ── My groups (9.6) ─────────────────────────────────────────────────────────
+
+@pytest.mark.asyncio
+async def test_my_groups(client):
+ """GET /v1/groups/mine returns groups the user belongs to."""
+ pk_ed_a, pk_x_a, _ = _gen_user_keys()
+ pk_ed_b, pk_x_b, _ = _gen_user_keys()
+
+ await client.post("/v1/users/register", json={
+ "username": "mg_alice", "email": "mga@x.com", "password": "alicepass99",
+ "pk_user_ed25519": pk_ed_a, "pk_user_x25519": pk_x_a})
+ await client.post("/v1/users/register", json={
+ "username": "mg_bob", "email": "mgb@x.com", "password": "bobpass99",
+ "pk_user_ed25519": pk_ed_b, "pk_user_x25519": pk_x_b})
+
+ alice_token = (await client.post("/v1/users/login",
+ json={"username": "mg_alice", "password": "alicepass99"})).json()["access_token"]
+ bob_token = (await client.post("/v1/users/login",
+ json={"username": "mg_bob", "password": "bobpass99"})).json()["access_token"]
+
+ # Bob has no groups initially
+ r = await client.get("/v1/groups/mine",
+ headers={"Authorization": f"Bearer {bob_token}"})
+ assert r.status_code == 200
+ assert r.json()["groups"] == []
+
+ # Alice creates a group and adds Bob
+ r = await client.post("/v1/groups", json={"name": "mg-group"},
+ headers={"Authorization": f"Bearer {alice_token}"})
+ group_id = r.json()["group_id"]
+ gek = generate_gek()
+ bundle = wrap_gek(gek, base64.b64decode(pk_x_b))
+ await client.post(f"/v1/groups/{group_id}/members/mg_bob/gek",
+ json=bundle,
+ headers={"Authorization": f"Bearer {alice_token}"})
+
+ # Re-login to get fresh token with group claims
+ bob_token = (await client.post("/v1/users/login",
+ json={"username": "mg_bob", "password": "bobpass99"})).json()["access_token"]
+
+ # Now Bob should see the group
+ r = await client.get("/v1/groups/mine",
+ headers={"Authorization": f"Bearer {bob_token}"})
+ assert r.status_code == 200
+ groups = r.json()["groups"]
+ assert len(groups) == 1
+ assert groups[0]["id"] == group_id
+ assert groups[0]["name"] == "mg-group"
+ assert groups[0]["is_admin"] is False
+
+ # Alice should see it too, with is_admin=True
+ alice_token = (await client.post("/v1/users/login",
+ json={"username": "mg_alice", "password": "alicepass99"})).json()["access_token"]
+ r = await client.get("/v1/groups/mine",
+ headers={"Authorization": f"Bearer {alice_token}"})
+ groups = r.json()["groups"]
+ assert any(g["id"] == group_id and g["is_admin"] for g in groups)
+
+ # Unauthenticated → rejected
+ r = await client.get("/v1/groups/mine")
+ assert r.status_code >= 400
+
+
+@pytest.mark.asyncio
+async def test_group_online_nodes(client):
+ """GET /v1/groups/{id}/nodes returns online nodes serving the group."""
+ import json
+ from meshbay_hub.api.revocation import _connected_nodes, _node_groups
+
+ pk_ed, pk_x, _ = _gen_user_keys()
+ await client.post("/v1/users/register", json={
+ "username": "gn_user", "email": "gn@x.com", "password": "gnpass999",
+ "pk_user_ed25519": pk_ed, "pk_user_x25519": pk_x})
+ r = await client.post("/v1/users/login",
+ json={"username": "gn_user", "password": "gnpass999"})
+ token = r.json()["access_token"]
+
+ r = await client.post("/v1/groups", json={"name": "gn-group"},
+ headers={"Authorization": f"Bearer {token}"})
+ group_id = r.json()["group_id"]
+
+ # Re-login to get fresh token with group claims
+ token = (await client.post("/v1/users/login",
+ json={"username": "gn_user", "password": "gnpass999"})).json()["access_token"]
+
+ # Announce a node
+ r = await client.post("/v1/nodes/announce", json={
+ "pk_node": pk_ed, "endpoint_hint": "1.2.3.4:19000"},
+ headers={"Authorization": f"Bearer {token}"})
+ node_id = r.json()["node_id"]
+
+ # No nodes online yet
+ r = await client.get(f"/v1/groups/{group_id}/nodes",
+ headers={"Authorization": f"Bearer {token}"})
+ assert r.status_code == 200
+ assert r.json()["nodes"] == []
+
+ # Simulate node connecting via WS with group_ids
+ class FakeWS:
+ async def send_text(self, text): pass
+ _connected_nodes[node_id] = FakeWS()
+ _node_groups[node_id] = [group_id]
+
+ try:
+ r = await client.get(f"/v1/groups/{group_id}/nodes",
+ headers={"Authorization": f"Bearer {token}"})
+ assert r.status_code == 200
+ nodes = r.json()["nodes"]
+ assert len(nodes) == 1
+ assert nodes[0]["node_id"] == node_id
+ assert nodes[0]["pk_node"] == pk_ed
+ finally:
+ _connected_nodes.pop(node_id, None)
+ _node_groups.pop(node_id, None)
+
+ # 404 for nonexistent group
+ r = await client.get("/v1/groups/fake-id/nodes",
+ headers={"Authorization": f"Bearer {token}"})
+ assert r.status_code == 404
+
+ # Unauthenticated → rejected
+ r = await client.get(f"/v1/groups/{group_id}/nodes")
+ assert r.status_code >= 400
+
+
# ── Admin authz (8.1) ───────────────────────────────────────────────────────
@pytest.mark.asyncio
@@ -578,3 +703,22 @@ async def test_ip_log_cleanup(app):
)).scalar_one()
assert count == 1
break
+
+
+# ── Webapp HTML shell (9.13) ─────────────────────────────────────────────────
+
+@pytest.mark.asyncio
+async def test_webapp_html_includes_scripts(client):
+ """SPA HTML shell includes all required script tags."""
+ r = await client.get("/")
+ assert r.status_code == 200
+ html = r.text
+ assert "<!DOCTYPE html>" in html
+ assert '<div id="app">' in html
+ assert 'src="/keyderive.js"' in html
+ assert 'src="/crypto.js"' in html
+ assert 'src="/transport.js"' in html
+ assert 'src="/app.js"' in html
+ assert 'type="module"' in html
+ assert 'rel="stylesheet"' in html
+ assert 'href="/style.css"' in html