/
/
/
1"""Tests for locating the selected item in a windowed Plex play queue."""
2
3from __future__ import annotations
4
5from types import SimpleNamespace
6from unittest.mock import Mock
7
8from music_assistant.providers.plex_connect.queue_commands import QueueCommandsMixin
9
10
11class _QueueHandler(QueueCommandsMixin):
12 """QueueCommandsMixin with the host-class attributes mocked."""
13
14 def __init__(self) -> None:
15 self.provider = Mock()
16 self._ma_player_id = "player1"
17 self._updating_from_plex = False
18 self.play_queue_id = None
19 self.play_queue_item_ids: dict[int, int] = {}
20
21
22def _make_playqueue(
23 first_track: int,
24 last_track: int,
25 selected_track: int | None,
26 selected_offset: int | None,
27) -> SimpleNamespace:
28 """
29 Build a fake windowed PlayQueue.
30
31 Track number N gets playQueueItemID 1000+N; the items list covers
32 first_track..last_track while selected_offset is the absolute (full-queue)
33 offset as reported by the Plex server.
34 """
35 items = [SimpleNamespace(playQueueItemID=1000 + n) for n in range(first_track, last_track + 1)]
36 return SimpleNamespace(
37 items=items,
38 playQueueSelectedItemID=1000 + selected_track if selected_track is not None else None,
39 playQueueSelectedItemOffset=selected_offset,
40 )
41
42
43def test_selected_index_windowed_queue() -> None:
44 """The absolute offset must not be used as index into a mid-queue window."""
45 handler = _QueueHandler()
46 # Track 94 selected in a 200-track album; server window starts at track 44.
47 # Absolute offset 93 would land on track 137 (the reported 2x-51 bug).
48 playqueue = _make_playqueue(44, 200, selected_track=94, selected_offset=93)
49 index = handler._selected_item_index(playqueue)
50 assert playqueue.items[index].playQueueItemID == 1000 + 94
51
52
53def test_selected_index_full_queue() -> None:
54 """When the window covers the full queue, ID match equals the offset."""
55 handler = _QueueHandler()
56 playqueue = _make_playqueue(1, 100, selected_track=52, selected_offset=51)
57 assert handler._selected_item_index(playqueue) == 51
58
59
60def test_selected_index_falls_back_to_offset() -> None:
61 """Without a selected item ID (track radio), fall back to the raw offset."""
62 handler = _QueueHandler()
63 playqueue = _make_playqueue(1, 10, selected_track=None, selected_offset=3)
64 assert handler._selected_item_index(playqueue) == 3
65
66
67def test_selected_index_out_of_range_offset() -> None:
68 """An offset beyond the fetched items must not raise; default to first item."""
69 handler = _QueueHandler()
70 playqueue = _make_playqueue(1, 10, selected_track=None, selected_offset=42)
71 assert handler._selected_item_index(playqueue) == 0
72
73
74def test_selected_index_negative_offset() -> None:
75 """A negative offset must not index from the end of the list; default to first item."""
76 handler = _QueueHandler()
77 playqueue = _make_playqueue(1, 10, selected_track=None, selected_offset=-1)
78 assert handler._selected_item_index(playqueue) == 0
79
80
81def test_selected_index_none_offset() -> None:
82 """A missing offset (None) defaults to the first item."""
83 handler = _QueueHandler()
84 playqueue = _make_playqueue(1, 10, selected_track=None, selected_offset=None)
85 assert handler._selected_item_index(playqueue) == 0
86