/
/
/
1"""Additional tests for Tidal Media Manager - Mix operations and similar tracks."""
2
3from unittest.mock import AsyncMock, Mock, patch
4
5import pytest
6from music_assistant_models.enums import MediaType
7from music_assistant_models.errors import MediaNotFoundError
8from music_assistant_models.media_items import ItemMapping
9
10from music_assistant.providers.tidal.media import TidalMediaManager
11
12
13@pytest.fixture
14def provider_mock() -> Mock:
15 """Return a mock provider."""
16 provider = Mock()
17 provider.domain = "tidal"
18 provider.instance_id = "tidal_instance"
19 provider.auth.user_id = "12345"
20 provider.auth.country_code = "US"
21 provider.api = AsyncMock()
22 provider.api.get.return_value = {}
23 provider.logger = Mock()
24
25 def get_item_mapping(media_type: MediaType, key: str, name: str) -> ItemMapping:
26 return ItemMapping(
27 media_type=media_type,
28 item_id=key,
29 provider=provider.instance_id,
30 name=name,
31 )
32
33 provider.get_item_mapping.side_effect = get_item_mapping
34
35 return provider
36
37
38@patch("music_assistant.providers.tidal.media.parse_playlist")
39async def test_get_playlist_mix(
40 mock_parse_playlist: Mock, media_manager: TidalMediaManager, provider_mock: Mock
41) -> None:
42 """Test get_playlist with mix ID."""
43 provider_mock.api.get.return_value = {
44 "title": "My Mix",
45 "rows": [
46 {"modules": [{"mix": {"images": {"MEDIUM": {"url": "http://example.com/mix.jpg"}}}}]},
47 ],
48 "lastUpdated": "2023-01-01",
49 }
50 mock_parse_playlist.return_value = Mock(item_id="mix_123")
51
52 playlist = await media_manager.get_playlist("mix_123")
53
54 assert playlist.item_id == "mix_123"
55 provider_mock.api.get.assert_called_with(
56 "pages/mix",
57 params={"mixId": "123", "deviceType": "BROWSER"},
58 )
59 mock_parse_playlist.assert_called_once()
60 # Verify is_mix=True was passed
61 assert mock_parse_playlist.call_args[1]["is_mix"] is True
62
63
64@patch("music_assistant.providers.tidal.media.parse_playlist")
65async def test_get_playlist_fallback_to_mix(
66 mock_parse_playlist: Mock, media_manager: TidalMediaManager, provider_mock: Mock
67) -> None:
68 """Test get_playlist falls back to mix lookup on MediaNotFoundError."""
69 # First call raises error, second succeeds
70 provider_mock.api.get.side_effect = [
71 MediaNotFoundError("Playlist not found"),
72 {
73 "title": "My Mix",
74 "rows": [{"modules": [{"mix": {"images": {}}}]}],
75 },
76 ]
77 mock_parse_playlist.return_value = Mock(item_id="123")
78
79 playlist = await media_manager.get_playlist("123")
80
81 assert playlist.item_id == "123"
82 assert provider_mock.api.get.call_count == 2
83 # First call as playlist
84 provider_mock.api.get.assert_any_call("playlists/123")
85 # Second call as mix
86 provider_mock.api.get.assert_any_call(
87 "pages/mix",
88 params={"mixId": "123", "deviceType": "BROWSER"},
89 )
90
91
92@patch("music_assistant.providers.tidal.media.parse_track")
93async def test_get_similar_tracks(
94 mock_parse_track: Mock, media_manager: TidalMediaManager, provider_mock: Mock
95) -> None:
96 """Test get_similar_tracks."""
97 provider_mock.api.get.return_value = {"items": [{"id": 1}, {"id": 2}, {"id": 3}]}
98 mock_parse_track.return_value = Mock(item_id="1")
99
100 tracks = await media_manager.get_similar_tracks("123", limit=25)
101
102 assert len(tracks) == 3
103 provider_mock.api.get.assert_called_with(
104 "tracks/123/radio",
105 params={"limit": 25},
106 )
107
108
109@patch("music_assistant.providers.tidal.media.parse_track")
110async def test_get_playlist_tracks_mix(
111 mock_parse_track: Mock, media_manager: TidalMediaManager, provider_mock: Mock
112) -> None:
113 """Test get_playlist_tracks with mix ID."""
114 provider_mock.api.get.return_value = {
115 "rows": [
116 {}, # First row is mix info
117 { # Second row has tracks
118 "modules": [{"pagedList": {"items": [{"id": 1}, {"id": 2}]}}]
119 },
120 ]
121 }
122
123 # Mock track with position attribute
124 def create_track(item_id: int, position: int) -> Mock:
125 track = Mock(item_id=str(item_id))
126 track.position = position
127 return track
128
129 mock_parse_track.side_effect = [
130 create_track(1, 1),
131 create_track(2, 2),
132 ]
133
134 tracks = await media_manager.get_playlist_tracks("mix_123")
135
136 assert len(tracks) == 2
137 assert tracks[0].position == 1
138 assert tracks[1].position == 2
139 provider_mock.api.get.assert_called_with(
140 "pages/mix",
141 params={"mixId": "123", "deviceType": "BROWSER"},
142 )
143
144
145async def test_get_mix_details_no_rows(
146 media_manager: TidalMediaManager, provider_mock: Mock
147) -> None:
148 """Test _get_mix_details raises error when no rows."""
149 provider_mock.api.get.return_value = {"rows": []}
150
151 with pytest.raises(MediaNotFoundError, match="Mix 123 has no tracks"):
152 await media_manager.get_playlist_tracks("mix_123")
153
154
155async def test_search_empty_results(media_manager: TidalMediaManager, provider_mock: Mock) -> None:
156 """Test search with empty results."""
157 provider_mock.api.get.return_value = {}
158
159 results = await media_manager.search("query", [MediaType.ARTIST])
160
161 assert len(results.artists) == 0
162 assert len(results.albums) == 0
163 assert len(results.tracks) == 0
164 assert len(results.playlists) == 0
165