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