/
/
/
1"""
2End-to-end test for the radio_playlist provider.
3
4Boots a hermetic MusicAssistant (the always-on `radio_playlist` builtin provider loads automatically,
5plus the deterministic `test` music provider), builds a radio-playlist URI for a seed track, and
6asserts it resolves to a dynamic playlist whose tracks are the seed + similar tracks.
7"""
8
9from __future__ import annotations
10
11from typing import TYPE_CHECKING, cast
12
13import pytest
14from music_assistant_models.media_items import Playlist
15
16from music_assistant.helpers.track_filter import track_filter
17from music_assistant.mass import MusicAssistant
18from music_assistant.providers.radio_playlist import RadioPlaylistProvider, radio_playlist_uri
19
20if TYPE_CHECKING:
21 from music_assistant.models.music_provider import MusicProvider
22
23
24@pytest.mark.asyncio
25async def test_radio_playlist_generates_dynamic_playlist(e2e_mass: MusicAssistant) -> None:
26 """A radio_playlist:// URI resolves to a dynamic playlist of the seed's own + similar tracks."""
27 test_prov = cast("MusicProvider", e2e_mass.get_provider("test"))
28 assert test_prov is not None
29 # the radio_playlist provider is always-on (builtin) and loaded by mass.start()
30 radio_prov = cast("MusicProvider", e2e_mass.get_provider("radio_playlist"))
31 assert radio_prov is not None
32
33 seed = await test_prov.get_track("0_0_0")
34 uri = radio_playlist_uri(seed)
35 assert uri == f"radio_playlist://playlist/{seed.uri}"
36
37 # the URI resolves to a virtual, dynamic playlist
38 playlist = await e2e_mass.music.get_item_by_uri(uri)
39 assert isinstance(playlist, Playlist)
40 assert playlist.is_dynamic
41 assert playlist.name == f"{seed.name} Endless Mix"
42
43 # its tracks are the seed (base) + similar tracks (deterministic via the test provider)
44 tracks = await radio_prov.get_playlist_tracks(playlist.item_id)
45 assert tracks
46 track_ids = {track.item_id for track in tracks}
47 assert seed.item_id in track_ids
48 assert len(track_ids) > 1
49
50
51@pytest.mark.asyncio
52async def test_get_dynamic_tracks_honors_track_filter(e2e_mass: MusicAssistant) -> None:
53 """A published track filter pre-skips excluded tracks during radio generation."""
54 test_prov = cast("MusicProvider", e2e_mass.get_provider("test"))
55 radio_prov = cast("RadioPlaylistProvider", e2e_mass.get_provider("radio_playlist"))
56 seed = await test_prov.get_track("0_0_0")
57
58 # baseline: a dynamic (non-seed) track that generation produces for this seed
59 baseline = await radio_prov.get_dynamic_tracks([seed], include_base_tracks=True)
60 dynamic_ids = [track.item_id for track in baseline if track.item_id != seed.item_id]
61 assert dynamic_ids
62 excluded = dynamic_ids[0]
63
64 # with a filter excluding that track, it must not appear and the filter must be consulted
65 with track_filter(lambda track: track.item_id != excluded) as active:
66 filtered = await radio_prov.get_dynamic_tracks([seed], include_base_tracks=True)
67 assert active.called
68 assert all(track.item_id != excluded for track in filtered)
69