summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_tmdb_rematch_policy.py')
-rw-r--r--packages/meshbay-node/tests/test_tmdb_rematch_policy.py147
1 files changed, 147 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_tmdb_rematch_policy.py b/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
new file mode 100644
index 0000000..ff259c1
--- /dev/null
+++ b/packages/meshbay-node/tests/test_tmdb_rematch_policy.py
@@ -0,0 +1,147 @@
+"""
+`tmdb_rematch` (ยง10.1/V13) โ€” an operator dropping one file's cached TMDB
+match so it re-resolves with the current matcher. Signed like
+`tmdb_override` (media_cache is shared node-wide); unlike `clear_file_tmdb`
+it forgets a manual override marker too, since the operator is explicitly
+asking for a fresh resolution.
+"""
+
+import hashlib
+
+import pytest
+from conftest import one_root
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from meshbay_common.adminop import OP_TMDB_REMATCH
+from meshbay_common.protocol import MNP, IndexEntry
+from meshbay_node.indexer.group_index import GroupIndex
+from meshbay_node.media_cache import MediaCache
+from meshbay_node.transport.webrtc_server import WebRTCPeerSession
+
+pytestmark = pytest.mark.asyncio
+
+
+def _session(tmp_path, user_id, *, operator=None):
+ shared = tmp_path / "shared"
+ shared.mkdir(exist_ok=True)
+ index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate())
+ s = WebRTCPeerSession.__new__(WebRTCPeerSession)
+ s._ctx = {"roots": one_root(shared), "index": index, "sk_node": index.sk_node,
+ "node_user_id": operator}
+ s._group_id = None
+ s._user_id = user_id
+ s._pk_user = ""
+ s.sent = []
+ s._send = s.sent.append
+ s._audit = lambda *a, **k: None
+ return s
+
+
+def _entry(name):
+ digest = hashlib.sha256(name.encode()).hexdigest()
+ return IndexEntry(id=digest, name=name, path="movies", size=1, type="video",
+ added_at=0, display_title="Some Film")
+
+
+async def _true():
+ return True
+
+
+async def test_missing_file_id_is_refused(tmp_path):
+ s = _session(tmp_path, "op", operator="op")
+ s._has_admin_authority = lambda: True
+ issued = []
+ s._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
+
+ s._do_tmdb_rematch({})
+
+ assert not issued
+ assert [m for m in s.sent if m.get("type") == "error"]
+
+
+async def test_unknown_file_id_is_refused(tmp_path):
+ s = _session(tmp_path, "op", operator="op")
+ s._has_admin_authority = lambda: True
+ issued = []
+ s._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
+
+ s._do_tmdb_rematch({"file_id": "nope"})
+
+ assert not issued
+ assert [m for m in s.sent if m.get("type") == "error"]
+
+
+async def test_no_authorized_key_is_refused(tmp_path):
+ s = _session(tmp_path, "member", operator="the-operator")
+ e = _entry("some.film.2001.mkv")
+ s._ctx["index"].add_entry(e)
+ s._has_admin_authority = lambda: False
+
+ s._do_tmdb_rematch({"file_id": e.id})
+
+ assert [m for m in s.sent if m.get("type") == "error"]
+
+
+async def test_a_valid_request_is_signed(tmp_path):
+ s = _session(tmp_path, "op", operator="op")
+ e = _entry("some.film.2001.mkv")
+ s._ctx["index"].add_entry(e)
+ s._has_admin_authority = lambda: True
+ issued = []
+ s._issue_admin_challenge = lambda op, subject: issued.append((op, subject))
+
+ s._do_tmdb_rematch({"file_id": e.id})
+
+ assert issued == [(OP_TMDB_REMATCH, f"file_id={e.id}")]
+
+
+async def test_exec_drops_the_match_and_the_override_marker(tmp_path):
+ s = _session(tmp_path, "op", operator="op")
+ e = _entry("some.film.2001.mkv")
+ s._ctx["index"].add_entry(e)
+
+ media_cache = MediaCache(db_path=tmp_path / "media_cache.db")
+ await media_cache.open()
+ try:
+ s._ctx["media_cache"] = media_cache
+ await media_cache.set_file_tmdb(e.id, "wrong-id", "movie")
+ await media_cache.mark_tmdb_override(e.id)
+ s._verify_admin_sig = lambda transcript, sig: _true()
+ peer = type("Peer", (), {"sent": []})()
+ peer._send = peer.sent.append
+ s._peer_registry = lambda: {"peer-1": peer}
+
+ await s._admin_exec_tmdb_rematch(
+ {"subject": f"file_id={e.id}"}, b"transcript", b"sig")
+
+ assert await media_cache.get_file_tmdb(e.id) is None
+ # the override marker is gone too, so a later group-wide rematch
+ # would treat a fresh match as ordinary
+ await media_cache.set_file_tmdb(e.id, "fresh", "movie")
+ assert await media_cache.clear_tmdb_matches([e.id]) == 1
+ assert [m for m in peer.sent if m.get("type") == MNP.TMDB_REMATCH_ACK]
+ finally:
+ await media_cache.close()
+
+
+async def test_exec_refuses_a_bad_signature(tmp_path):
+ s = _session(tmp_path, "op", operator="op")
+ e = _entry("some.film.2001.mkv")
+ s._ctx["index"].add_entry(e)
+
+ media_cache = MediaCache(db_path=tmp_path / "media_cache.db")
+ await media_cache.open()
+ try:
+ s._ctx["media_cache"] = media_cache
+ await media_cache.set_file_tmdb(e.id, "keep-me", "movie")
+
+ async def _false():
+ return False
+ s._verify_admin_sig = lambda transcript, sig: _false()
+
+ await s._admin_exec_tmdb_rematch(
+ {"subject": f"file_id={e.id}"}, b"transcript", b"badsig")
+
+ assert await media_cache.get_file_tmdb(e.id) == ("keep-me", "movie")
+ assert [m for m in s.sent if m.get("type") == "error"]
+ finally:
+ await media_cache.close()