/
/
/
1"""
2Tests for the settings a queue starts out with when its player is first registered.
3
4Autoplay and crossfade each follow the global default until a queue pins its own override (via
5the player controls' toggle). A queue restored from a legacy cache (no override key) resets to
6follow the current global default rather than keeping its last wire value - the agreed behavior
7for the global-default rollout.
8"""
9
10from __future__ import annotations
11
12from types import SimpleNamespace
13from typing import Any
14from unittest.mock import AsyncMock, MagicMock
15
16from music_assistant_models.player_queue import PlayerQueue
17
18from music_assistant.controllers.player_queues.controller import PlayerQueuesController
19from music_assistant.controllers.player_queues.state import PlayerQueueData
20
21
22def _controller(
23 cached_state: dict[str, Any] | None = None,
24 *,
25 global_autoplay: bool = True,
26 global_crossfade: bool = False,
27) -> Any:
28 """
29 Build a queue-controller stand-in for ``on_player_register``.
30
31 :param cached_state: A previously persisted queue state to restore, or None for a new queue.
32 :param global_autoplay: The stubbed global autoplay_enabled core config value.
33 :param global_crossfade: The stubbed global crossfade_enabled core config value.
34 """
35 queues = MagicMock()
36 queues._queue_data = {}
37 queues.mass.cache.get = AsyncMock(side_effect=[cached_state, []])
38 defaults = {"autoplay_enabled": global_autoplay, "crossfade_enabled": global_crossfade}
39 queues.mass.config.get_raw_core_config_value = MagicMock(
40 side_effect=lambda _core_module, key, _default: defaults[key]
41 )
42 # bind the real resolver so on_player_register's call to it exercises the actual logic
43 queues._resolve_default_toggles = lambda queue_data: (
44 PlayerQueuesController._resolve_default_toggles(queues, queue_data)
45 )
46 return queues
47
48
49def _player(player_id: str = "p1") -> Any:
50 """Build a player stand-in with the state fields the queue snapshot is built from."""
51 return SimpleNamespace(
52 player_id=player_id, state=SimpleNamespace(name="Player A", available=True)
53 )
54
55
56async def test_new_queue_follows_the_global_defaults() -> None:
57 """A queue created for a newly registered player follows the global autoplay/crossfade defaults."""
58 queues = _controller(global_autoplay=True, global_crossfade=False)
59
60 await PlayerQueuesController.on_player_register(queues, _player())
61
62 queue = queues._queue_data["p1"].queue
63 assert queue.autoplay_enabled is True
64 assert queue.crossfade_enabled is False
65
66
67async def test_new_queue_follows_a_flipped_global_default() -> None:
68 """A new queue reflects whatever the global defaults are currently set to."""
69 queues = _controller(global_autoplay=False, global_crossfade=True)
70
71 await PlayerQueuesController.on_player_register(queues, _player())
72
73 queue = queues._queue_data["p1"].queue
74 assert queue.autoplay_enabled is False
75 assert queue.crossfade_enabled is True
76
77
78async def test_restored_queue_without_override_follows_the_global_default() -> None:
79 """A legacy cache (no pinned override) resets to follow the current global default."""
80 stored = PlayerQueueData(
81 queue=PlayerQueue(
82 queue_id="p1",
83 active=False,
84 display_name="Player A",
85 available=True,
86 autoplay_enabled=False,
87 items=0,
88 )
89 )
90 queues = _controller(stored.to_cache(), global_autoplay=True)
91
92 await PlayerQueuesController.on_player_register(queues, _player())
93
94 # the stale wire value (False) is overwritten by the current global default (True)
95 assert queues._queue_data["p1"].queue.autoplay_enabled is True
96
97
98async def test_restored_queue_with_pinned_override_keeps_it() -> None:
99 """A queue that pinned its own Autoplay setting keeps it regardless of the global default."""
100 stored = PlayerQueueData(
101 queue=PlayerQueue(
102 queue_id="p1",
103 active=False,
104 display_name="Player A",
105 available=True,
106 items=0,
107 ),
108 autoplay_override=False,
109 )
110 queues = _controller(stored.to_cache(), global_autoplay=True)
111
112 await PlayerQueuesController.on_player_register(queues, _player())
113
114 # the pinned override (False) sticks even though the global default is now True
115 assert queues._queue_data["p1"].queue.autoplay_enabled is False
116