/
/
1"""Test Tidal Library Manager."""
2
3from collections.abc import AsyncGenerator
4from typing import Any
5from unittest.mock import Mock, patch
6
7import pytest
8from music_assistant_models.enums import MediaType
9
10from music_assistant.providers.tidal.library import TidalLibraryManager
11
12
13@pytest.fixture
14def library_manager(provider_mock: Mock) -> TidalLibraryManager:
15 """Return a TidalLibraryManager instance."""
16 return TidalLibraryManager(provider_mock)
17
18
19@patch("music_assistant.providers.tidal.library.parse_artist")
20async def test_get_artists(
21 mock_parse_artist: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
22) -> None:
23 """Test get_artists."""
24 provider_mock.api.paginate.return_value = [
25 {"created": "2024-01-01T00:00:00", "item": {"id": 1, "name": "Test Artist"}}
26 ]
27 mock_parse_artist.return_value = Mock(item_id="1")
28
29 artists = [a async for a in library_manager.get_artists()]
30
31 assert len(artists) == 1
32 assert artists[0].item_id == "1"
33 provider_mock.api.paginate.assert_called_with(
34 "users/12345/favorites/artists",
35 )
36 mock_parse_artist.assert_called_once()
37
38
39@patch("music_assistant.providers.tidal.library.parse_album")
40async def test_get_albums(
41 mock_parse_album: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
42) -> None:
43 """Test get_albums."""
44 provider_mock.api.paginate.return_value = [
45 {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Album"}}
46 ]
47 mock_parse_album.return_value = Mock(item_id="1")
48
49 albums = [a async for a in library_manager.get_albums()]
50
51 assert len(albums) == 1
52 assert albums[0].item_id == "1"
53 provider_mock.api.paginate.assert_called_with(
54 "users/12345/favorites/albums",
55 )
56 mock_parse_album.assert_called_once()
57
58
59@patch("music_assistant.providers.tidal.library.parse_track")
60async def test_get_tracks(
61 mock_parse_track: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
62) -> None:
63 """Test get_tracks."""
64 provider_mock.api.paginate.return_value = [
65 {"created": "2024-01-01T00:00:00", "item": {"id": 1, "title": "Test Track"}}
66 ]
67 mock_parse_track.return_value = Mock(item_id="1")
68
69 tracks = [t async for t in library_manager.get_tracks()]
70
71 assert len(tracks) == 1
72 assert tracks[0].item_id == "1"
73 provider_mock.api.paginate.assert_called_with(
74 "users/12345/favorites/tracks",
75 )
76 mock_parse_track.assert_called_once()
77
78
79@patch("music_assistant.providers.tidal.library.parse_playlist")
80async def test_get_playlists(
81 mock_parse_playlist: Mock, library_manager: TidalLibraryManager, provider_mock: Mock
82) -> None:
83 """Test get_playlists."""
84 # Mock mixes response
85 mixes_response = [{"id": "mix_1", "title": "Mix 1"}]
86 # Mock playlists response
87 playlists_response = [
88 {
89 "created": "2024-01-01T00:00:00",
90 "playlist": {"uuid": "pl_1", "title": "Playlist 1"},
91 }
92 ]
93
94 # Configure paginate side effect
95 async def paginate_side_effect(endpoint: str, **_kwargs: Any) -> AsyncGenerator[dict[str, Any]]:
96 if "mixes" in endpoint:
97 for item in mixes_response:
98 yield item
99 else:
100 # The ignore[assignment] is needed because of the different return types
101 for item in playlists_response: # type: ignore[assignment]
102 yield item
103
104 provider_mock.api.paginate.side_effect = paginate_side_effect
105
106 # Setup mock return values
107 mock_parse_playlist.side_effect = [
108 Mock(item_id="mix_1"),
109 Mock(item_id="pl_1"),
110 ]
111
112 playlists = [p async for p in library_manager.get_playlists()]
113
114 assert len(playlists) == 3
115 assert playlists[0].item_id == "mix_1"
116 assert playlists[1].item_id == "pl_1"
117 assert playlists[2].item_id == "favorite_tracks"
118 assert mock_parse_playlist.call_count == 2
119
120
121async def test_add_item_artist(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
122 """Test add_item for artist."""
123 item = Mock(item_id="123", media_type=MediaType.ARTIST)
124 await library_manager.add_item(item)
125
126 provider_mock.api.post.assert_called_with(
127 "users/12345/favorites/artists",
128 data={"artistId": "123"},
129 as_form=True,
130 )
131
132
133async def test_add_item_album(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
134 """Test add_item for album."""
135 item = Mock(item_id="123", media_type=MediaType.ALBUM)
136 await library_manager.add_item(item)
137
138 provider_mock.api.post.assert_called_with(
139 "users/12345/favorites/albums",
140 data={"albumId": "123"},
141 as_form=True,
142 )
143
144
145async def test_add_item_track(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
146 """Test add_item for track."""
147 item = Mock(item_id="123", media_type=MediaType.TRACK)
148 await library_manager.add_item(item)
149
150 provider_mock.api.post.assert_called_with(
151 "users/12345/favorites/tracks",
152 data={"trackId": "123"},
153 as_form=True,
154 )
155
156
157async def test_add_item_playlist(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
158 """Test add_item for playlist."""
159 item = Mock(item_id="123", media_type=MediaType.PLAYLIST)
160 await library_manager.add_item(item)
161
162 provider_mock.api.post.assert_called_with(
163 "users/12345/favorites/playlists",
164 data={"uuids": "123"},
165 as_form=True,
166 )
167
168
169async def test_remove_item_artist(
170 library_manager: TidalLibraryManager, provider_mock: Mock
171) -> None:
172 """Test remove_item for artist."""
173 await library_manager.remove_item("123", MediaType.ARTIST)
174
175 provider_mock.api.delete.assert_called_with("users/12345/favorites/artists/123")
176
177
178async def test_remove_item_album(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
179 """Test remove_item for album."""
180 await library_manager.remove_item("123", MediaType.ALBUM)
181
182 provider_mock.api.delete.assert_called_with("users/12345/favorites/albums/123")
183
184
185async def test_remove_item_track(library_manager: TidalLibraryManager, provider_mock: Mock) -> None:
186 """Test remove_item for track."""
187 await library_manager.remove_item("123", MediaType.TRACK)
188
189 provider_mock.api.delete.assert_called_with("users/12345/favorites/tracks/123")
190
191
192async def test_remove_item_playlist(
193 library_manager: TidalLibraryManager, provider_mock: Mock
194) -> None:
195 """Test remove_item for playlist."""
196 await library_manager.remove_item("123", MediaType.PLAYLIST)
197
198 provider_mock.api.delete.assert_called_with("users/12345/favorites/playlists/123")
199
200
201async def test_get_playlists_includes_favorite_tracks(
202 library_manager: TidalLibraryManager, provider_mock: Mock
203) -> None:
204 """Test that get_playlists yields the favorite tracks playlist as the first item."""
205 provider_mock.api.paginate.return_value = []
206
207 playlists = [p async for p in library_manager.get_playlists()]
208
209 assert len(playlists) >= 1
210 assert playlists[0].item_id == "favorite_tracks"
211 assert playlists[0].name == "Favorite Tracks"
212 assert not playlists[0].is_editable
213