aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-11 14:55:52 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-11 14:55:52 +0200
commitcc90dc943fcd0e7bbf52674fb3f95ff097026f4a (patch)
tree6315a5e966c027c50b92d59e60c4b506a418143e /packages/meshbay-hub/tests
parentedde9e441fb6b84e9d56215d6e2a8d9338b8f962 (diff)
downloadmeshbay-cc90dc943fcd0e7bbf52674fb3f95ff097026f4a.tar.gz
feat: Phase 10b — Self-service UI (group create/join, upload, IndexedDB, search)
Six self-service features for the web SPA: - Group creation UI with GEK auto-generation (AES-256-GCM ECIES) - Member management + invite by username (GEK wrapping for invitee) - Open group self-join flow (POST /v1/groups/{id}/join) - File upload client→node (FILE_UPLOAD MNP type, .uploads/ staging) - IndexedDB caching of group file indexes (instant display on revisit) - Cross-group file search (SearchPage, pure client-side on cached indexes) 11 new tests (166 total): 8 group self-service + 3 AES GEK wrap/unwrap. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_groups_self_service.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_groups_self_service.py b/packages/meshbay-hub/tests/test_groups_self_service.py
new file mode 100644
index 0000000..a60209d
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_groups_self_service.py
@@ -0,0 +1,161 @@
+"""Integration tests for group self-service: create, join, members."""
+
+import pytest
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
+
+from meshbay_common.crypto import pk_to_b64
+
+
+def _gen_user_keys():
+ sk_ed = Ed25519PrivateKey.generate()
+ sk_x = X25519PrivateKey.generate()
+ return pk_to_b64(sk_ed.public_key()), pk_to_b64(sk_x.public_key())
+
+
+async def _register(client, username, email="test@x.com", password="testpass99"):
+ pk_ed, pk_x = _gen_user_keys()
+ r = await client.post("/v1/users/register", json={
+ "username": username, "email": email, "password": password,
+ "pk_user_ed25519": pk_ed, "pk_user_x25519": pk_x,
+ })
+ assert r.status_code == 201
+ return r.json()["user_id"]
+
+
+async def _login(client, username, password="testpass99"):
+ r = await client.post("/v1/users/login", json={
+ "username": username, "password": password,
+ })
+ assert r.status_code == 200
+ return r.json()["access_token"]
+
+
+async def _create_group(client, token, name="test-group", visibility="public",
+ join_policy="open"):
+ r = await client.post("/v1/groups", json={
+ "name": name, "visibility": visibility, "join_policy": join_policy,
+ }, headers={"Authorization": f"Bearer {token}"})
+ assert r.status_code == 201
+ return r.json()["group_id"]
+
+
+@pytest.mark.asyncio
+async def test_create_group(client):
+ await _register(client, "alice", email="a@x.com")
+ token = await _login(client, "alice")
+
+ r = await client.post("/v1/groups", json={
+ "name": "my-group", "visibility": "public", "join_policy": "open",
+ }, headers={"Authorization": f"Bearer {token}"})
+ assert r.status_code == 201
+ data = r.json()
+ assert data["name"] == "my-group"
+ assert "group_id" in data
+
+
+@pytest.mark.asyncio
+async def test_join_open_group(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "open-group")
+
+ await _register(client, "bob", email="b@x.com")
+ bob_token = await _login(client, "bob")
+
+ r = await client.post(f"/v1/groups/{gid}/join",
+ headers={"Authorization": f"Bearer {bob_token}"})
+ assert r.status_code == 200
+ assert r.json()["status"] == "joined"
+
+
+@pytest.mark.asyncio
+async def test_join_invite_group_rejected(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "invite-group",
+ join_policy="invite")
+
+ await _register(client, "bob", email="b@x.com")
+ bob_token = await _login(client, "bob")
+
+ r = await client.post(f"/v1/groups/{gid}/join",
+ headers={"Authorization": f"Bearer {bob_token}"})
+ assert r.status_code == 403
+
+
+@pytest.mark.asyncio
+async def test_join_already_member(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "dup-group")
+
+ r = await client.post(f"/v1/groups/{gid}/join",
+ headers={"Authorization": f"Bearer {alice_token}"})
+ assert r.status_code == 409
+
+
+@pytest.mark.asyncio
+async def test_group_members(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "team-group")
+
+ await _register(client, "bob", email="b@x.com")
+ bob_token = await _login(client, "bob")
+ await client.post(f"/v1/groups/{gid}/join",
+ headers={"Authorization": f"Bearer {bob_token}"})
+
+ r = await client.get(f"/v1/groups/{gid}/members",
+ headers={"Authorization": f"Bearer {alice_token}"})
+ assert r.status_code == 200
+ data = r.json()
+ usernames = [m["username"] for m in data["members"]]
+ assert "alice" in usernames
+ assert "bob" in usernames
+ assert data["admin_id"] is not None
+
+
+@pytest.mark.asyncio
+async def test_group_members_non_member_denied(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "private-group",
+ visibility="private", join_policy="invite")
+
+ await _register(client, "bob", email="b@x.com")
+ bob_token = await _login(client, "bob")
+
+ r = await client.get(f"/v1/groups/{gid}/members",
+ headers={"Authorization": f"Bearer {bob_token}"})
+ assert r.status_code == 403
+
+
+@pytest.mark.asyncio
+async def test_group_search(client):
+ await _register(client, "alice", email="a@x.com")
+ token = await _login(client, "alice")
+ await _create_group(client, token, "alpha-team")
+ await _create_group(client, token, "beta-team")
+
+ r = await client.get("/v1/groups?q=alpha")
+ assert r.status_code == 200
+ names = [g["name"] for g in r.json()["groups"]]
+ assert "alpha-team" in names
+ assert "beta-team" not in names
+
+
+@pytest.mark.asyncio
+async def test_join_triggers_notification(client):
+ await _register(client, "alice", email="a@x.com")
+ alice_token = await _login(client, "alice")
+ gid = await _create_group(client, alice_token, "notif-group")
+
+ await _register(client, "bob", email="b@x.com")
+ bob_token = await _login(client, "bob")
+ await client.post(f"/v1/groups/{gid}/join",
+ headers={"Authorization": f"Bearer {bob_token}"})
+
+ r = await client.get(f"/v1/groups/{gid}/members",
+ headers={"Authorization": f"Bearer {alice_token}"})
+ assert len(r.json()["members"]) == 2