beets/test/plugins/test_mbcollection.py
2026-01-06 09:54:02 +00:00

142 lines
4.6 KiB
Python

import re
import uuid
from contextlib import nullcontext as does_not_raise
import pytest
from beets.library import Album
from beets.test.helper import PluginMixin, TestHelper
from beets.ui import UserError
from beetsplug import mbcollection
class TestMbCollectionPlugin(PluginMixin, TestHelper):
"""Tests for the MusicBrainzCollectionPlugin class methods."""
plugin = "mbcollection"
COLLECTION_ID = str(uuid.uuid4())
@pytest.fixture(autouse=True)
def setup_config(self):
self.config["musicbrainz"]["user"] = "testuser"
self.config["musicbrainz"]["pass"] = "testpass"
self.config["mbcollection"]["collection"] = self.COLLECTION_ID
@pytest.fixture(autouse=True)
def helper(self):
self.setup_beets()
yield self
self.teardown_beets()
@pytest.mark.parametrize(
"user_collections,expectation",
[
(
[],
pytest.raises(
UserError, match=r"no collections exist for user"
),
),
(
[{"id": "c1", "entity-type": "event"}],
pytest.raises(UserError, match=r"No release collection found."),
),
(
[{"id": "c1", "entity-type": "release"}],
pytest.raises(UserError, match=r"invalid collection ID"),
),
(
[{"id": COLLECTION_ID, "entity-type": "release"}],
does_not_raise(),
),
],
ids=["no collections", "no release collections", "invalid ID", "valid"],
)
def test_get_collection_validation(
self, requests_mock, user_collections, expectation
):
requests_mock.get(
"/ws/2/collection", json={"collections": user_collections}
)
with expectation:
mbcollection.MusicBrainzCollectionPlugin().collection
def test_mbupdate(self, helper, requests_mock, monkeypatch):
"""Verify mbupdate sync of a MusicBrainz collection with the library.
This test ensures that the command:
- fetches collection releases using paginated requests,
- submits releases that exist locally but are missing from the remote
collection
- and removes releases from the remote collection that are not in the
local library. Small chunk sizes are forced to exercise pagination and
batching logic.
"""
for mb_albumid in [
# already present in remote collection
"in_collection1",
"in_collection2",
# two new albums not in remote collection
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
]:
helper.lib.add(Album(mb_albumid=mb_albumid))
# The relevant collection
requests_mock.get(
"/ws/2/collection",
json={
"collections": [
{
"id": self.COLLECTION_ID,
"entity-type": "release",
"release-count": 3,
}
]
},
)
collection_releases = f"/ws/2/collection/{self.COLLECTION_ID}/releases"
# Force small fetch chunk to require multiple paged requests.
monkeypatch.setattr(
"beetsplug.mbcollection.MBCollection.FETCH_CHUNK_SIZE", 2
)
# 3 releases are fetched in two pages.
requests_mock.get(
re.compile(rf".*{collection_releases}\b.*&offset=0.*"),
json={
"releases": [{"id": "in_collection1"}, {"id": "not_in_library"}]
},
)
requests_mock.get(
re.compile(rf".*{collection_releases}\b.*&offset=2.*"),
json={"releases": [{"id": "in_collection2"}]},
)
# Force small submission chunk
monkeypatch.setattr(
"beetsplug.mbcollection.MBCollection.SUBMISSION_CHUNK_SIZE", 1
)
# so that releases are added using two requests
requests_mock.put(
re.compile(
rf".*{collection_releases}/00000000-0000-0000-0000-000000000001"
)
)
requests_mock.put(
re.compile(
rf".*{collection_releases}/00000000-0000-0000-0000-000000000002"
)
)
# and finally, one release is removed
requests_mock.delete(
re.compile(rf".*{collection_releases}/not_in_library")
)
helper.run_command("mbupdate", "--remove")
assert requests_mock.call_count == 6