aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_cli_dispatch.py2
-rw-r--r--packages/meshbay-node/tests/test_musicbrainz.py33
-rw-r--r--packages/meshbay-node/tests/test_musicbrainz_config_policy.py179
3 files changed, 13 insertions, 201 deletions
diff --git a/packages/meshbay-node/tests/test_cli_dispatch.py b/packages/meshbay-node/tests/test_cli_dispatch.py
index d9edb17..aff7330 100644
--- a/packages/meshbay-node/tests/test_cli_dispatch.py
+++ b/packages/meshbay-node/tests/test_cli_dispatch.py
@@ -128,7 +128,7 @@ def test_the_verb_list_here_matches_the_parser():
exercised = {argv[0] for argv in VERBS}
# `init` writes a config file and `calibrate-argon2` burns CPU for seconds;
# both are excluded on purpose rather than by omission.
- untested = declared - exercised - {"init", "calibrate-argon2"}
+ untested = declared - exercised - {"init", "reset", "calibrate-argon2"}
assert not untested, (
f"CLI verbs with no dispatch test: {sorted(untested)} — add them to "
f"VERBS above")
diff --git a/packages/meshbay-node/tests/test_musicbrainz.py b/packages/meshbay-node/tests/test_musicbrainz.py
index 4d075d3..843a691 100644
--- a/packages/meshbay-node/tests/test_musicbrainz.py
+++ b/packages/meshbay-node/tests/test_musicbrainz.py
@@ -7,14 +7,6 @@ import pytest
from meshbay_node.musicbrainz import _MIN_INTERVAL_SECS, MusicBrainzClient, _escape_lucene
-class FakeRoster:
- def __init__(self, contact: str | None = "operator@example.invalid"):
- self._contact = contact
-
- async def musicbrainz_contact(self):
- return self._contact
-
-
def _handler(response_map):
def handle(request: httpx.Request) -> httpx.Response:
path = request.url.path
@@ -30,7 +22,7 @@ async def test_search_release_returns_top_result_and_confidence():
body = {"releases": [{"id": "abc-123", "title": "The Great Album",
"artist-credit": [{"name": "Some Artist"}]}]}
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(_handler({"release": body})),
)
result, ratio = await client.search_release("Some Artist", "The Great Album")
@@ -44,7 +36,7 @@ async def test_search_release_returns_top_result_and_confidence():
@pytest.mark.asyncio
async def test_no_results_returns_none_and_zero_confidence():
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(_handler({"release": {"releases": []}})),
)
result, ratio = await client.search_release("Nobody", "Nonexistent Obscure Album")
@@ -80,7 +72,7 @@ async def test_strict_match_does_not_pay_for_a_second_request():
})
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
await client.search_release("Some Artist", "The Great Album")
@@ -112,7 +104,7 @@ async def test_falls_back_to_a_loose_query_when_the_strict_one_finds_nothing():
})
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
result, ratio = await client.search_release("Groundation", "Hebron Gate (2003)")
@@ -139,7 +131,7 @@ async def test_confidence_reflects_a_wrong_artist_on_a_same_titled_release():
})
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
result, ratio = await client.search_release("Groundation", "Live")
@@ -150,8 +142,7 @@ async def test_confidence_reflects_a_wrong_artist_on_a_same_titled_release():
@pytest.mark.asyncio
-async def test_no_contact_configured_makes_no_request(monkeypatch):
- monkeypatch.delenv("MESHBAY_MUSICBRAINZ_CONTACT_DEFAULT", raising=False)
+async def test_no_contact_configured_makes_no_request():
calls = []
def handle(request: httpx.Request) -> httpx.Response:
@@ -159,7 +150,7 @@ async def test_no_contact_configured_makes_no_request(monkeypatch):
return httpx.Response(200, json={"releases": []})
client = MusicBrainzClient(
- roster=FakeRoster(contact=None),
+ owner_email="",
transport=httpx.MockTransport(handle),
)
result, ratio = await client.search_release("Anyone", "Anything")
@@ -178,7 +169,7 @@ async def test_the_configured_contact_is_sent_as_user_agent():
return httpx.Response(200, json={"releases": []})
client = MusicBrainzClient(
- roster=FakeRoster(contact="operator@example.invalid"),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
await client.search_release("Anyone", "Anything")
@@ -193,7 +184,7 @@ async def test_http_error_returns_none_gracefully():
return httpx.Response(500, json={"error": "server error"})
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
result, ratio = await client.search_release("Anyone", "Anything")
@@ -209,7 +200,7 @@ async def test_cover_art_missing_returns_none_not_an_error():
return httpx.Response(404)
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
content = await client.fetch_cover_art("abc-123")
@@ -224,7 +215,7 @@ async def test_cover_art_found_returns_bytes():
return httpx.Response(200, content=b"\xff\xd8fake-jpeg-bytes")
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
content = await client.fetch_cover_art("abc-123")
@@ -245,7 +236,7 @@ async def test_calls_are_paced_at_least_min_interval_apart():
return httpx.Response(200, json={"releases": []})
client = MusicBrainzClient(
- roster=FakeRoster(),
+ owner_email="operator@example.invalid",
transport=httpx.MockTransport(handle),
)
start = time.monotonic()
diff --git a/packages/meshbay-node/tests/test_musicbrainz_config_policy.py b/packages/meshbay-node/tests/test_musicbrainz_config_policy.py
deleted file mode 100644
index 3590f01..0000000
--- a/packages/meshbay-node/tests/test_musicbrainz_config_policy.py
+++ /dev/null
@@ -1,179 +0,0 @@
-"""
-The operator's MusicBrainz User-Agent contact string — docs/musicbay.md
-§3.2/§6. Same shape as test_tmdb_config_policy.py: a signed operator
-instruction, node-wide (group_id="") rather than per-group, stored via
-roster.py's group_settings table.
-
-Unlike TMDB's token, a contact string is not a secret — MusicBrainz's usage
-policy expects it to be visible to the service it's sent to — but the
-subject signed/audited still only ever says whether one was configured
-(never the address itself), the same "yes/no" shape as tmdb_config's
-subject, to keep a personal contact out of the audit log as free text.
-"""
-
-from pathlib import Path
-
-import pytest
-
-from meshbay_common.adminop import OP_MUSICBRAINZ_CONFIG
-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)
- 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
-
-
-def _fake_challenge(issued: list):
- return lambda op, subject, payload=None, group_id=None: issued.append(
- (op, subject, payload, group_id))
-
-
-# ── Refused before a challenge is even issued ───────────────────────────────
-
-async def test_non_string_contact_is_refused(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = _fake_challenge(issued)
-
- session._do_musicbrainz_config({"contact": 12345})
-
- 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_musicbrainz_config({"contact": "https://example.invalid/contact"})
-
- assert [m for m in session.sent if m.get("type") == "error"]
-
-
-# ── Who may change it, and what gets signed ─────────────────────────────────
-
-async def test_changing_it_needs_a_signature(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = _fake_challenge(issued)
-
- session._do_musicbrainz_config({})
-
- assert len(issued) == 1
- op, subject, payload, group_id = issued[0]
- assert op == OP_MUSICBRAINZ_CONFIG
- assert group_id == "", "node-wide, like tmdb_config — not tied to self._group_id"
-
-
-async def test_the_contact_itself_never_appears_in_the_signed_subject(tmp_path):
- """
- Not a secret the way a TMDB token is, but still kept out of the audited
- subject line as free text — same "yes/no configured" shape.
- """
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = _fake_challenge(issued)
-
- contact = "operator@example.invalid"
- session._do_musicbrainz_config({"contact": contact})
-
- _, subject, payload, _ = issued[0]
- assert contact not in subject
- assert payload["contact"] == contact, "the real value still has to reach the exec step somehow"
-
-
-async def test_subject_reflects_whether_a_contact_was_supplied(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = _fake_challenge(issued)
-
- session._do_musicbrainz_config({"contact": "x"})
-
- _, subject, _, _ = issued[0]
- assert subject == "contact_configured=yes"
-
-
-async def test_subject_says_no_contact_when_none_given(tmp_path):
- session = _session(tmp_path, "op", operator="op")
- session._has_admin_authority = lambda: True
- issued = []
- session._issue_admin_challenge = _fake_challenge(issued)
-
- session._do_musicbrainz_config({})
-
- _, subject, _, _ = issued[0]
- assert subject == "contact_configured=no"
-
-
-# ── 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.musicbrainz_contact() is None, \
- "absent must mean 'no contact configured' — no shipped default to fall back to"
- await roster.set_musicbrainz_contact("operator@example.invalid", set_by="op")
- assert await roster.musicbrainz_contact() == "operator@example.invalid"
- finally:
- await roster.close()
-
- reopened = Roster(db_path=tmp_path / "roster.db")
- await reopened.open()
- try:
- assert await reopened.musicbrainz_contact() == "operator@example.invalid"
- finally:
- await reopened.close()
-
-
-async def test_clearing_the_contact_reverts_to_unconfigured(tmp_path):
- roster = Roster(db_path=tmp_path / "roster.db")
- await roster.open()
- try:
- await roster.set_musicbrainz_contact("a-contact", set_by="op")
- assert await roster.musicbrainz_contact() == "a-contact"
-
- await roster.set_musicbrainz_contact("", set_by="op")
- assert await roster.musicbrainz_contact() is None, \
- "an explicit empty string clears the contact"
- finally:
- await roster.close()
-
-
-async def test_omitting_the_contact_leaves_it_unchanged(tmp_path):
- roster = Roster(db_path=tmp_path / "roster.db")
- await roster.open()
- try:
- await roster.set_musicbrainz_contact("a-contact", set_by="op")
- await roster.set_musicbrainz_contact(None, set_by="op")
- assert await roster.musicbrainz_contact() == "a-contact"
- finally:
- await roster.close()