/
/
/
1"""
2Tests for the capability flags on the implicit "Music Assistant Queue" source.
3
4Clients drive their transport controls off these flags, so they must follow the queue:
5with nothing queued there is nothing to play, seek or skip through, and a queue that played to
6its end can only be started over.
7"""
8
9from __future__ import annotations
10
11from unittest.mock import MagicMock
12
13import pytest
14from music_assistant_models.player import PlayerSource
15
16from tests.common import MockPlayer, MockProvider
17
18
19@pytest.fixture
20def mock_mass() -> MagicMock:
21 """Create a mock MusicAssistant instance."""
22 mass = MagicMock()
23 mass.config.get_raw_player_config_value = MagicMock(
24 side_effect=lambda _player_id, _key, default=None: default
25 )
26 return mass
27
28
29@pytest.fixture
30def provider(mock_mass: MagicMock) -> MockProvider:
31 """Create a mock provider."""
32 return MockProvider("test_provider", mass=mock_mass)
33
34
35def _queue_source(player: MockPlayer) -> PlayerSource | None:
36 """Return the implicit MA queue source from the player's FINAL source list."""
37 sources = player._Player__final_source_list # type: ignore[attr-defined]
38 return next((x for x in sources if x.id == player.player_id), None)
39
40
41def test_filled_queue_advertises_transport_controls(
42 mock_mass: MagicMock, provider: MockProvider
43) -> None:
44 """A queue holding items can be played, seeked and skipped through."""
45 player = MockPlayer(provider, "player_1", "Player")
46 mock_mass.player_queues.get = MagicMock(return_value=MagicMock(items=5, ended=False))
47
48 source = _queue_source(player)
49
50 assert source is not None
51 assert source.can_play_pause is True
52 assert source.can_seek is True
53 assert source.can_next_previous is True
54
55
56def test_empty_queue_reports_transport_controls_unavailable(
57 mock_mass: MagicMock, provider: MockProvider
58) -> None:
59 """An empty queue has nothing to play, so clients can grey the controls out."""
60 player = MockPlayer(provider, "player_2", "Player")
61 mock_mass.player_queues.get = MagicMock(return_value=MagicMock(items=0, ended=False))
62
63 source = _queue_source(player)
64
65 assert source is not None
66 assert source.can_play_pause is False
67 assert source.can_seek is False
68 assert source.can_next_previous is False
69
70
71def test_finished_queue_only_advertises_play(mock_mass: MagicMock, provider: MockProvider) -> None:
72 """A queue that played to its end can be started over, but has nothing left to seek or skip."""
73 player = MockPlayer(provider, "player_5", "Player")
74 mock_mass.player_queues.get = MagicMock(return_value=MagicMock(items=5, ended=True))
75
76 source = _queue_source(player)
77
78 assert source is not None
79 assert source.can_play_pause is True
80 assert source.can_seek is False
81 assert source.can_next_previous is False
82
83
84def test_missing_queue_reports_transport_controls_unavailable(
85 mock_mass: MagicMock, provider: MockProvider
86) -> None:
87 """A player without a registered queue behaves like an empty one."""
88 player = MockPlayer(provider, "player_3", "Player")
89 mock_mass.player_queues.get = MagicMock(return_value=None)
90
91 source = _queue_source(player)
92
93 assert source is not None
94 assert source.can_play_pause is False
95
96
97def test_provider_supplied_queue_source_is_left_alone(
98 mock_mass: MagicMock, provider: MockProvider
99) -> None:
100 """A provider publishing its own source under the player id keeps its own flags."""
101 player = MockPlayer(provider, "player_4", "Player")
102 player._attr_source_list = [
103 PlayerSource(id="player_4", name="Native", passive=False, can_play_pause=True)
104 ]
105 player._cache.clear()
106 mock_mass.player_queues.get = MagicMock(return_value=MagicMock(items=0, ended=False))
107
108 source = _queue_source(player)
109
110 assert source is not None
111 assert source.name == "Native"
112 assert source.can_play_pause is True
113