aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_audio_root_policy.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_audio_root_policy.py')
-rw-r--r--packages/meshbay-node/tests/test_audio_root_policy.py260
1 files changed, 0 insertions, 260 deletions
diff --git a/packages/meshbay-node/tests/test_audio_root_policy.py b/packages/meshbay-node/tests/test_audio_root_policy.py
deleted file mode 100644
index e2e9254..0000000
--- a/packages/meshbay-node/tests/test_audio_root_policy.py
+++ /dev/null
@@ -1,260 +0,0 @@
-"""
-Which folder (possibly a subfolder of a shared root) is the Music app's
-entry point for a group. Same shape as test_video_root_policy.py — a
-signed operator instruction, per-group, stored via roster.py's
-group_settings table, added later once a real messy library showed
-musicbay.md's original "no root, whole shared tree" call was wrong.
-"""
-
-import base64
-from pathlib import Path
-
-import pytest
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-from meshbay_common.adminop import OP_AUDIO_ROOT, admin_transcript
-from meshbay_common.crypto import pk_to_b64
-from meshbay_common.join import ROLE_OPERATOR
-from meshbay_node.indexer.group_index import GroupIndex
-from meshbay_node.roster import Roster, open_roster
-from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-
-from conftest import one_root
-
-pytestmark = pytest.mark.asyncio
-
-
-def _session(tmp_path: Path, user_id: str, *, operator: str | None = None) -> WebRTCPeerSession:
- shared_root = tmp_path / "shared"
- shared_root.mkdir(exist_ok=True)
- (shared_root / "Music").mkdir()
- (shared_root / "Podcasts").mkdir()
- index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate())
- ctx = {
- "roots": one_root(shared_root),
- "index": index,
- "sk_node": index.sk_node,
- "node_user_id": operator,
- }
- session = WebRTCPeerSession.__new__(WebRTCPeerSession)
- session._ctx = ctx
- session._group_id = None
- session._user_id = user_id
- session._pk_user = ""
- session.sent = []
- session._send = session.sent.append
- session._audit = lambda *a, **k: None
- return session
-
-
-# ── Refused before a challenge is even issued ───────────────────────────────
-
-async def test_missing_path_is_refused(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
-
- session._do_audio_root({})
-
- assert not issued
- assert [m for m in session.sent if m.get("type") == "error"]
-
-
-async def test_a_nonexistent_folder_is_refused(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
-
- session._do_audio_root({"path": "shared/Nonexistent"})
-
- assert not issued, "a mistyped path must be refused before a signature round trip"
- assert [m for m in session.sent if m.get("type") == "error"]
-
-
-async def test_path_traversal_is_refused(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
-
- session._do_audio_root({"path": "../../etc"})
-
- assert not issued
- assert [m for m in session.sent if m.get("type") == "error"]
-
-
-async def test_a_request_with_nobody_to_authorize_it_is_refused(tmp_path):
- session = _session(tmp_path, "member-1", operator="the-operator")
- session._has_admin_authority = lambda: False
-
- session._do_audio_root({"path": "shared/Music"})
-
- assert [m for m in session.sent if m.get("type") == "error"]
-
-
-# ── Accepted cases ───────────────────────────────────────────────────────────
-
-async def test_an_empty_path_is_always_accepted(tmp_path):
- """Empty means 'unset' — Music shows nothing yet, always valid to clear."""
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
-
- session._do_audio_root({"path": ""})
-
- assert issued == [(OP_AUDIO_ROOT, "")]
-
-
-async def test_a_real_subfolder_is_accepted_and_signed(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
-
- session._do_audio_root({"path": "shared/Music"})
-
- assert issued == [(OP_AUDIO_ROOT, "shared/Music")]
-
-
-# ── Where it is stored ──────────────────────────────────────────────────────
-
-async def test_the_setting_lives_on_the_node_and_survives_a_restart(tmp_path):
- roster = Roster(db_path=tmp_path / "roster.db")
- await roster.open()
- try:
- assert await roster.app_directories("g1", "music") == [], (
- "absent must mean unset")
- await roster.set_app_directories("g1", "music", ["shared/Music"],
- set_by="op")
- assert await roster.app_directories("g1", "music") == ["shared/Music"]
- finally:
- await roster.close()
-
- reopened = Roster(db_path=tmp_path / "roster.db")
- await reopened.open()
- try:
- assert await reopened.app_directories("g1", "music") == ["shared/Music"]
- assert await reopened.app_directories("g2", "music") == [], (
- "one group's setting must not answer for another")
- finally:
- await reopened.close()
-
-
-# ── End to end through the real signed-op path ──────────────────────────────
-#
-# Everything above either calls ops.set_audio_root directly or mocks out
-# _issue_admin_challenge — neither one ever exercises real signature
-# verification (_verify_admin_sig, _do_admin_response) or the shared
-# groups_ctx/roster wiring _run_op depends on. Found live: a save that
-# looked like it worked (the Music tab showed content right afterward) did
-# not survive a reload — worth ruling out a break somewhere in that real
-# path specifically, not just in the pure-Python setter. Session shape
-# mirrors test_admin_ops_mnp.py's _session helper.
-
-GROUP = "g" * 32
-
-
-def _keypair():
- sk = Ed25519PrivateKey.generate()
- return sk, pk_to_b64(sk.public_key())
-
-
-async def _full_session(tmp_path: Path, roster) -> tuple[WebRTCPeerSession, Ed25519PrivateKey]:
- shared = tmp_path / "shared"
- (shared / "Music").mkdir(parents=True)
- index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
- roots = one_root(shared)
-
- sk_op, pk_op = _keypair()
- await roster.pin_identity("grenet", "grenet", pk_op, pk_op, "code")
- await roster.set_member("", "grenet", ROLE_OPERATOR, "active", "local-cli")
-
- group_ctx = {"gek": b"\x01" * 32, "roots": roots, "index": index,
- "join_policy": "invite", "audio_root": ""}
- state = {
- "groups_ctx": {GROUP: group_ctx},
- "roster": roster,
- "node_user_id": "node-user",
- }
-
- session = WebRTCPeerSession.__new__(WebRTCPeerSession)
- session._ctx = {
- "roots": roots, "index": index, "sk_node": index.sk_node,
- "roster": roster, "groups": {GROUP: group_ctx},
- "has_admin_authority": True,
- "daemon_state": state,
- }
- session._group_id = GROUP
- session._user_id = "grenet"
- session._pk_user = ""
- session._admin_ops = {}
- session.sent = []
- session._send = session.sent.append
- session._audit = lambda *a, **k: None
- session.spawned = []
- session._spawn = session.spawned.append
- # A real session registers itself here on handshake completion
- # (`self._peer_registry()[self._user_id] = self`) — without it, the
- # broadcast loop in _admin_exec_audio_root (and every other admin op)
- # has nobody to send the final ack to, including the requester itself.
- group_ctx["_peers"] = {"grenet": session}
- session._peer_registry = lambda: group_ctx["_peers"]
- return session, sk_op
-
-
-async def _drain(session):
- for coro in session.spawned:
- await coro
- session.spawned.clear()
-
-
-async def test_a_real_signed_save_persists_and_survives_a_fresh_roster_read(tmp_path):
- """
- The exact question a "worked, then reverted after reload" report raises:
- does the value set through the real challenge/response path actually
- land in the database, in a form any later connection — this one, or a
- freshly-opened Roster after a restart — reads back correctly?
- """
- roster = await open_roster(tmp_path)
- try:
- session, sk_op = await _full_session(tmp_path, roster)
-
- session._do_audio_root({"path": "shared/Music"})
- challenge = session.sent[-1]
- assert challenge["type"] == "admin_challenge", challenge
-
- transcript = admin_transcript(
- op=OP_AUDIO_ROOT, node_pk_b64=session._node_pk_b64(), group_id=GROUP,
- subject="shared/Music", nonce=base64.b64decode(challenge["nonce"]),
- ts=challenge["ts"])
- session._do_admin_response({
- "op_id": challenge["op_id"],
- "signature": base64.b64encode(sk_op.sign(transcript)).decode(),
- })
- await _drain(session)
-
- ack = session.sent[-1]
- assert ack["type"] == "audio_root_ack", ack
- assert ack["path"] == "shared/Music"
-
- assert session._ctx["groups"][GROUP]["audio_root"] == "shared/Music", (
- "the live in-memory context must reflect the new root immediately")
- assert await roster.app_directories(GROUP, "music") == ["shared/Music"], (
- "the same Roster instance must read back what it just wrote")
- finally:
- await roster.close()
-
- # A fresh connection (or a restarted daemon) never touches the Roster
- # instance above at all — it opens its own. This is the check that
- # actually answers "does it survive a reload".
- reopened = await open_roster(tmp_path)
- try:
- assert await reopened.app_directories(GROUP, "music") == ["shared/Music"], (
- "a freshly-opened Roster against the same db file must see the "
- "committed value — anything else means the write was never "
- "durable in the first place")
- finally:
- await reopened.close()