/
/
/
1"""
2Tests for how ChromecastPlayer reports volume from a CastStatus.
3
4A combo device (e.g. a TV or amplifier with a Cast endpoint next to its own
5protocol) reports volume 0 through its Cast interface whenever no Cast app is
6running, even though its real volume is set through the other interface. That
70 is not authoritative and must not be reported as a hard mute, so an idle
8Cast player reporting 0 is treated as "unknown" instead. This only applies to
9a combo device's Cast endpoint (PlayerType.PROTOCOL); a standalone Google Cast
10player reporting 0 while idle is a genuine mute.
11"""
12
13from __future__ import annotations
14
15from typing import cast
16from unittest.mock import MagicMock
17
18from music_assistant_models.enums import PlayerType
19from pychromecast import IDLE_APP_ID
20
21from music_assistant.providers.chromecast.constants import MASS_APP_ID
22from music_assistant.providers.chromecast.player import ChromecastPlayer
23
24
25def _handle_cast_status(fake: MagicMock, status: MagicMock) -> None:
26 ChromecastPlayer._handle_cast_status(cast("ChromecastPlayer", fake), status)
27
28
29def _fake_player(app_id: str | None, player_type: PlayerType = PlayerType.PLAYER) -> MagicMock:
30 """
31 Build a MagicMock standing in for an ungrouped ChromecastPlayer.
32
33 :param app_id: Cast application id the (mocked) Chromecast currently reports.
34 :param player_type: Player type to report, defaults to a standalone Cast player.
35 """
36 fake = MagicMock()
37 fake.mass.closing = False
38 fake.cc.app_id = app_id
39 fake.cast_info.is_multichannel_group = False
40 fake.cast_info.is_audio_group = False
41 fake.on_app_status_changed = None
42 fake.type = player_type
43 return fake
44
45
46def _cast_status(*, volume_level: float, volume_muted: bool = False) -> MagicMock:
47 """
48 Build a CastStatus as the receiver reports it.
49
50 :param volume_level: Volume level (0..1) as reported by the Cast interface.
51 :param volume_muted: Mute state as reported by the Cast interface.
52 """
53 status = MagicMock()
54 status.app_id = None
55 status.volume_level = volume_level
56 status.volume_muted = volume_muted
57 return status
58
59
60def test_idle_cast_reporting_zero_volume_is_unknown() -> None:
61 """No Cast app running on a combo device's Cast endpoint, reported 0 is not authoritative."""
62 fake = _fake_player(app_id=None, player_type=PlayerType.PROTOCOL)
63
64 _handle_cast_status(fake, _cast_status(volume_level=0.0))
65
66 assert fake._attr_volume_level is None
67
68
69def test_idle_cast_reporting_nonzero_volume_is_kept() -> None:
70 """No Cast app running but a non-zero reported volume is still real."""
71 fake = _fake_player(app_id=IDLE_APP_ID)
72
73 _handle_cast_status(fake, _cast_status(volume_level=0.35))
74
75 assert fake._attr_volume_level == 35
76
77
78def test_active_cast_reporting_zero_volume_is_kept() -> None:
79 """A Cast app actively rendering and reporting 0 volume means a genuine mute."""
80 fake = _fake_player(app_id=MASS_APP_ID)
81
82 _handle_cast_status(fake, _cast_status(volume_level=0.0))
83
84 assert fake._attr_volume_level == 0
85
86
87def test_idle_standalone_cast_player_reporting_zero_volume_is_kept() -> None:
88 """A standalone Cast player (e.g. a Nest speaker) idle at 0 volume is a genuine mute."""
89 fake = _fake_player(app_id=None, player_type=PlayerType.PLAYER)
90
91 _handle_cast_status(fake, _cast_status(volume_level=0.0))
92
93 assert fake._attr_volume_level == 0
94