/
/
/
1"""
2Tests for ChromecastPlayer dashboard keepalive media-status handling.
3
4The cast receiver app plays a keepalive media item (currently a paused
5``keepalive.png``, soon a looping ``dashboard-keepalive.mp4``) while showing a
6dashboard. That media is an implementation detail of the receiver and must
7never leak into MA player state - otherwise a Nest Hub showing a dashboard
8appears in MA as "playing dashboard-keepalive.mp4".
9"""
10
11from __future__ import annotations
12
13from typing import Any, cast
14from unittest.mock import MagicMock
15
16from music_assistant_models.enums import PlaybackState
17from music_assistant_models.player import PlayerMedia
18
19from music_assistant.providers.chromecast.player import ChromecastPlayer
20
21
22def _fake_player() -> Any:
23 """Build a Cast player with no active cast group, on mocked collaborators."""
24 # __init__ is skipped: it needs a provider, cast info and a live Chromecast connection.
25 # Typed as Any because the collaborators below are read back as the mocks they are.
26 fake = cast("Any", ChromecastPlayer.__new__(ChromecastPlayer))
27 fake.logger = MagicMock()
28 fake.update_state = MagicMock()
29 fake.active_cast_group = None
30 # display_name is a cached property, normally built from the config in __init__
31 fake._cache = {"display_name": "Test Cast"}
32 # state left over from what played before the dashboard took over, so the assertions
33 # prove the handler clears it rather than matching an untouched default
34 fake._attr_playback_state = PlaybackState.PLAYING
35 fake._attr_current_media = PlayerMedia(uri="http://mass/previous.flac")
36 fake._attr_active_source = "previous_source"
37 fake._attr_elapsed_time = 123.0
38 return fake
39
40
41def _media_status(content_id: str, *, player_is_playing: bool, player_is_paused: bool) -> MagicMock:
42 status = MagicMock()
43 status.content_id = content_id
44 status.player_is_playing = player_is_playing
45 status.player_is_paused = player_is_paused
46 return status
47
48
49def test_dashboard_keepalive_video_treated_as_idle() -> None:
50 """A playing dashboard-keepalive.mp4 status must not surface as MA playback."""
51 fake = _fake_player()
52 status = _media_status(
53 "https://cast.music-assistant.io/dashboard-keepalive.mp4",
54 player_is_playing=True,
55 player_is_paused=False,
56 )
57
58 fake._handle_media_status(status)
59
60 assert fake._attr_playback_state == PlaybackState.IDLE
61 assert fake._attr_current_media is None
62 assert fake._attr_active_source is None
63 assert fake._attr_elapsed_time == 0
64 assert isinstance(fake._attr_elapsed_time_last_updated, float)
65 fake.update_state.assert_called_once()
66
67
68def test_legacy_keepalive_image_treated_as_idle() -> None:
69 """The legacy paused keepalive.png status must also not surface as MA playback."""
70 fake = _fake_player()
71 status = _media_status(
72 "https://cast.music-assistant.io/keepalive.png",
73 player_is_playing=False,
74 player_is_paused=True,
75 )
76
77 fake._handle_media_status(status)
78
79 assert fake._attr_playback_state == PlaybackState.IDLE
80 assert fake._attr_current_media is None
81 assert fake._attr_active_source is None
82 assert fake._attr_elapsed_time == 0
83 assert isinstance(fake._attr_elapsed_time_last_updated, float)
84 fake.update_state.assert_called_once()
85