From b7733e812fadd6007976d262bd1d793572a36ba7 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sat, 29 Aug 2026 11:15:54 +0200 Subject: feat: node workflow redesign — wizard auto-config, reset, MusicBrainz contact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wizard (Electron): - Auto-provisions node config (hub URL + username) from logged-in user - node:start handles both cold start and restart of misconfigured daemon - Waits for daemon to reach 'running', auto-links node key on hub - probeNode accepts intermediate states for wizard progress feedback Reset (meshbay-node reset): - Unlinks node key from hub (DELETE /me/node_key, best-effort) - Stops and disables daemon (systemctl --user disable --now) - Erases ~/.config/meshbay, ~/.local/share/meshbay, ~/.local/state/meshbay MusicBrainz contact: - Resolved from owner's hub email instead of per-node roster config - Removed musicbrainz_contact UI and WebRTC handshake field - Removed set_musicbrainz_contact/musicbrainz_contact from roster Node pairing: - Added operator pairing banner on NodePage - Added operator_paired flag to list_groups Co-Authored-By: Claude Opus 4.6 --- packages/meshbay-node/tests/test_musicbrainz.py | 33 +++++++++---------------- 1 file changed, 12 insertions(+), 21 deletions(-) (limited to 'packages/meshbay-node/tests/test_musicbrainz.py') 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() -- cgit v1.2.3