/
/
/
1"""
2Tests for resuming a queue that holds no items.
3
4An empty queue has nothing to resume and must report that, rather than reaching into the
5playlog for whatever was played most recently elsewhere in the system.
6"""
7
8from __future__ import annotations
9
10from unittest.mock import AsyncMock, MagicMock, Mock
11
12import pytest
13from music_assistant_models.enums import PlaybackState
14from music_assistant_models.errors import QueueEmpty
15from music_assistant_models.player_queue import PlayerQueue
16from music_assistant_models.queue_item import QueueItem
17
18from music_assistant.controllers.player_queues import PlayerQueuesController
19from music_assistant.controllers.player_queues.state import PlayerQueueData
20
21QUEUE_ID = "q1"
22
23
24def _controller(*, with_items: bool = True) -> tuple[PlayerQueuesController, PlayerQueue]:
25 """Build a bare controller owning a single idle queue."""
26 ctrl = PlayerQueuesController.__new__(PlayerQueuesController)
27 queue = PlayerQueue(queue_id=QUEUE_ID, active=True, display_name="Q1", available=True, items=0)
28 items = (
29 [QueueItem(queue_id=QUEUE_ID, queue_item_id="first", name="first", duration=100)]
30 if with_items
31 else []
32 )
33 queue.items = len(items)
34 queue.state = PlaybackState.IDLE
35 queue_data = PlayerQueueData(queue=queue)
36 queue_data.items = items
37 ctrl._queue_data = {QUEUE_ID: queue_data}
38 ctrl.signal_update = Mock() # type: ignore[method-assign]
39 ctrl.on_player_update = Mock() # type: ignore[method-assign]
40 ctrl._check_player_permission = Mock() # type: ignore[method-assign]
41 ctrl.play_index = AsyncMock() # type: ignore[method-assign]
42 ctrl.mass = MagicMock()
43 ctrl.mass.players.get_player = Mock(
44 return_value=MagicMock(state=MagicMock(playback_state=PlaybackState.IDLE))
45 )
46 ctrl.logger = MagicMock()
47 return ctrl, queue
48
49
50async def test_resume_on_empty_queue_raises() -> None:
51 """A queue with no items has nothing to resume and says so."""
52 ctrl, _queue = _controller(with_items=False)
53
54 with pytest.raises(QueueEmpty):
55 await ctrl.resume(QUEUE_ID)
56
57 ctrl.play_index.assert_not_awaited() # type: ignore[attr-defined]
58
59
60async def test_resume_with_items_but_no_position_starts_at_the_first_item() -> None:
61 """A loaded queue that never started plays from its first item."""
62 ctrl, _queue = _controller()
63
64 await ctrl.resume(QUEUE_ID)
65
66 ctrl.play_index.assert_awaited_once() # type: ignore[attr-defined]
67 queue_id, item_id = ctrl.play_index.await_args.args[:2] # type: ignore[attr-defined]
68 assert queue_id == QUEUE_ID
69 assert item_id == "first"
70