/
/
/
1"""
2Tests that a Plex-authored queue order survives the load into Music Assistant.
3
4Plex owns the order of a play queue - a shuffled one arrives already shuffled - and the
5index-to-playQueueItemID map is built against that order, so the Music Assistant queue's own
6shuffle must never reorder the tracks on their way in. Plex's shuffle flag is mirrored onto the
7queue separately, once the items have landed.
8"""
9
10from __future__ import annotations
11
12from types import SimpleNamespace
13from typing import Any
14from unittest.mock import AsyncMock, Mock
15
16from music_assistant_models.enums import PlaybackState, QueueOption
17
18from music_assistant.providers.plex_connect.queue_commands import QueueCommandsMixin
19from music_assistant.providers.plex_connect.queue_sync import QueueSyncMixin
20
21
22def _fake_track(track_key: str) -> SimpleNamespace:
23 """Return a stand-in for the MA track a Plex item resolves to."""
24 return SimpleNamespace(key=track_key, name=track_key)
25
26
27class _QueueHandler(QueueSyncMixin, QueueCommandsMixin):
28 """The queue mixins with the host-class attributes mocked."""
29
30 def __init__(self) -> None:
31 self.play_media = AsyncMock()
32 provider = Mock()
33 provider.get_track = AsyncMock(side_effect=_fake_track)
34 provider.mass.player_queues.play_media = self.play_media
35 self.queue = SimpleNamespace(
36 state=PlaybackState.PLAYING, current_index=0, index_in_buffer=0
37 )
38 provider.mass.player_queues.get = Mock(return_value=self.queue)
39 self.provider = provider
40 self._ma_player_id = "player1"
41 self._updating_from_plex = False
42 self.play_queue_id = "1093"
43 self.play_queue_item_ids: dict[int, int] = {}
44
45
46def _make_playqueue(count: int) -> Any:
47 """Build a fake Plex PlayQueue of ``count`` items in the order Plex wants them played."""
48 items = [
49 SimpleNamespace(key=f"/library/metadata/{n}", playQueueItemID=1000 + n)
50 for n in range(count)
51 ]
52 return SimpleNamespace(items=items, playQueueSelectedItemID=1000, playQueueSelectedItemOffset=0)
53
54
55async def test_replacing_the_queue_refuses_to_let_shuffle_reorder_plex() -> None:
56 """
57 The whole-queue load asks for an unshuffled queue, whatever the queue's own toggle says.
58
59 The tracks arrive in the order Plex wants them played, and play_queue_item_ids is keyed on
60 that order - a shuffle applied on the way in would start the wrong track and make every
61 position MA reports back to Plexamp wrong.
62 """
63 handler = _QueueHandler()
64
65 await handler._replace_entire_queue("player1", _make_playqueue(5))
66
67 call = handler.play_media.await_args
68 assert call is not None
69 assert call.kwargs["option"] == QueueOption.REPLACE
70 assert call.kwargs["shuffle"] is False
71 # and the tracks themselves go over in Plex's order
72 assert [track.key for track in call.kwargs["media"]] == [
73 f"/library/metadata/{n}" for n in range(5)
74 ]
75 assert handler.play_queue_item_ids == {n: 1000 + n for n in range(5)}
76