/
/
/
1"""Tests for MSX Playlist mapping."""
2
3from __future__ import annotations
4
5from unittest.mock import MagicMock, Mock
6
7from music_assistant.providers.msx_bridge.mappers import map_tracks_to_msx_playlist
8from music_assistant.providers.msx_bridge.provider import MSXBridgeProvider
9
10
11def _mock_provider() -> MSXBridgeProvider:
12 """Create a mock provider."""
13 provider = MagicMock()
14 provider.mass.metadata.get_image_url.return_value = "http://image.url"
15 return provider
16
17
18def _make_track(item_id: int, name: str, artist: str, duration: int) -> Mock:
19 """Create a mock Track object."""
20 track = Mock()
21 track.item_id = item_id
22 track.name = name
23 track.artist_str = artist
24 track.uri = f"library://track/{item_id}"
25 track.image = f"img{item_id}"
26 track.duration = duration
27 return track
28
29
30def test_map_tracks_to_msx_playlist_basic() -> None:
31 """Test mapping tracks to MSX content page for playlist playback."""
32 prov = _mock_provider()
33 tracks = [
34 _make_track(1, "Track 1", "Artist 1", 180),
35 _make_track(2, "Track 2", "Artist 2", 200),
36 ]
37
38 content = map_tracks_to_msx_playlist(
39 tracks=tracks,
40 start_index=1,
41 prefix="http://localhost",
42 player_id="msx_1",
43 provider=prov,
44 )
45
46 assert content.type == "list"
47 # Always player:play â rotation puts the desired track at index 0
48 assert content.action == "player:play"
49 assert content.items is not None
50 assert len(content.items) == 2
51
52 # Items rotated by start_index=1: [Track 2, Track 1]
53 item0 = content.items[0]
54 assert item0.title == "Track 2"
55 assert item0.action is not None
56 assert item0.action.startswith("audio:")
57 assert "library%3A%2F%2Ftrack%2F2" in item0.action
58 assert "/msx/audio/msx_1?" in item0.action
59 assert "&from_playlist=1" in item0.action
60 assert item0.player_label == "Track 2"
61 assert item0.duration == 200
62 assert item0.label is not None
63 assert "3:20" in item0.label
64 assert "Artist 2" in item0.label
65
66 item1 = content.items[1]
67 assert item1.title == "Track 1"
68 assert item1.action is not None
69 assert "library%3A%2F%2Ftrack%2F1" in item1.action
70
71
72def test_map_tracks_to_msx_playlist_start_zero() -> None:
73 """Test that start_index=0 uses player:play action."""
74 prov = _mock_provider()
75 tracks = [_make_track(1, "Track 1", "Artist 1", 120)]
76
77 content = map_tracks_to_msx_playlist(
78 tracks=tracks,
79 start_index=0,
80 prefix="http://localhost",
81 player_id="msx_1",
82 provider=prov,
83 )
84
85 assert content.action == "player:play"
86
87
88def test_map_tracks_to_msx_playlist_with_device_param() -> None:
89 """Test that device_param is appended to audio URLs."""
90 prov = _mock_provider()
91 tracks = [_make_track(1, "Track 1", "Artist 1", 120)]
92
93 content = map_tracks_to_msx_playlist(
94 tracks=tracks,
95 start_index=0,
96 prefix="http://localhost",
97 player_id="msx_1",
98 provider=prov,
99 device_param="device_id=my_tv",
100 )
101
102 assert content.items is not None
103 assert content.items[0].action is not None
104 assert "device_id=my_tv" in content.items[0].action
105
106
107def test_map_tracks_to_msx_playlist_empty() -> None:
108 """Test mapping empty track list."""
109 prov = _mock_provider()
110 content = map_tracks_to_msx_playlist(
111 tracks=[],
112 start_index=0,
113 prefix="http://localhost",
114 player_id="msx_1",
115 provider=prov,
116 )
117
118 assert content.items is not None
119 assert len(content.items) == 0
120
121
122def test_map_tracks_to_msx_playlist_serialization() -> None:
123 """Test that content serializes correctly with aliases."""
124 prov = _mock_provider()
125 tracks = [_make_track(1, "Track 1", "Artist 1", 180)]
126
127 content = map_tracks_to_msx_playlist(
128 tracks=tracks,
129 start_index=0,
130 prefix="http://localhost",
131 player_id="msx_1",
132 provider=prov,
133 )
134
135 data = content.model_dump(by_alias=True, exclude_none=True)
136 assert data["type"] == "list"
137 assert "template" in data
138 assert len(data["items"]) == 1
139 assert data["items"][0]["playerLabel"] == "Track 1"
140 assert data["items"][0]["action"].startswith("audio:")
141