/
/
/
1"""Integration tests for the Bandcamp provider."""
2
3from collections.abc import AsyncGenerator
4from unittest import mock
5
6import pytest
7from music_assistant_models.config_entries import ProviderConfig
8from music_assistant_models.enums import MediaType, StreamType
9
10from music_assistant.mass import MusicAssistant
11from tests.common import wait_for_sync_completion
12
13
14@pytest.fixture
15async def bandcamp_provider(mass: MusicAssistant) -> AsyncGenerator[ProviderConfig, None]:
16 """Configure a Bandcamp test fixture, and add a provider to mass that uses it."""
17 # Mock the BandcampAPIClient to avoid real API calls
18 with mock.patch("music_assistant.providers.bandcamp.BandcampAPIClient") as mock_client_class:
19 mock_client = mock.AsyncMock()
20 mock_client_class.return_value = mock_client
21
22 # Configure mock client for collection access
23 mock_collection = mock.AsyncMock()
24 mock_collection.items = []
25
26 # Mock collection items for library tests
27 mock_item_artist = mock.AsyncMock()
28 mock_item_artist.item_type = "band"
29 mock_item_artist.item_id = 123
30 mock_item_artist.band_name = "Test Artist"
31 mock_item_artist.item_url = "https://test.bandcamp.com"
32
33 mock_item_album = mock.AsyncMock()
34 mock_item_album.item_type = "album"
35 mock_item_album.band_id = 123
36 mock_item_album.item_id = 456
37 mock_item_album.item_title = "Test Album"
38 mock_item_album.item_url = "https://test.bandcamp.com/album/test-album"
39
40 mock_collection.items = [mock_item_artist, mock_item_album]
41 mock_client.get_collection_items.return_value = mock_collection
42
43 # Mock artist and album data
44 mock_artist = mock.AsyncMock()
45 mock_artist.id = 123
46 mock_artist.name = "Test Artist"
47 mock_artist.url = "https://test.bandcamp.com"
48 mock_client.get_artist.return_value = mock_artist
49
50 mock_album = mock.AsyncMock()
51 mock_album.id = 456
52 mock_album.title = "Test Album"
53 mock_album.artist = mock_artist
54 mock_album.url = "https://test.bandcamp.com/album/test-album"
55 mock_album.art_url = "https://f4.bcbits.com/img/a1234567890_16.jpg"
56 mock_album.release_date = 1609459200
57 mock_album.about = "Test album description"
58
59 mock_track = mock.AsyncMock()
60 mock_track.id = 789
61 mock_track.title = "Test Track"
62 mock_track.artist = mock_artist
63 mock_track.url = "https://test.bandcamp.com/track/test-track"
64 mock_track.duration = 300
65 mock_track.streaming_url = {"mp3-320": "https://example.com/track.mp3"}
66 mock_track.track_number = 1
67 mock_track.lyrics = "Test lyrics"
68
69 # Configure the streaming_url to behave like a dictionary
70 mock_track.configure_mock(streaming_url={"mp3-320": "https://example.com/track.mp3"})
71
72 mock_album.tracks = [mock_track]
73 mock_client.get_album.return_value = mock_album
74 mock_client.get_track.return_value = mock_track
75
76 async with wait_for_sync_completion(mass):
77 config = await mass.config.save_provider_config(
78 "bandcamp",
79 {
80 "identity": "mock_identity_token",
81 "search_limit": 10,
82 "top_tracks_limit": 50,
83 },
84 )
85 await mass.music.start_sync()
86
87 yield config
88
89
90@pytest.mark.usefixtures("bandcamp_provider")
91async def test_initial_sync(mass: MusicAssistant) -> None:
92 """Test that initial sync worked."""
93 # Test library access (requires identity token)
94 all_artists = await mass.music.artists.library_items()
95 artists = [artist for artist in all_artists if artist.provider == "bandcamp"]
96
97 assert len(artists) >= 0 # May be empty if no collection items
98
99 all_albums = await mass.music.albums.library_items()
100 albums = [album for album in all_albums if album.provider == "bandcamp"]
101
102 assert len(albums) >= 0 # May be empty if no collection items
103
104
105@pytest.mark.usefixtures("bandcamp_provider")
106async def test_search_functionality(mass: MusicAssistant) -> None:
107 """Test search functionality."""
108 # Mock search results
109 with mock.patch("music_assistant.providers.bandcamp.BandcampAPIClient") as mock_client_class:
110 mock_client = mock.AsyncMock()
111 mock_client_class.return_value = mock_client
112
113 # Mock search results
114 mock_search_result_track = mock.AsyncMock()
115 mock_search_result_track.__class__.__name__ = "SearchResultTrack"
116 mock_search_result_track.artist_id = 123
117 mock_search_result_track.album_id = 456
118 mock_search_result_track.id = 789
119 mock_search_result_track.name = "Search Test Track"
120 mock_search_result_track.artist_name = "Search Test Artist"
121 mock_search_result_track.album_name = "Search Test Album"
122 mock_search_result_track.url = "https://test.bandcamp.com/track/search-test"
123
124 mock_client.search.return_value = [mock_search_result_track]
125
126 # Perform search
127 results = await mass.music.search("test query", [MediaType.TRACK], limit=5)
128
129 # Filter for bandcamp results
130 bandcamp_tracks = [track for track in results.tracks if track.provider == "bandcamp"]
131 assert len(bandcamp_tracks) >= 0 # May be empty if search is mocked differently
132
133
134@pytest.mark.usefixtures("bandcamp_provider")
135async def test_get_artist_details(mass: MusicAssistant) -> None:
136 """Test getting artist details."""
137 # Get the bandcamp provider instance
138 bandcamp_provider = None
139 for provider in mass.music.providers:
140 if provider.domain == "bandcamp":
141 bandcamp_provider = provider
142 break
143
144 assert bandcamp_provider is not None
145
146 # Test artist retrieval
147 artist = await bandcamp_provider.get_artist("123")
148 assert artist is not None
149 assert artist.name == "Test Artist"
150 assert artist.provider == bandcamp_provider.instance_id
151
152
153@pytest.mark.usefixtures("bandcamp_provider")
154async def test_get_album_details(mass: MusicAssistant) -> None:
155 """Test getting album details."""
156 # Get the bandcamp provider instance
157 bandcamp_provider = None
158 for provider in mass.music.providers:
159 if provider.domain == "bandcamp":
160 bandcamp_provider = provider
161 break
162
163 assert bandcamp_provider is not None
164
165 # Test album retrieval
166 album = await bandcamp_provider.get_album("123-456")
167 assert album is not None
168 assert album.name == "Test Album"
169 assert album.provider == bandcamp_provider.instance_id
170
171
172@pytest.mark.usefixtures("bandcamp_provider")
173async def test_get_track_details(mass: MusicAssistant) -> None:
174 """Test getting track details."""
175 # Get the bandcamp provider instance
176 bandcamp_provider = None
177 for provider in mass.music.providers:
178 if provider.domain == "bandcamp":
179 bandcamp_provider = provider
180 break
181
182 assert bandcamp_provider is not None
183
184 # Test track retrieval
185 track = await bandcamp_provider.get_track("123-456-789")
186 assert track is not None
187 assert track.name == "Test Track"
188 assert track.provider == bandcamp_provider.instance_id
189
190
191@pytest.mark.usefixtures("bandcamp_provider")
192async def test_get_album_tracks(mass: MusicAssistant) -> None:
193 """Test getting album tracks."""
194 # Get the bandcamp provider instance
195 bandcamp_provider = None
196 for provider in mass.music.providers:
197 if provider.domain == "bandcamp":
198 bandcamp_provider = provider
199 break
200
201 assert bandcamp_provider is not None
202
203 # Test album tracks retrieval
204 tracks = await bandcamp_provider.get_album_tracks("123-456")
205 assert len(tracks) == 1
206 assert tracks[0].name == "Test Track"
207
208
209@pytest.mark.usefixtures("bandcamp_provider")
210async def test_stream_details(mass: MusicAssistant) -> None:
211 """Test stream details retrieval."""
212 # Get the bandcamp provider instance
213 bandcamp_provider = None
214 for provider in mass.music.providers:
215 if provider.domain == "bandcamp":
216 bandcamp_provider = provider
217 break
218
219 assert bandcamp_provider is not None
220
221 # Test stream details retrieval
222 stream_details = await bandcamp_provider.get_stream_details("123-456-789", MediaType.TRACK)
223 assert stream_details is not None
224 assert stream_details.stream_type == StreamType.HTTP
225 assert stream_details.path == "https://example.com/track.mp3"
226