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