/
/
/
1"""Tests for the identifier backfill of TheAudioDB metadata provider."""
2
3from typing import Any, cast
4from unittest.mock import AsyncMock, MagicMock
5
6import pytest
7from music_assistant_models.enums import AlbumType, ExternalID
8from music_assistant_models.media_items import Album, Artist, ProviderMapping, Track, UniqueList
9
10from music_assistant.providers.theaudiodb import (
11 CONF_ENABLE_ALBUM_METADATA,
12 CONF_ENABLE_IMAGES,
13 CONF_ENABLE_TRACK_METADATA,
14 SUPPORTED_FEATURES,
15 AudioDbMetadataProvider,
16)
17
18ARTIST_MBID = "7cf0ea9d-86b9-4dad-ba9e-2355a64899ea"
19RELEASEGROUP_MBID = "1b022e01-4da6-387b-8658-8678046e4cef"
20RECORDING_MBID = "b1a9c0e9-d987-4042-ae91-78d6a3267d69"
21
22
23async def test_album_backfill_runs_with_images_disabled() -> None:
24 """Album and artist identifiers are backfilled even when images are turned off."""
25 provider = _provider(images_enabled=False)
26 artist = _artist("Nirvana")
27 album = Album(
28 item_id="1",
29 provider="library",
30 name="Nevermind",
31 artists=UniqueList([artist]),
32 external_ids={(ExternalID.MB_RELEASEGROUP, RELEASEGROUP_MBID)},
33 provider_mappings=_mappings("1"),
34 )
35 provider._get_data = AsyncMock( # type: ignore[method-assign]
36 return_value={"album": [_adb_album()]}
37 )
38
39 metadata = await provider.get_album_metadata(album)
40
41 assert metadata is not None
42 assert metadata.images is None
43 assert album.year == 1991
44 assert album.album_type == AlbumType.ALBUM
45 assert artist.mbid == ARTIST_MBID
46 cast("AsyncMock", provider.mass.music.artists.update_item_in_library).assert_awaited_once()
47
48
49@pytest.mark.parametrize(
50 "library_album",
51 [
52 "Nevermind",
53 # the retail suffix Apple Music appends carries no identity of its own
54 "Nevermind - EP",
55 ],
56)
57async def test_track_release_group_written_when_album_matches(library_album: str) -> None:
58 """The album gets TheAudioDB's release group id when the matched record is that album."""
59 # images off, so this covers the track backfill not being tied to the images option
60 provider = _provider(images_enabled=False)
61 track = _track(library_album)
62 provider._get_data = AsyncMock( # type: ignore[method-assign]
63 return_value={"track": [_adb_track()]}
64 )
65
66 await provider.get_track_metadata(track)
67
68 cast("AsyncMock", provider.mass.music.albums.set_release_group).assert_awaited_once_with(
69 1, RELEASEGROUP_MBID
70 )
71
72
73@pytest.mark.parametrize(
74 ("library_album", "adb_album"),
75 [
76 ("Nirvana: The Best Of", "Nevermind"),
77 # near-identical titles are still separate releases
78 ("Vol. 1", "Vol. 2"),
79 ],
80)
81async def test_track_release_group_skipped_for_another_release(
82 library_album: str, adb_album: str
83) -> None:
84 """A recording shared with another release must not stamp its id on our album."""
85 provider = _provider()
86 track = _track(library_album)
87 provider._get_data = AsyncMock( # type: ignore[method-assign]
88 return_value={"track": [_adb_track(adb_album)]}
89 )
90
91 await provider.get_track_metadata(track)
92
93 cast("AsyncMock", provider.mass.music.albums.set_release_group).assert_not_awaited()
94 # the artist backfill is independent of the album, so it still happens
95 assert track.artists[0].mbid == ARTIST_MBID
96
97
98def _provider(*, images_enabled: bool = True) -> AudioDbMetadataProvider:
99 """
100 Return a provider with mocked dependencies and every metadata lookup enabled.
101
102 :param images_enabled: Whether the provider's image option is turned on.
103 """
104 mass = AsyncMock()
105 mass.metadata.locale = "en_US"
106 manifest = MagicMock()
107 manifest.domain = "theaudiodb"
108 config = MagicMock()
109 flags = {
110 CONF_ENABLE_ALBUM_METADATA: True,
111 CONF_ENABLE_TRACK_METADATA: True,
112 CONF_ENABLE_IMAGES: images_enabled,
113 }
114 config.get_value.side_effect = lambda key: flags.get(key, "GLOBAL")
115 return AudioDbMetadataProvider(mass, manifest, config, SUPPORTED_FEATURES)
116
117
118def _mappings(item_id: str) -> set[ProviderMapping]:
119 """Return a single library provider mapping for the given item id."""
120 return {ProviderMapping(item_id=item_id, provider_domain="test", provider_instance="test")}
121
122
123def _artist(name: str) -> Artist:
124 """Return a library artist without a MusicBrainz id."""
125 return Artist(
126 item_id="artist",
127 provider="library",
128 name=name,
129 provider_mappings=_mappings("artist"),
130 )
131
132
133def _track(album_name: str) -> Track:
134 """Return a library track matched by its MusicBrainz recording id."""
135 return Track(
136 item_id="track",
137 provider="library",
138 name="Come as You Are",
139 external_ids={(ExternalID.MB_RECORDING, RECORDING_MBID)},
140 artists=UniqueList([_artist("Nirvana")]),
141 album=Album(
142 item_id="1",
143 provider="library",
144 name=album_name,
145 provider_mappings=_mappings("1"),
146 ),
147 provider_mappings=_mappings("track"),
148 )
149
150
151def _adb_album() -> dict[str, Any]:
152 """Return a minimal TheAudioDB album record."""
153 return {
154 "strArtist": "Nirvana",
155 "strAlbum": "Nevermind",
156 "strMusicBrainzArtistID": ARTIST_MBID,
157 "intYearReleased": "1991",
158 "strReleaseFormat": "Album",
159 }
160
161
162def _adb_track(album_name: str = "Nevermind") -> dict[str, Any]:
163 """Return a minimal TheAudioDB track record."""
164 return {
165 "strArtist": "Nirvana",
166 "strAlbum": album_name,
167 "strTrack": "Come as You Are",
168 "strMusicBrainzArtistID": ARTIST_MBID,
169 "strMusicBrainzAlbumID": RELEASEGROUP_MBID,
170 }
171