/
/
/
1"""
2Tests for the ``ConfigActionResult`` return contract of ``ConfigEntryType.ACTION`` handlers.
3
4A handler returning a result reports the outcome of its one-shot side effect: ``config/*/
5invoke_action`` must hand that result straight to the caller instead of rendering entries,
6and stamp the owning namespace its ``translation_key`` resolves under.
7"""
8
9from __future__ import annotations
10
11from unittest.mock import AsyncMock, MagicMock, patch
12
13from music_assistant_models.config_entries import ConfigActionResult
14
15from music_assistant.constants import CONF_PROTOCOL_KEY_SPLITTER
16
17from .helpers import build_bare_config_controller
18
19
20async def test_provider_action_result_is_returned_with_the_provider_owner() -> None:
21 """A provider action result is passed through, stamped with the provider's namespace."""
22 mock_mass = MagicMock()
23 provider = MagicMock()
24 provider.domain = "some_provider"
25 action_result = ConfigActionResult(translation_key="some_action.result")
26 provider.handle_config_action = AsyncMock(return_value=action_result)
27 mock_mass.get_provider.return_value = provider
28 controller = build_bare_config_controller(mock_mass)
29
30 result = await controller.invoke_provider_config_action("some_instance", "some_action")
31
32 assert result is action_result
33 assert action_result.translation_owner == "provider.some_provider"
34
35
36async def test_provider_action_result_keeps_a_preset_owner() -> None:
37 """A result that already declares its owner keeps it."""
38 mock_mass = MagicMock()
39 provider = MagicMock()
40 provider.domain = "some_provider"
41 action_result = ConfigActionResult(
42 translation_key="some_action.result", translation_owner="common"
43 )
44 provider.handle_config_action = AsyncMock(return_value=action_result)
45 mock_mass.get_provider.return_value = provider
46 controller = build_bare_config_controller(mock_mass)
47
48 result = await controller.invoke_provider_config_action("some_instance", "some_action")
49
50 assert result is action_result
51 assert action_result.translation_owner == "common"
52
53
54async def test_core_action_result_is_returned_with_the_core_owner() -> None:
55 """A core-module action result is passed through, stamped with the module's namespace."""
56 mock_mass = MagicMock()
57 module = MagicMock()
58 action_result = ConfigActionResult(translation_key="some_action.result")
59 module.handle_config_action = AsyncMock(return_value=action_result)
60 mock_mass.some_module = module
61 controller = build_bare_config_controller(mock_mass)
62
63 result = await controller.invoke_core_config_action("some_module", "some_action")
64
65 assert result is action_result
66 assert action_result.translation_owner == "core.some_module"
67
68
69async def test_core_action_result_keeps_a_preset_owner() -> None:
70 """A core-module result that already declares its owner keeps it."""
71 mock_mass = MagicMock()
72 module = MagicMock()
73 action_result = ConfigActionResult(
74 translation_key="some_action.result", translation_owner="common"
75 )
76 module.handle_config_action = AsyncMock(return_value=action_result)
77 mock_mass.some_module = module
78 controller = build_bare_config_controller(mock_mass)
79
80 result = await controller.invoke_core_config_action("some_module", "some_action")
81
82 assert result is action_result
83 assert action_result.translation_owner == "common"
84
85
86async def test_player_action_result_is_returned_without_rerender() -> None:
87 """A player action result is passed through and skips the full-entries re-render."""
88 mock_mass = MagicMock()
89 player = MagicMock()
90 player.translation_owner = "provider.some_player_provider"
91 action_result = ConfigActionResult(translation_key="some_action.result")
92 player.handle_config_action = AsyncMock(return_value=action_result)
93 mock_mass.players.get_player.return_value = player
94 controller = build_bare_config_controller(mock_mass)
95
96 with patch.object(controller, "get_player_config_entries", AsyncMock()) as mock_rerender:
97 result = await controller.invoke_player_config_action("player_1", "some_action")
98
99 assert result is action_result
100 assert action_result.translation_owner == "provider.some_player_provider"
101 mock_rerender.assert_not_awaited()
102
103
104async def test_player_action_result_keeps_a_preset_owner() -> None:
105 """A player result that already declares its owner keeps it."""
106 mock_mass = MagicMock()
107 player = MagicMock()
108 player.translation_owner = "provider.some_player_provider"
109 action_result = ConfigActionResult(
110 translation_key="some_action.result", translation_owner="common"
111 )
112 player.handle_config_action = AsyncMock(return_value=action_result)
113 mock_mass.players.get_player.return_value = player
114 controller = build_bare_config_controller(mock_mass)
115
116 with patch.object(controller, "get_player_config_entries", AsyncMock()):
117 await controller.invoke_player_config_action("player_1", "some_action")
118
119 assert action_result.translation_owner == "common"
120
121
122async def test_protocol_action_result_is_returned_without_rerendering_the_parent() -> None:
123 """A protocol-routed result is passed through, owned by the protocol player's provider."""
124 mock_mass = MagicMock()
125 parent = MagicMock()
126 parent.translation_owner = "provider.host_provider"
127 parent.handle_config_action = AsyncMock()
128 protocol_player = MagicMock()
129 protocol_player.translation_owner = "provider.protocol_provider"
130 action_result = ConfigActionResult(translation_key="some_action.result")
131 protocol_player.handle_config_action = AsyncMock(return_value=action_result)
132 mock_mass.players.get_player.side_effect = lambda player_id, *_args: {
133 "player_1": parent,
134 "protocol_1": protocol_player,
135 }[player_id]
136 controller = build_bare_config_controller(mock_mass)
137 action = f"protocol_1{CONF_PROTOCOL_KEY_SPLITTER}some_action"
138
139 with patch.object(controller, "get_player_config_entries", AsyncMock()) as mock_rerender:
140 result = await controller.invoke_player_config_action("player_1", action)
141
142 assert result is action_result
143 assert action_result.translation_owner == "provider.protocol_provider"
144 protocol_player.handle_config_action.assert_awaited_once_with("some_action")
145 parent.handle_config_action.assert_not_awaited()
146 mock_rerender.assert_not_awaited()
147