/
/
/
1"""Tests for the dashboard-casting helper in the Chromecast provider."""
2
3from __future__ import annotations
4
5from collections.abc import Callable
6from typing import Any
7from unittest.mock import MagicMock
8
9import pytest
10
11from music_assistant.providers.chromecast.constants import DASHBOARD_NAMESPACE, MASS_APP_ID
12from music_assistant.providers.chromecast.helpers import send_hide_dashboard, send_show_dashboard
13
14
15def test_send_show_dashboard_happy_path() -> None:
16 """Launches the receiver app and sends show_dashboard once the namespace is available."""
17 chromecast = MagicMock()
18 chromecast.name = "Living Room TV"
19 chromecast.socket_client.app_namespaces = {DASHBOARD_NAMESPACE}
20
21 def _launch_app(
22 app_id: str, *, callback_function: Callable[[bool, Any], None], **_kwargs: Any
23 ) -> None:
24 assert app_id == MASS_APP_ID
25 callback_function(True, None)
26
27 chromecast.socket_client.receiver_controller.launch_app.side_effect = _launch_app
28
29 send_show_dashboard(chromecast, "https://mass.example.com?path=%2Fparty")
30
31 chromecast.socket_client.send_app_message.assert_called_once_with(
32 DASHBOARD_NAMESPACE,
33 {"type": "show_dashboard", "url": "https://mass.example.com?path=%2Fparty"},
34 )
35
36
37def test_send_show_dashboard_launch_failure_raises() -> None:
38 """A failed launch callback raises TimeoutError without sending a message."""
39 chromecast = MagicMock()
40 chromecast.name = "Living Room TV"
41
42 def _launch_app(
43 _app_id: str, *, callback_function: Callable[[bool, Any], None], **_kwargs: Any
44 ) -> None:
45 callback_function(False, None)
46
47 chromecast.socket_client.receiver_controller.launch_app.side_effect = _launch_app
48
49 with pytest.raises(TimeoutError):
50 send_show_dashboard(chromecast, "https://mass.example.com?path=%2Fparty")
51
52 chromecast.socket_client.send_app_message.assert_not_called()
53
54
55def test_send_show_dashboard_namespace_never_appears_raises(
56 monkeypatch: pytest.MonkeyPatch,
57) -> None:
58 """Raises TimeoutError if the dashboard namespace never shows up in app_namespaces."""
59 chromecast = MagicMock()
60 chromecast.name = "Living Room TV"
61 chromecast.socket_client.app_namespaces = set()
62
63 def _launch_app(
64 _app_id: str, *, callback_function: Callable[[bool, Any], None], **_kwargs: Any
65 ) -> None:
66 callback_function(True, None)
67
68 chromecast.socket_client.receiver_controller.launch_app.side_effect = _launch_app
69
70 # fake clock that advances on every call, so the poll loop's deadline check
71 # trips deterministically without any real sleeping
72 fake_clock = [0.0]
73
74 def _fake_monotonic() -> float:
75 fake_clock[0] += 1.0
76 return fake_clock[0]
77
78 monkeypatch.setattr(
79 "music_assistant.providers.chromecast.helpers.time.monotonic", _fake_monotonic
80 )
81 monkeypatch.setattr("music_assistant.providers.chromecast.helpers.time.sleep", lambda _s: None)
82
83 with pytest.raises(TimeoutError):
84 send_show_dashboard(chromecast, "https://mass.example.com?path=%2Fparty", timeout=5.0)
85
86 chromecast.socket_client.send_app_message.assert_not_called()
87
88
89def test_send_hide_dashboard_sends_message_when_app_and_namespace_match() -> None:
90 """Sends the hide_dashboard message when our app is running with the dashboard namespace."""
91 chromecast = MagicMock()
92 chromecast.app_id = MASS_APP_ID
93 chromecast.socket_client.app_namespaces = {DASHBOARD_NAMESPACE}
94
95 result = send_hide_dashboard(chromecast)
96
97 assert result is True
98 chromecast.socket_client.send_app_message.assert_called_once_with(
99 DASHBOARD_NAMESPACE, {"type": "hide_dashboard"}
100 )
101 chromecast.socket_client.receiver_controller.launch_app.assert_not_called()
102
103
104def test_send_hide_dashboard_returns_false_when_app_differs() -> None:
105 """Nothing is sent when the receiver is not running our app."""
106 chromecast = MagicMock()
107 chromecast.app_id = "some-other-app"
108 chromecast.socket_client.app_namespaces = {DASHBOARD_NAMESPACE}
109
110 result = send_hide_dashboard(chromecast)
111
112 assert result is False
113 chromecast.socket_client.send_app_message.assert_not_called()
114
115
116def test_send_hide_dashboard_returns_false_when_namespace_missing() -> None:
117 """Nothing is sent when our app is running but the dashboard namespace isn't up yet."""
118 chromecast = MagicMock()
119 chromecast.app_id = MASS_APP_ID
120 chromecast.socket_client.app_namespaces = set()
121
122 result = send_hide_dashboard(chromecast)
123
124 assert result is False
125 chromecast.socket_client.send_app_message.assert_not_called()
126