/
/
/
1"""Tests for mediaitem compare helper functions."""
2
3from music_assistant_models import media_items
4from music_assistant_models.enums import ExternalID
5
6from music_assistant.helpers import compare
7
8
9def test_compare_version() -> None:
10 """Test the version compare helper."""
11 assert compare.compare_version("Remaster", "remaster") is True
12 assert compare.compare_version("Remastered", "remaster") is True
13 assert compare.compare_version("Remaster", "") is False
14 assert compare.compare_version("Remaster", "Remix") is False
15 assert compare.compare_version("", "Deluxe") is False
16 assert compare.compare_version("", "Live") is False
17 assert compare.compare_version("Live", "live") is True
18 assert compare.compare_version("Live", "live version") is True
19 assert compare.compare_version("Live version", "live") is True
20 assert compare.compare_version("Deluxe Edition", "Deluxe") is True
21 assert compare.compare_version("Deluxe Karaoke Edition", "Deluxe") is False
22 assert compare.compare_version("Deluxe Karaoke Edition", "Karaoke") is False
23 assert compare.compare_version("Deluxe Edition", "Edition Deluxe") is True
24 assert compare.compare_version("", "Karaoke Version") is False
25 assert compare.compare_version("Karaoke", "Karaoke Version") is True
26 assert compare.compare_version("Remaster", "Remaster Edition Deluxe") is False
27 assert compare.compare_version("Remastered Version", "Deluxe Version") is False
28
29
30def test_compare_artist() -> None:
31 """Test artist comparison."""
32 artist_a = media_items.Artist(
33 item_id="1",
34 provider="test1",
35 name="Artist A",
36 provider_mappings={
37 media_items.ProviderMapping(
38 item_id="1", provider_domain="test", provider_instance="test1"
39 )
40 },
41 )
42 artist_b = media_items.Artist(
43 item_id="1",
44 provider="test2",
45 name="Artist A",
46 provider_mappings={
47 media_items.ProviderMapping(
48 item_id="2", provider_domain="test", provider_instance="test2"
49 )
50 },
51 )
52 # test match on name match
53 assert compare.compare_artist(artist_a, artist_b) is True
54 # test match on name mismatch
55 artist_b.name = "Artist B"
56 assert compare.compare_artist(artist_a, artist_b) is False
57 # test on exact item_id match
58 artist_b.item_id = artist_a.item_id
59 artist_b.provider = artist_a.provider
60 assert compare.compare_artist(artist_a, artist_b) is True
61 # test on external id match
62 artist_b.name = "Artist B"
63 artist_b.item_id = "2"
64 artist_b.provider = "test2"
65 artist_a.external_ids = {(ExternalID.MB_ARTIST, "123")}
66 artist_b.external_ids = artist_a.external_ids
67 assert compare.compare_artist(artist_a, artist_b) is True
68 # test on external id mismatch
69 artist_b.name = artist_a.name
70 artist_b.external_ids = {(ExternalID.MB_ARTIST, "1234")}
71 assert compare.compare_artist(artist_a, artist_b) is False
72 # test on external id mismatch while name matches
73 artist_a = media_items.Artist(
74 item_id="1",
75 provider="test1",
76 name="Artist A",
77 external_ids={(ExternalID.MB_ARTIST, "123")},
78 provider_mappings={
79 media_items.ProviderMapping(
80 item_id="1", provider_domain="test", provider_instance="test1"
81 )
82 },
83 )
84 artist_b = media_items.Artist(
85 item_id="1",
86 provider="test2",
87 name="Artist A",
88 external_ids={(ExternalID.MB_ARTIST, "abc")},
89 provider_mappings={
90 media_items.ProviderMapping(
91 item_id="2", provider_domain="test", provider_instance="test2"
92 )
93 },
94 )
95 assert compare.compare_artist(artist_a, artist_b) is False
96
97
98def test_compare_album() -> None:
99 """Test album comparison."""
100 album_a = media_items.Album(
101 item_id="1",
102 provider="test1",
103 name="Album A",
104 provider_mappings={
105 media_items.ProviderMapping(
106 item_id="1", provider_domain="test", provider_instance="test1"
107 )
108 },
109 artists=media_items.UniqueList(
110 [
111 media_items.Artist(
112 item_id="1",
113 provider="test1",
114 name="Artist A",
115 provider_mappings={
116 media_items.ProviderMapping(
117 item_id="1", provider_domain="test", provider_instance="test1"
118 )
119 },
120 )
121 ]
122 ),
123 )
124 album_b = media_items.Album(
125 item_id="1",
126 provider="test2",
127 name="Album A",
128 provider_mappings={
129 media_items.ProviderMapping(
130 item_id="2", provider_domain="test", provider_instance="test2"
131 )
132 },
133 artists=media_items.UniqueList(
134 [
135 media_items.Artist(
136 item_id="1",
137 provider="test1",
138 name="Artist A",
139 provider_mappings={
140 media_items.ProviderMapping(
141 item_id="1", provider_domain="test", provider_instance="test1"
142 )
143 },
144 )
145 ]
146 ),
147 )
148 # test match on name match
149 assert compare.compare_album(album_a, album_b) is True
150 # test match on name mismatch
151 album_b.name = "Album B"
152 assert compare.compare_album(album_a, album_b) is False
153 # test on version mismatch
154 album_b.name = album_a.name
155 album_b.version = "Deluxe"
156 assert compare.compare_album(album_a, album_b) is False
157 album_b.version = "Remix"
158 assert compare.compare_album(album_a, album_b) is False
159 # test on version match
160 album_b.name = album_a.name
161 album_a.version = "Deluxe"
162 album_b.version = "Deluxe Edition"
163 assert compare.compare_album(album_a, album_b) is True
164 # test on exact item_id match
165 album_b.item_id = album_a.item_id
166 album_b.provider = album_a.provider
167 assert compare.compare_album(album_a, album_b) is True
168 # test on external id match
169 album_b.name = "Album B"
170 album_b.item_id = "2"
171 album_b.provider = "test2"
172 album_a.external_ids = {(ExternalID.MB_ALBUM, "123")}
173 album_b.external_ids = album_a.external_ids
174 assert compare.compare_album(album_a, album_b) is True
175 # test on external id mismatch
176 album_b.name = album_a.name
177 album_b.external_ids = {(ExternalID.MB_ALBUM, "1234")}
178 assert compare.compare_album(album_a, album_b) is False
179 album_a.external_ids = set()
180 album_b.external_ids = set()
181 # fail on year mismatch
182 album_b.external_ids = set()
183 album_a.year = 2021
184 album_b.year = 2020
185 assert compare.compare_album(album_a, album_b) is False
186 # pass on year match
187 album_b.year = 2021
188 assert compare.compare_album(album_a, album_b) is True
189 # fail on artist mismatch
190 album_a.artists = media_items.UniqueList(
191 [media_items.ItemMapping(item_id="1", provider="test1", name="Artist A")]
192 )
193 album_b.artists = media_items.UniqueList(
194 [media_items.ItemMapping(item_id="2", provider="test1", name="Artist B")]
195 )
196 assert compare.compare_album(album_a, album_b) is False
197 # pass on partial artist match (if first artist matches)
198 album_a.artists = media_items.UniqueList(
199 [media_items.ItemMapping(item_id="1", provider="test1", name="Artist A")]
200 )
201 album_b.artists = media_items.UniqueList(
202 [
203 media_items.ItemMapping(item_id="1", provider="test1", name="Artist A"),
204 media_items.ItemMapping(item_id="2", provider="test1", name="Artist B"),
205 ]
206 )
207 assert compare.compare_album(album_a, album_b) is True
208 # fail on partial artist match in strict mode
209 album_b.artists = media_items.UniqueList(
210 [
211 media_items.ItemMapping(item_id="2", provider="test1", name="Artist B"),
212 media_items.ItemMapping(item_id="1", provider="test1", name="Artist A"),
213 ]
214 )
215 assert compare.compare_album(album_a, album_b) is False
216 # partial artist match is allowed in non-strict mode
217 assert compare.compare_album(album_a, album_b, False) is True
218
219
220def test_compare_track() -> None: # noqa: PLR0915
221 """Test track comparison."""
222 track_a = media_items.Track(
223 item_id="1",
224 provider="test1",
225 name="Track A",
226 provider_mappings={
227 media_items.ProviderMapping(
228 item_id="1", provider_domain="test", provider_instance="test1"
229 )
230 },
231 artists=media_items.UniqueList(
232 [
233 media_items.Artist(
234 item_id="1",
235 provider="test1",
236 name="Artist A",
237 provider_mappings={
238 media_items.ProviderMapping(
239 item_id="1", provider_domain="test", provider_instance="test1"
240 )
241 },
242 )
243 ]
244 ),
245 )
246 track_b = media_items.Track(
247 item_id="1",
248 provider="test2",
249 name="Track A",
250 provider_mappings={
251 media_items.ProviderMapping(
252 item_id="2", provider_domain="test", provider_instance="test2"
253 )
254 },
255 artists=media_items.UniqueList(
256 [
257 media_items.Artist(
258 item_id="1",
259 provider="test1",
260 name="Artist A",
261 provider_mappings={
262 media_items.ProviderMapping(
263 item_id="1", provider_domain="test", provider_instance="test1"
264 )
265 },
266 )
267 ]
268 ),
269 )
270 # test match on name match
271 assert compare.compare_track(track_a, track_b) is True
272 # test match on name mismatch
273 track_b.name = "Track B"
274 assert compare.compare_track(track_a, track_b) is False
275 # test on version mismatch
276 track_b.name = track_a.name
277 track_b.version = "Deluxe"
278 assert compare.compare_track(track_a, track_b) is False
279 track_b.version = "Remix"
280 assert compare.compare_track(track_a, track_b) is False
281 # test on version mismatch
282 track_b.name = track_a.name
283 track_a.version = ""
284 track_b.version = "Remaster"
285 assert compare.compare_track(track_a, track_b) is False
286 track_b.version = "Remix"
287 assert compare.compare_track(track_a, track_b) is False
288 # test on version match
289 track_b.name = track_a.name
290 track_a.version = "Deluxe"
291 track_b.version = "Deluxe Edition"
292 assert compare.compare_track(track_a, track_b) is True
293 # test on exact item_id match
294 track_b.item_id = track_a.item_id
295 track_b.provider = track_a.provider
296 assert compare.compare_track(track_a, track_b) is True
297 # test on external id match
298 track_b.name = "Track B"
299 track_b.item_id = "2"
300 track_b.provider = "test2"
301 track_a.external_ids = {(ExternalID.MB_RECORDING, "123")}
302 track_b.external_ids = track_a.external_ids
303 assert compare.compare_track(track_a, track_b) is True
304 # test on external id mismatch
305 track_b.name = track_a.name
306 track_b.external_ids = {(ExternalID.MB_RECORDING, "1234")}
307 assert compare.compare_track(track_a, track_b) is False
308 track_a.external_ids = set()
309 track_b.external_ids = set()
310 # fail on artist mismatch
311 track_a.artists = media_items.UniqueList(
312 [media_items.ItemMapping(item_id="1", provider="test1", name="Artist A")]
313 )
314 track_b.artists = media_items.UniqueList(
315 [media_items.ItemMapping(item_id="2", provider="test1", name="Artist B")]
316 )
317 assert compare.compare_track(track_a, track_b) is False
318 # pass on partial artist match (if first artist matches)
319 track_a.artists = media_items.UniqueList(
320 [media_items.ItemMapping(item_id="1", provider="test1", name="Artist A")]
321 )
322 track_b.artists = media_items.UniqueList(
323 [
324 media_items.ItemMapping(item_id="1", provider="test1", name="Artist A"),
325 media_items.ItemMapping(item_id="2", provider="test1", name="Artist B"),
326 ]
327 )
328 assert compare.compare_track(track_a, track_b) is True
329 # fail on partial artist match in strict mode
330 track_b.artists = media_items.UniqueList(
331 [
332 media_items.ItemMapping(item_id="2", provider="test1", name="Artist B"),
333 media_items.ItemMapping(item_id="1", provider="test1", name="Artist A"),
334 ]
335 )
336 assert compare.compare_track(track_a, track_b) is False
337 # partial artist match is allowed in non-strict mode
338 assert compare.compare_track(track_a, track_b, False) is True
339 track_b.artists = track_a.artists
340 # fail on album mismatch
341 track_a.album = media_items.ItemMapping(item_id="1", provider="test1", name="Album A")
342 track_b.album = media_items.ItemMapping(item_id="2", provider="test1", name="Album B")
343 assert compare.compare_track(track_a, track_b) is False
344 # pass on exact album(track) match (regardless duration)
345 track_b.album = track_a.album
346 track_a.disc_number = 1
347 track_a.track_number = 1
348 track_b.disc_number = track_a.disc_number
349 track_b.track_number = track_a.track_number
350 track_a.duration = 300
351 track_b.duration = 310
352 assert compare.compare_track(track_a, track_b) is True
353 # pass on album(track) mismatch
354 track_b.album = track_a.album
355 track_a.disc_number = 1
356 track_a.track_number = 1
357 track_b.disc_number = track_a.disc_number
358 track_b.track_number = 2
359 track_b.duration = track_a.duration
360 assert compare.compare_track(track_a, track_b) is False
361 # test special case - ISRC match but MusicBrainz ID mismatch
362 # this can happen for some classical music albums
363 track_a.external_ids = {
364 (ExternalID.ISRC, "123"),
365 (ExternalID.MB_RECORDING, "abc"),
366 }
367 track_b.external_ids = {
368 (ExternalID.ISRC, "123"),
369 (ExternalID.MB_RECORDING, "abcd"),
370 }
371 assert compare.compare_track(track_a, track_b) is False
372
373
374def test_compare_strings_case_insensitive_fuzzy() -> None:
375 """Test that non-strict fuzzy matching is fully case-insensitive."""
376 # These differ slightly ("Feat." vs "FT.") so create_safe_string won't match,
377 # falling through to SequenceMatcher which must compare both strings lowered.
378 assert compare.compare_strings("Track Feat. John", "TRACK FT. JOHN", strict=False) is True
379