Fix MusicBrainz external ID match (#1485)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Tue, 9 Jul 2024 17:18:51 +0000 (19:18 +0200)
committerGitHub <noreply@github.com>
Tue, 9 Jul 2024 17:18:51 +0000 (19:18 +0200)
16 files changed:
music_assistant/common/helpers/util.py
music_assistant/common/models/enums.py
music_assistant/common/models/media_items.py
music_assistant/server/controllers/cache.py
music_assistant/server/controllers/music.py
music_assistant/server/helpers/compare.py
music_assistant/server/helpers/tags.py
music_assistant/server/providers/fanarttv/__init__.py
music_assistant/server/providers/filesystem_local/base.py
music_assistant/server/providers/jellyfin/const.py
music_assistant/server/providers/jellyfin/parsers.py
music_assistant/server/providers/musicbrainz/__init__.py
music_assistant/server/providers/theaudiodb/__init__.py
tests/server/providers/jellyfin/__snapshots__/test_parsers.ambr
tests/server/providers/jellyfin/test_parsers.py
tests/server/test_compare.py

index cdc4f1c05280e97f805c75076a7c27eb516410e9..260dd82131fdd85278c82edcf55af2a8275ecd87 100644 (file)
@@ -384,6 +384,6 @@ def is_valid_uuid(uuid_to_test: str) -> bool:
     """Check if uuid string is a valid UUID."""
     try:
         uuid_obj = UUID(uuid_to_test)
-    except ValueError:
+    except (ValueError, TypeError):
         return False
     return str(uuid_obj) == uuid_to_test
index bd018efa748c57bb3618338143203542020a44fc..cca6ae424450e25c0b25321159e1b59b995795b9 100644 (file)
@@ -43,11 +43,12 @@ class MediaType(StrEnum, metaclass=MediaTypeMeta):
 class ExternalID(StrEnum):
     """Enum with External ID types."""
 
-    # musicbrainz:
-    # for tracks this is the RecordingID
-    # for albums this is the ReleaseGroupID (NOT the release ID!)
-    # for artists this is the ArtistID
-    MUSICBRAINZ = "musicbrainz"
+    MB_ARTIST = "musicbrainz_artistid"  # MusicBrainz Artist ID (or AlbumArtist ID)
+    MB_ALBUM = "musicbrainz_albumid"  # MusicBrainz Album ID
+    MB_RELEASEGROUP = "musicbrainz_releasegroupid"  # MusicBrainz ReleaseGroupID
+    MB_TRACK = "musicbrainz_trackid"  # MusicBrainz Track ID
+    MB_RECORDING = "musicbrainz_recordingid"  # MusicBrainz Recording ID
+
     ISRC = "isrc"  # used to identify unique recordings
     BARCODE = "barcode"  # EAN-13 barcode for identifying albums
     ACOUSTID = "acoustid"  # unique fingerprint (id) for a recording
@@ -61,6 +62,26 @@ class ExternalID(StrEnum):
         """Set default enum member if an unknown value is provided."""
         return cls.UNKNOWN
 
+    @property
+    def is_unique(self) -> bool:
+        """Return if the ExternalID is unique."""
+        return self.is_musicbrainz or self in (
+            ExternalID.ACOUSTID,
+            ExternalID.DISCOGS,
+            ExternalID.TADB,
+        )
+
+    @property
+    def is_musicbrainz(self) -> bool:
+        """Return if the ExternalID is a MusicBrainz identifier."""
+        return self in (
+            ExternalID.MB_RELEASEGROUP,
+            ExternalID.MB_ALBUM,
+            ExternalID.MB_TRACK,
+            ExternalID.MB_ARTIST,
+            ExternalID.MB_RECORDING,
+        )
+
 
 class LinkType(StrEnum):
     """Enum with link types."""
index d44b39f853ac765cfb05dc0789346855a43a84f8..d24115d85a5561cae016e52caf8588cfa12d6cf3 100644 (file)
@@ -287,31 +287,50 @@ class _MediaItemBase(DataClassDictMixin):
         if self.sort_name is None:
             self.sort_name = create_sort_name(self.name)
 
+    def get_external_id(self, external_id_type: ExternalID) -> str | None:
+        """Get (the first instance) of given External ID or None if not found."""
+        for ext_id in self.external_ids:
+            if ext_id[0] != external_id_type:
+                continue
+            return ext_id[1]
+        return None
+
+    def add_external_id(self, external_id_type: ExternalID, value: str) -> None:
+        """Add ExternalID."""
+        if external_id_type.is_musicbrainz and not is_valid_uuid(value):
+            msg = f"Invalid MusicBrainz identifier: {value}"
+            raise InvalidDataError(msg)
+        if external_id_type.is_unique and (
+            existing := next((x for x in self.external_ids if x[0] == external_id_type), None)
+        ):
+            self.external_ids.remove(existing)
+        self.external_ids.add((external_id_type, value))
+
     @property
     def mbid(self) -> str | None:
         """Return MusicBrainz ID."""
-        return self.get_external_id(ExternalID.MUSICBRAINZ)
+        if self.media_type == MediaType.ARTIST:
+            return self.get_external_id(ExternalID.MB_ARTIST)
+        if self.media_type == MediaType.ALBUM:
+            return self.get_external_id(ExternalID.MB_ALBUM)
+        if self.media_type == MediaType.TRACK:
+            return self.get_external_id(ExternalID.MB_RECORDING)
+        return None
 
     @mbid.setter
     def mbid(self, value: str) -> None:
         """Set MusicBrainz External ID."""
-        if not value:
+        if self.media_type == MediaType.ARTIST:
+            self.add_external_id(ExternalID.MB_ARTIST, value)
+        elif self.media_type == MediaType.ALBUM:
+            self.add_external_id(ExternalID.MB_ALBUM, value)
+        elif self.media_type == MediaType.TRACK:
+            # NOTE: for tracks we use the recording id to
+            # differentiate a unique recording
+            # and not the track id (as that is just the reference
+            #  of the recording on a specific album)
+            self.add_external_id(ExternalID.MB_RECORDING, value)
             return
-        if not is_valid_uuid(value):
-            msg = f"Invalid MusicBrainz identifier: {value}"
-            raise InvalidDataError(msg)
-        if existing := next((x for x in self.external_ids if x[0] == ExternalID.MUSICBRAINZ), None):
-            # Musicbrainz ID is unique so remove existing entry
-            self.external_ids.remove(existing)
-        self.external_ids.add((ExternalID.MUSICBRAINZ, value))
-
-    def get_external_id(self, external_id_type: ExternalID) -> str | None:
-        """Get (the first instance) of given External ID or None if not found."""
-        for ext_id in self.external_ids:
-            if ext_id[0] != external_id_type:
-                continue
-            return ext_id[1]
-        return None
 
     def __hash__(self) -> int:
         """Return custom hash."""
index 870f33742ac522846e773609e4d56b8156c31f12..efd297744de244516f5359c076898ba8ad53af46 100644 (file)
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
 
 LOGGER = logging.getLogger(f"{MASS_LOGGER_NAME}.cache")
 CONF_CLEAR_CACHE = "clear_cache"
-DB_SCHEMA_VERSION = 1
+DB_SCHEMA_VERSION = 3
 
 
 class CacheController(CoreController):
index 7382f683a735d4b166c22f6798145a9e5751935e..bf2e3cb9cb2dfdf51aaa57c16cca794511a649c4 100644 (file)
@@ -66,7 +66,7 @@ DEFAULT_SYNC_INTERVAL = 3 * 60  # default sync interval in minutes
 CONF_SYNC_INTERVAL = "sync_interval"
 CONF_DELETED_PROVIDERS = "deleted_providers"
 CONF_ADD_LIBRARY_ON_PLAY = "add_library_on_play"
-DB_SCHEMA_VERSION: Final[int] = 2
+DB_SCHEMA_VERSION: Final[int] = 3
 
 
 class MusicController(CoreController):
@@ -900,10 +900,21 @@ class MusicController(CoreController):
         self.logger.info(
             "Migrating database from version %s to %s", prev_version, DB_SCHEMA_VERSION
         )
-        if prev_version == 1:
-            # migrate from version 1 to 2
+        if prev_version == 2:
+            # migrate from version 2 to 3
+            # convert musicbrainz external id's
             await self.database.execute(
-                f"DELETE FROM {DB_TABLE_PLAYLOG} WHERE provider = 'builtin'"
+                f"UPDATE {DB_TABLE_ARTISTS} SET external_ids = "
+                "replace(external_ids, 'musicbrainz', 'musicbrainz_artistid')"
+            )
+            # convert musicbrainz external id's
+            await self.database.execute(
+                f"UPDATE {DB_TABLE_ALBUMS} SET external_ids = "
+                "replace(external_ids, 'musicbrainz', 'musicbrainz_releasegroupid')"
+            )
+            await self.database.execute(
+                f"UPDATE {DB_TABLE_TRACKS} SET external_ids = "
+                "replace(external_ids, 'musicbrainz', 'musicbrainz_recordingid')"
             )
             await self.database.commit()
             return
index 768a6cae5e722999ace3ad026306c0c9ca09c33e..34a111a12b8edd2abcd84f72d610e30e2b52f786 100644 (file)
@@ -59,7 +59,7 @@ def compare_artist(
     if compare_item_ids(base_item, compare_item):
         return True
     # return early on (un)matched external id
-    for ext_id in (ExternalID.DISCOGS, ExternalID.MUSICBRAINZ, ExternalID.TADB):
+    for ext_id in (ExternalID.DISCOGS, ExternalID.MB_ARTIST, ExternalID.TADB):
         external_id_match = compare_external_ids(
             base_item.external_ids, compare_item.external_ids, ext_id
         )
@@ -84,7 +84,7 @@ def compare_album(
     # return early on (un)matched external id
     for ext_id in (
         ExternalID.DISCOGS,
-        ExternalID.MUSICBRAINZ,
+        ExternalID.MB_ALBUM,
         ExternalID.TADB,
         ExternalID.ASIN,
         ExternalID.BARCODE,
@@ -130,7 +130,7 @@ def compare_track(
         return True
     # return early on (un)matched external id
     for ext_id in (
-        ExternalID.MUSICBRAINZ,
+        ExternalID.MB_RECORDING,
         ExternalID.DISCOGS,
         ExternalID.ACOUSTID,
         ExternalID.TADB,
@@ -385,7 +385,7 @@ def compare_external_ids(
             if base_id[1:] in compare_ids:
                 return True
         # return false if the identifier is unique (e.g. musicbrainz id)
-        if external_id_type in (ExternalID.DISCOGS, ExternalID.MUSICBRAINZ, ExternalID.TADB):
+        if external_id_type.is_unique:
             return False
     return None
 
index 18fb443997a0860a334668df71da335fe13a04c2..e7d00a1fdb9a1b16d36db21dd0c045202e373bd4 100644 (file)
@@ -212,8 +212,8 @@ class AudioTags:
         return self.tags.get("musicbrainzreleasegroupid")
 
     @property
-    def musicbrainz_releaseid(self) -> str | None:
-        """Return musicbrainz_releaseid tag if present."""
+    def musicbrainz_albumid(self) -> str | None:
+        """Return musicbrainz_albumid tag if present."""
         return self.tags.get("musicbrainzreleaseid", self.tags.get("musicbrainzalbumid"))
 
     @property
index f39f38d561806cde8a52c09e5b9aa9c2d6e66537..229f9d90435bb08fd40a378a717b932c94b6c59f 100644 (file)
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
 import aiohttp.client_exceptions
 from asyncio_throttle import Throttler
 
-from music_assistant.common.models.enums import ProviderFeature
+from music_assistant.common.models.enums import ExternalID, ProviderFeature
 from music_assistant.common.models.media_items import ImageType, MediaItemImage, MediaItemMetadata
 from music_assistant.server.controllers.cache import use_cache
 from music_assistant.server.helpers.app_vars import app_var  # pylint: disable=no-name-in-module
@@ -108,12 +108,12 @@ class FanartTvMetadataProvider(MetadataProvider):
 
     async def get_album_metadata(self, album: Album) -> MediaItemMetadata | None:
         """Retrieve metadata for album on fanart.tv."""
-        if not album.mbid:
+        if (mbid := album.get_external_id(ExternalID.MB_RELEASEGROUP)) is None:
             return None
         self.logger.debug("Fetching metadata for Album %s on Fanart.tv", album.name)
-        if data := await self._get_data(f"music/albums/{album.mbid}"):
+        if data := await self._get_data(f"music/albums/{mbid}"):
             if data and data.get("albums"):
-                data = data["albums"][album.mbid]
+                data = data["albums"][mbid]
                 metadata = MediaItemMetadata()
                 metadata.images = []
                 for key, img_type in IMG_MAPPING.items():
index 2da274f7c8fb5b0e1ed73a9a4ff2e1ea99cb8886..377d0a28dc8336aa29cf6342a5cbf889e95c4021 100644 (file)
@@ -854,8 +854,10 @@ class FileSystemProviderBase(MusicProvider):
         track.mbid = tags.musicbrainz_recordingid
         track.metadata.chapters = UniqueList(tags.chapters)
         if album:
-            if not album.mbid:
-                album.mbid = tags.musicbrainz_releasegroupid
+            if not album.mbid and tags.musicbrainz_albumid:
+                album.mbid = tags.musicbrainz_albumid
+            if tags.musicbrainz_releasegroupid:
+                album.add_external_id(ExternalID.MB_RELEASEGROUP, tags.musicbrainz_releasegroupid)
             if not album.year:
                 album.year = tags.year
             album.album_type = tags.album_type
@@ -1008,8 +1010,10 @@ class FileSystemProviderBase(MusicProvider):
                 album.name = info.get("title", info.get("name", name))
                 if sort_name := info.get("sortname"):
                     album.sort_name = sort_name
-                if mbid := info.get("musicbrainzreleasegroupid"):
-                    album.mbid = mbid
+                if releasegroup_id := info.get("musicbrainzreleasegroupid"):
+                    album.add_external_id(ExternalID.MB_RELEASEGROUP, releasegroup_id)
+                if album_id := info.get("musicbrainzalbumid"):
+                    album.add_external_id(ExternalID.MB_ALBUM, album_id)
                 if mb_artist_id := info.get("musicbrainzalbumartistid"):
                     if album.artists and not album.artists[0].mbid:
                         album.artists[0].mbid = mb_artist_id
index b01fd7dc07cce1e15340b6828fc4be81481038d5..7ea656f74f9e8c904d7f385fcb5b4bebfacc0215 100644 (file)
@@ -35,6 +35,7 @@ ITEM_KEY_PRODUCTION_YEAR: Final = "ProductionYear"
 ITEM_KEY_OVERVIEW: Final = "Overview"
 ITEM_KEY_MUSICBRAINZ_RELEASE_GROUP: Final = "MusicBrainzReleaseGroup"
 ITEM_KEY_MUSICBRAINZ_ARTIST: Final = "MusicBrainzArtist"
+ITEM_KEY_MUSICBRAINZ_ALBUM: Final = "MusicBrainzAlbum"
 ITEM_KEY_MUSICBRAINZ_TRACK: Final = "MusicBrainzTrack"
 ITEM_KEY_SORT_NAME: Final = "SortName"
 ITEM_KEY_ALBUM_ARTIST: Final = "AlbumArtist"
index 907f44b4121069bbcd07c6c5dafb85cc4e501bf3..84676e863aa1f7ca32d15a8058872449d6491d03 100644 (file)
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
 
 from aiojellyfin.const import ImageType as JellyImageType
 
-from music_assistant.common.models.enums import ContentType, ImageType, MediaType
+from music_assistant.common.models.enums import ContentType, ExternalID, ImageType, MediaType
 from music_assistant.common.models.errors import InvalidDataError
 from music_assistant.common.models.media_items import (
     Album,
@@ -34,6 +34,7 @@ from .const import (
     ITEM_KEY_IMAGE_TAGS,
     ITEM_KEY_MEDIA_CODEC,
     ITEM_KEY_MEDIA_STREAMS,
+    ITEM_KEY_MUSICBRAINZ_ALBUM,
     ITEM_KEY_MUSICBRAINZ_ARTIST,
     ITEM_KEY_MUSICBRAINZ_RELEASE_GROUP,
     ITEM_KEY_MUSICBRAINZ_TRACK,
@@ -81,9 +82,24 @@ def parse_album(
     album.metadata.images = _get_artwork(instance_id, connection, jellyfin_album)
     if ITEM_KEY_OVERVIEW in jellyfin_album:
         album.metadata.description = jellyfin_album[ITEM_KEY_OVERVIEW]
+    if ITEM_KEY_MUSICBRAINZ_ALBUM in jellyfin_album[ITEM_KEY_PROVIDER_IDS]:
+        try:
+            album.add_external_id(
+                ExternalID.MB_ALBUM,
+                jellyfin_album[ITEM_KEY_PROVIDER_IDS][ITEM_KEY_MUSICBRAINZ_ALBUM],
+            )
+        except InvalidDataError as error:
+            logger.warning(
+                "Jellyfin has an invalid musicbrainz album id for album %s",
+                album.name,
+                exc_info=error if logger.isEnabledFor(logging.DEBUG) else None,
+            )
     if ITEM_KEY_MUSICBRAINZ_RELEASE_GROUP in jellyfin_album[ITEM_KEY_PROVIDER_IDS]:
         try:
-            album.mbid = jellyfin_album[ITEM_KEY_PROVIDER_IDS][ITEM_KEY_MUSICBRAINZ_RELEASE_GROUP]
+            album.add_external_id(
+                ExternalID.MB_RELEASEGROUP,
+                jellyfin_album[ITEM_KEY_PROVIDER_IDS][ITEM_KEY_MUSICBRAINZ_RELEASE_GROUP],
+            )
         except InvalidDataError as error:
             logger.warning(
                 "Jellyfin has an invalid musicbrainz id for album %s",
index 7491ddb28b17a53c4ba7fba39d2857154ae184f7..2a2b7e9ca5d827c99e24eeb7bc7d1c6d58f12925 100644 (file)
@@ -15,7 +15,7 @@ from mashumaro.exceptions import MissingField
 
 from music_assistant.common.helpers.json import json_loads
 from music_assistant.common.helpers.util import parse_title_and_version
-from music_assistant.common.models.enums import ProviderFeature
+from music_assistant.common.models.enums import ExternalID, ProviderFeature
 from music_assistant.common.models.errors import (
     InvalidDataError,
     MediaNotFoundError,
@@ -341,6 +341,19 @@ class MusicbrainzProvider(MetadataProvider):
         msg = "Invalid MusicBrainz recording ID provided"
         raise InvalidDataError(msg)
 
+    async def get_release_details(self, album_id: str) -> MusicBrainzRelease:
+        """Get Release/Album details by providing a MusicBrainz Album id."""
+        endpoint = f"release/{album_id}?inc=artist-credits+aliases+labels"
+        if result := await self.get_data(endpoint):
+            if "id" not in result:
+                result["id"] = album_id
+            try:
+                return MusicBrainzRelease.from_dict(replace_hyphens(result))
+            except MissingField as err:
+                raise InvalidDataError from err
+        msg = "Invalid MusicBrainz Album ID provided"
+        raise InvalidDataError(msg)
+
     async def get_releasegroup_details(self, releasegroup_id: str) -> MusicBrainzReleaseGroup:
         """Get ReleaseGroup details by providing a MusicBrainz ReleaseGroup id."""
         endpoint = f"release-group/{releasegroup_id}?inc=artists+aliases"
@@ -351,7 +364,7 @@ class MusicbrainzProvider(MetadataProvider):
                 return MusicBrainzReleaseGroup.from_dict(replace_hyphens(result))
             except MissingField as err:
                 raise InvalidDataError from err
-        msg = "Invalid MusicBrainz ReleaseGroup ID or barcode provided"
+        msg = "Invalid MusicBrainz ReleaseGroup ID provided"
         raise InvalidDataError(msg)
 
     async def get_artist_details_by_album(
@@ -362,11 +375,15 @@ class MusicbrainzProvider(MetadataProvider):
 
         MusicBrainzArtist object that is returned does not contain the optional data.
         """
-        if not ref_album.mbid:
-            return None
         result = None
-        with suppress(InvalidDataError):
-            result = await self.get_releasegroup_details(ref_album.mbid)
+        if mb_id := ref_album.get_external_id(ExternalID.MB_RELEASEGROUP):
+            with suppress(InvalidDataError):
+                result = await self.get_releasegroup_details(mb_id)
+        elif mb_id := ref_album.get_external_id(ExternalID.MB_ALBUM):
+            with suppress(InvalidDataError):
+                result = await self.get_release_details(mb_id)
+        else:
+            return None
         if not (result and result.artist_credit):
             return None
         for strict in (True, False):
index 0957224bc2aff79ac502cc923e6da695c692467b..ccde5cb52a71d4e72793ef89ad2672b6da76727b 100644 (file)
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
 import aiohttp.client_exceptions
 from asyncio_throttle import Throttler
 
-from music_assistant.common.models.enums import ProviderFeature
+from music_assistant.common.models.enums import ExternalID, ProviderFeature
 from music_assistant.common.models.media_items import (
     Album,
     AlbumType,
@@ -128,10 +128,9 @@ class AudioDbMetadataProvider(MetadataProvider):
 
     async def get_album_metadata(self, album: Album) -> MediaItemMetadata | None:
         """Retrieve metadata for album on theaudiodb."""
-        if not album.mbid:
-            # for 100% accuracy we require the musicbrainz id for all lookups
+        if (mbid := album.get_external_id(ExternalID.MB_RELEASEGROUP)) is None:
             return None
-        result = await self._get_data("album-mb.php", i=album.mbid)
+        result = await self._get_data("album-mb.php", i=mbid)
         if result and result.get("album"):
             adb_album = result["album"][0]
             # fill in some missing album info if needed
@@ -166,8 +165,9 @@ class AudioDbMetadataProvider(MetadataProvider):
                         continue
                     if (
                         track.album
-                        and track.album.mbid
-                        and track.album.mbid != item["strMusicBrainzAlbumID"]
+                        and (mb_rgid := track.album.get_external_id(ExternalID.MB_RELEASEGROUP))
+                        # AudioDb swapped MB Album ID and ReleaseGroup ID ?!
+                        and mb_rgid != item["strMusicBrainzAlbumID"]
                     ):
                         continue
                     if not compare_strings(track_artist.name, item["strArtist"]):
index 79998f7b744d0c816647c36ebf7b8e8543ba4935..c57e958528994387cce1c22a94e976d64cc76689 100644 (file)
     ]),
     'external_ids': list([
       list([
-        'musicbrainz',
+        'musicbrainz_albumid',
+        'bf25b030-0cbb-495a-8d79-6c7fee20a089',
+      ]),
+      list([
+        'musicbrainz_releasegroupid',
         '0193355a-cdfb-3936-afd2-44d651eb006d',
       ]),
     ]),
     ]),
     'external_ids': list([
       list([
-        'musicbrainz',
+        'musicbrainz_albumid',
+        'b13a174d-527d-44a1-b8f8-a4c78b03b7d9',
+      ]),
+      list([
+        'musicbrainz_releasegroupid',
         'f002d6b7-17af-4f9e-8d30-5486548ffe6f',
       ]),
     ]),
   dict({
     'external_ids': list([
       list([
-        'musicbrainz',
+        'musicbrainz_artistid',
         '99164692-c02d-407c-81c9-25d338dd21f4',
       ]),
     ]),
     'duration': 224,
     'external_ids': list([
       list([
-        'musicbrainz',
+        'musicbrainz_recordingid',
         '17d1019d-d4f4-326c-b4bb-d8aec2607bd7',
       ]),
     ]),
index b7dacf12706253a52a64b35fc56546f075772043..12a49457e08230c87be53a6b68fe9a75881b889a 100644 (file)
@@ -45,8 +45,10 @@ async def test_parse_artists(
     """Test we can parse artists."""
     async with aiofiles.open(example) as fp:
         raw_data = ARTIST_DECODER.decode(await fp.read())
-    parsed = parse_artist(_LOGGER, "xx-instance-id-xx", connection, raw_data)
-    assert snapshot == parsed.to_dict()
+    parsed = parse_artist(_LOGGER, "xx-instance-id-xx", connection, raw_data).to_dict()
+    # sort external Ids to ensure they are always in the same order for snapshot testing
+    parsed["external_ids"].sort()
+    assert snapshot == parsed
 
 
 @pytest.mark.parametrize("example", ALBUM_FIXTURES, ids=lambda val: str(val.stem))
@@ -56,8 +58,10 @@ async def test_parse_albums(
     """Test we can parse albums."""
     async with aiofiles.open(example) as fp:
         raw_data = ARTIST_DECODER.decode(await fp.read())
-    parsed = parse_album(_LOGGER, "xx-instance-id-xx", connection, raw_data)
-    assert snapshot == parsed.to_dict()
+    parsed = parse_album(_LOGGER, "xx-instance-id-xx", connection, raw_data).to_dict()
+    # sort external Ids to ensure they are always in the same order for snapshot testing
+    parsed["external_ids"].sort()
+    assert snapshot == parsed
 
 
 @pytest.mark.parametrize("example", TRACK_FIXTURES, ids=lambda val: str(val.stem))
@@ -67,5 +71,7 @@ async def test_parse_tracks(
     """Test we can parse tracks."""
     async with aiofiles.open(example) as fp:
         raw_data = ARTIST_DECODER.decode(await fp.read())
-    parsed = parse_track(_LOGGER, "xx-instance-id-xx", connection, raw_data)
-    assert snapshot == parsed.to_dict()
+    parsed = parse_track(_LOGGER, "xx-instance-id-xx", connection, raw_data).to_dict()
+    # sort external Ids to ensure they are always in the same order for snapshot testing
+    parsed["external_ids"]
+    assert snapshot == parsed
index 9cfb0208901bc715ea141ad4263220e8fad32842..8fad014b0af6ba4924d4d7a52ca62cb882718e21 100644 (file)
@@ -60,12 +60,12 @@ def test_compare_artist() -> None:
     artist_b.name = "Artist B"
     artist_b.item_id = "2"
     artist_b.provider = "test2"
-    artist_a.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "123")}
+    artist_a.external_ids = {(media_items.ExternalID.MB_ARTIST, "123")}
     artist_b.external_ids = artist_a.external_ids
     assert compare.compare_artist(artist_a, artist_b) is True
     # test on external id mismatch
     artist_b.name = artist_a.name
-    artist_b.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "1234")}
+    artist_b.external_ids = {(media_items.ExternalID.MB_ARTIST, "1234")}
     assert compare.compare_artist(artist_a, artist_b) is False
 
 
@@ -115,12 +115,12 @@ def test_compare_album() -> None:
     album_b.name = "Album B"
     album_b.item_id = "2"
     album_b.provider = "test2"
-    album_a.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "123")}
+    album_a.external_ids = {(media_items.ExternalID.MB_ALBUM, "123")}
     album_b.external_ids = album_a.external_ids
     assert compare.compare_album(album_a, album_b) is True
     # test on external id mismatch
     album_b.name = album_a.name
-    album_b.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "1234")}
+    album_b.external_ids = {(media_items.ExternalID.MB_ALBUM, "1234")}
     assert compare.compare_album(album_a, album_b) is False
     album_a.external_ids = set()
     album_b.external_ids = set()
@@ -216,12 +216,12 @@ def test_compare_track() -> None:  # noqa: PLR0915
     track_b.name = "Track B"
     track_b.item_id = "2"
     track_b.provider = "test2"
-    track_a.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "123")}
+    track_a.external_ids = {(media_items.ExternalID.MB_RECORDING, "123")}
     track_b.external_ids = track_a.external_ids
     assert compare.compare_track(track_a, track_b) is True
     # test on external id mismatch
     track_b.name = track_a.name
-    track_b.external_ids = {(media_items.ExternalID.MUSICBRAINZ, "1234")}
+    track_b.external_ids = {(media_items.ExternalID.MB_RECORDING, "1234")}
     assert compare.compare_track(track_a, track_b) is False
     track_a.external_ids = set()
     track_b.external_ids = set()
@@ -280,10 +280,10 @@ def test_compare_track() -> None:  # noqa: PLR0915
     # this can happen for some classical music albums
     track_a.external_ids = {
         (media_items.ExternalID.ISRC, "123"),
-        (media_items.ExternalID.MUSICBRAINZ, "abc"),
+        (media_items.ExternalID.MB_RECORDING, "abc"),
     }
     track_b.external_ids = {
         (media_items.ExternalID.ISRC, "123"),
-        (media_items.ExternalID.MUSICBRAINZ, "abcd"),
+        (media_items.ExternalID.MB_RECORDING, "abcd"),
     }
     assert compare.compare_track(track_a, track_b) is False