aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_video_root_policy.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-10 17:49:58 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-10 17:49:58 +0200
commit07ff8b4f6143039fcc74b8cf7c423282bce093c1 (patch)
tree89e095aeef9f5ad0bcbe7b3e6cfdc67d36bcbba8 /packages/meshbay-node/tests/test_video_root_policy.py
parent1e6f3861a1570029897a30b42121836fd03565c1 (diff)
downloadmeshbay-07ff8b4f6143039fcc74b8cf7c423282bce093c1.tar.gz
refactor(mnp)!: one operation for an app's folders, not one per app
`video_root`, `audio_root` and `photo_roots` are gone — the messages, the signed operations, the handlers, the `ops` wrappers, the three scalars on the handshake ack, and the client's handlers for their acks. `app_directories` does the same thing for every application, keyed by the app's own registry name, and it is what the SPA has been sending. The three were the same instruction three times, differing only in the key they wrote and whether they carried a string or a list. That shape is what made adding an application mean adding a message type, an op, a handler and a widget; it also meant three validation paths, and the older ones validated nothing — a typo was stored and then quietly matched no entry, an app showing an empty tab with no way to tell "misconfigured" from "no files yet". **What stays, and why.** `Roster.LEGACY_DIR_KEYS` still reads `video_root` and friends out of `group_settings`: that is a key on an operator's disk, not on the wire, and a node upgraded into this must find its own configuration. The Search page still reads its own older cache keys, for the same reason — the cache outlives a deploy. `CTX_ALIASES` keeps only `chat`, which is the one app whose second name something still reads. The two per-app policy test files go with the messages. What only they held — the real challenge/response path from message to database, which no other test exercises — is retargeted at `app_directories` in `test_app_directories_signed.py`, and the handler's own refusals (unknown app, malformed `directories`, nobody to authorize it) join `test_app_directories.py`. Node and common suites 1368 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AsoWC3GmhNdwVFomW3QjH3
Diffstat (limited to 'packages/meshbay-node/tests/test_video_root_policy.py')
-rw-r--r--packages/meshbay-node/tests/test_video_root_policy.py144
1 files changed, 0 insertions, 144 deletions
diff --git a/packages/meshbay-node/tests/test_video_root_policy.py b/packages/meshbay-node/tests/test_video_root_policy.py
deleted file mode 100644
index 8d8c45a..0000000
--- a/packages/meshbay-node/tests/test_video_root_policy.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Which folder (possibly a subfolder of a shared root) is the Videos app's
-entry point for a group. Same shape as test_apps_enabled_policy.py: a
-signed operator instruction, per-group (unlike tmdb_config, which is
-node-wide), stored via roster.py's group_settings table.
-
-Specific to this one: a non-empty path must resolve to a real, readable
-directory inside one of the group's own roots before a challenge is ever
-issued — refusing a typo up front, the same way an empty apps set is
-refused up front rather than round-tripped to the operator's browser.
-"""
-
-from pathlib import Path
-
-import pytest
-
-from meshbay_common.adminop import OP_VIDEO_ROOT
-from meshbay_node.indexer.group_index import GroupIndex
-from meshbay_node.roster import Roster
-from meshbay_node.transport.webrtc_server import WebRTCPeerSession
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-
-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 / "Movies").mkdir()
- (shared_root / "Shows").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_video_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_video_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_video_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_video_root({"path": "shared/Movies"})
-
- 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 'the whole group index' — always valid, nothing to resolve."""
- 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_video_root({"path": ""})
-
- assert issued == [(OP_VIDEO_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_video_root({"path": "shared/Movies"})
-
- assert issued == [(OP_VIDEO_ROOT, "shared/Movies")]
-
-
-# ── 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", "video") == [], (
- "absent must mean nothing configured")
- await roster.set_app_directories("g1", "video", ["shared/Movies"],
- set_by="op")
- assert await roster.app_directories("g1", "video") == ["shared/Movies"]
- finally:
- await roster.close()
-
- reopened = Roster(db_path=tmp_path / "roster.db")
- await reopened.open()
- try:
- assert await reopened.app_directories("g1", "video") == ["shared/Movies"]
- assert await reopened.app_directories("g2", "video") == [], (
- "one group's setting must not answer for another")
- finally:
- await reopened.close()