diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-11 14:55:52 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-11 14:55:52 +0200 |
| commit | cc90dc943fcd0e7bbf52674fb3f95ff097026f4a (patch) | |
| tree | 6315a5e966c027c50b92d59e60c4b506a418143e /packages/meshbay-hub/tests | |
| parent | edde9e441fb6b84e9d56215d6e2a8d9338b8f962 (diff) | |
| download | meshbay-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.py | 161 |
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 |