music-assistant-server

5.5 KBPY
test_media_extended.py
5.5 KB171 lines • python
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_data.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@pytest.fixture
39def media_manager(provider_mock: Mock) -> TidalMediaManager:
40    """Return a TidalMediaManager instance."""
41    return TidalMediaManager(provider_mock)
42
43
44@patch("music_assistant.providers.tidal.media.parse_playlist")
45async def test_get_playlist_mix(
46    mock_parse_playlist: Mock, media_manager: TidalMediaManager, provider_mock: Mock
47) -> None:
48    """Test get_playlist with mix ID."""
49    provider_mock.api.get_data.return_value = {
50        "title": "My Mix",
51        "rows": [
52            {"modules": [{"mix": {"images": {"MEDIUM": {"url": "http://example.com/mix.jpg"}}}}]},
53        ],
54        "lastUpdated": "2023-01-01",
55    }
56    mock_parse_playlist.return_value = Mock(item_id="mix_123")
57
58    playlist = await media_manager.get_playlist("mix_123")
59
60    assert playlist.item_id == "mix_123"
61    provider_mock.api.get_data.assert_called_with(
62        "pages/mix",
63        params={"mixId": "123", "deviceType": "BROWSER"},
64    )
65    mock_parse_playlist.assert_called_once()
66    # Verify is_mix=True was passed
67    assert mock_parse_playlist.call_args[1]["is_mix"] is True
68
69
70@patch("music_assistant.providers.tidal.media.parse_playlist")
71async def test_get_playlist_fallback_to_mix(
72    mock_parse_playlist: Mock, media_manager: TidalMediaManager, provider_mock: Mock
73) -> None:
74    """Test get_playlist falls back to mix lookup on MediaNotFoundError."""
75    # First call raises error, second succeeds
76    provider_mock.api.get_data.side_effect = [
77        MediaNotFoundError("Playlist not found"),
78        {
79            "title": "My Mix",
80            "rows": [{"modules": [{"mix": {"images": {}}}]}],
81        },
82    ]
83    mock_parse_playlist.return_value = Mock(item_id="123")
84
85    playlist = await media_manager.get_playlist("123")
86
87    assert playlist.item_id == "123"
88    assert provider_mock.api.get_data.call_count == 2
89    # First call as playlist
90    provider_mock.api.get_data.assert_any_call("playlists/123")
91    # Second call as mix
92    provider_mock.api.get_data.assert_any_call(
93        "pages/mix",
94        params={"mixId": "123", "deviceType": "BROWSER"},
95    )
96
97
98@patch("music_assistant.providers.tidal.media.parse_track")
99async def test_get_similar_tracks(
100    mock_parse_track: Mock, media_manager: TidalMediaManager, provider_mock: Mock
101) -> None:
102    """Test get_similar_tracks."""
103    provider_mock.api.get_data.return_value = {"items": [{"id": 1}, {"id": 2}, {"id": 3}]}
104    mock_parse_track.return_value = Mock(item_id="1")
105
106    tracks = await media_manager.get_similar_tracks("123", limit=25)
107
108    assert len(tracks) == 3
109    provider_mock.api.get_data.assert_called_with(
110        "tracks/123/radio",
111        params={"limit": 25},
112    )
113
114
115@patch("music_assistant.providers.tidal.media.parse_track")
116async def test_get_playlist_tracks_mix(
117    mock_parse_track: Mock, media_manager: TidalMediaManager, provider_mock: Mock
118) -> None:
119    """Test get_playlist_tracks with mix ID."""
120    provider_mock.api.get_data.return_value = {
121        "rows": [
122            {},  # First row is mix info
123            {  # Second row has tracks
124                "modules": [{"pagedList": {"items": [{"id": 1}, {"id": 2}]}}]
125            },
126        ]
127    }
128
129    # Mock track with position attribute
130    def create_track(item_id: int, position: int) -> Mock:
131        track = Mock(item_id=str(item_id))
132        track.position = position
133        return track
134
135    mock_parse_track.side_effect = [
136        create_track(1, 1),
137        create_track(2, 2),
138    ]
139
140    tracks = await media_manager.get_playlist_tracks("mix_123")
141
142    assert len(tracks) == 2
143    assert tracks[0].position == 1
144    assert tracks[1].position == 2
145    provider_mock.api.get_data.assert_called_with(
146        "pages/mix",
147        params={"mixId": "123", "deviceType": "BROWSER"},
148    )
149
150
151async def test_get_mix_details_no_rows(
152    media_manager: TidalMediaManager, provider_mock: Mock
153) -> None:
154    """Test _get_mix_details raises error when no rows."""
155    provider_mock.api.get_data.return_value = {"rows": []}
156
157    with pytest.raises(MediaNotFoundError, match="Mix 123 has no tracks"):
158        await media_manager.get_playlist_tracks("mix_123")
159
160
161async def test_search_empty_results(media_manager: TidalMediaManager, provider_mock: Mock) -> None:
162    """Test search with empty results."""
163    provider_mock.api.get_data.return_value = {}
164
165    results = await media_manager.search("query", [MediaType.ARTIST])
166
167    assert len(results.artists) == 0
168    assert len(results.albums) == 0
169    assert len(results.tracks) == 0
170    assert len(results.playlists) == 0
171