Add media player test to Cambridge Audio (#125780)

* Add media player tests to Cambridge Audio

* Add media player tests to Cambridge Audio

* Remove unnecessary test case

* Move state_update call out of mock

* Update tests/components/cambridge_audio/test_media_player.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/126382/head
Noah Husby 2024-09-11 16:06:03 -04:00 committed by GitHub
parent 98728d37a6
commit 610e9239a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 381 additions and 2 deletions

View File

@ -3,13 +3,13 @@
from collections.abc import Generator
from unittest.mock import Mock, patch
from aiostreammagic.models import Info
from aiostreammagic.models import Info, NowPlaying, PlayState, Source, State
import pytest
from homeassistant.components.cambridge_audio.const import DOMAIN
from homeassistant.const import CONF_HOST
from tests.common import MockConfigEntry, load_fixture
from tests.common import MockConfigEntry, load_fixture, load_json_array_fixture
from tests.components.smhi.common import AsyncMock
@ -39,7 +39,20 @@ def mock_stream_magic_client() -> Generator[AsyncMock]:
client = mock_client.return_value
client.host = "192.168.20.218"
client.info = Info.from_json(load_fixture("get_info.json", DOMAIN))
client.sources = [
Source.from_dict(x)
for x in load_json_array_fixture("get_sources.json", DOMAIN)
]
client.state = State.from_json(load_fixture("get_state.json", DOMAIN))
client.play_state = PlayState.from_json(
load_fixture("get_play_state.json", DOMAIN)
)
client.now_playing = NowPlaying.from_json(
load_fixture("get_now_playing.json", DOMAIN)
)
client.is_connected = Mock(return_value=True)
client.position_last_updated = client.play_state.position
client.unregister_state_update_callbacks = AsyncMock(return_value=True)
yield client

View File

@ -0,0 +1,6 @@
"""Constants for Cambridge Audio integration tests."""
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
DEVICE_NAME = "cambridge_audio_cxnv2"
ENTITY_ID = f"{MP_DOMAIN}.{DEVICE_NAME}"

View File

@ -0,0 +1,25 @@
{
"state": "PLAYING",
"source": {
"id": "AIRPLAY",
"name": "AirPlay"
},
"allow_apd": false,
"listening_on": "Listening on Cambridge Audio CXNv2 - AirPlay",
"display": {
"line1": "Holiday",
"line2": "Green Day",
"line3": "Greatest Hits: God's Favorite Band",
"format": "44.1kHz/16bit ALAC",
"mqa": "none",
"playback_source": "iPhone",
"class": "stream.service.airplay",
"art_file": "/tmp/current/AlbumArtFile-811-363",
"art_url": "http://192.168.20.218:80/album-art-2d89?id=1:246",
"progress": {
"position": 216,
"duration": 232
}
},
"controls": ["play_pause", "track_next", "track_previous"]
}

View File

@ -0,0 +1,22 @@
{
"state": "play",
"position": 179,
"presettable": false,
"mode_repeat": "off",
"mode_shuffle": "off",
"metadata": {
"class": "md.track",
"source": "AIRPLAY",
"name": "AirPlay",
"duration": 232,
"album": "Greatest Hits: God's Favorite Band",
"artist": "Green Day",
"title": "Holiday",
"art_url": "http://192.168.20.218:80/album-art-2d89?id=1:246",
"mqa": "none",
"codec": "ALAC",
"lossless": true,
"sample_rate": 44100,
"bit_depth": 16
}
}

View File

@ -0,0 +1,113 @@
[
{
"id": "IR",
"name": "Internet Radio",
"default_name": "Internet Radio",
"class": "stream.radio",
"nameable": false,
"ui_selectable": false,
"description": "Internet Radio",
"description_locale": "Internet Radio",
"preferred_order": 9
},
{
"id": "USB_AUDIO",
"name": "USB Audio",
"default_name": "USB Audio",
"class": "digital.usb",
"nameable": true,
"ui_selectable": true,
"description": "USB Audio",
"description_locale": "USB Audio",
"preferred_order": 1
},
{
"id": "SPDIF_COAX",
"name": "D2",
"default_name": "D2",
"class": "digital.coax",
"nameable": true,
"ui_selectable": false,
"description": "Digital Co-axial",
"description_locale": "Digital Co-axial",
"preferred_order": 3
},
{
"id": "SPDIF_TOSLINK",
"name": "D1",
"default_name": "D1",
"class": "digital.toslink",
"nameable": true,
"ui_selectable": false,
"description": "Digital Optical",
"description_locale": "Digital Optical",
"preferred_order": 2
},
{
"id": "MEDIA_PLAYER",
"name": "Media Library",
"default_name": "Media Library",
"class": "stream.media",
"nameable": false,
"ui_selectable": true,
"description": "Media Player",
"description_locale": "Media Player",
"preferred_order": 10
},
{
"id": "AIRPLAY",
"name": "AirPlay",
"default_name": "AirPlay",
"class": "stream.service.airplay",
"nameable": false,
"ui_selectable": true,
"description": "AirPlay",
"description_locale": "AirPlay",
"preferred_order": 11
},
{
"id": "SPOTIFY",
"name": "Spotify",
"default_name": "Spotify",
"class": "stream.service.spotify",
"nameable": false,
"ui_selectable": true,
"description": "Spotify",
"description_locale": "Spotify",
"preferred_order": 6,
"normalisation": "off"
},
{
"id": "CAST",
"name": "Chromecast built-in",
"default_name": "Chromecast built-in",
"class": "stream.service.cast",
"nameable": false,
"ui_selectable": true,
"description": "Chromecast built-in",
"description_locale": "Chromecast built-in",
"preferred_order": 8
},
{
"id": "ROON",
"name": "Roon Ready",
"default_name": "Roon Ready",
"class": "stream.service.roon",
"nameable": false,
"ui_selectable": false,
"description": "Roon Ready",
"description_locale": "Roon Ready",
"preferred_order": 5
},
{
"id": "TIDAL",
"name": "TIDAL Connect",
"default_name": "TIDAL Connect",
"class": "stream.service.tidal",
"nameable": false,
"ui_selectable": false,
"description": "TIDAL",
"description_locale": "TIDAL",
"preferred_order": 7
}
]

View File

@ -0,0 +1,7 @@
{
"source": "AIRPLAY",
"power": true,
"pre_amp_mode": false,
"pre_amp_state": "disabled_user",
"cbus": "off"
}

View File

@ -0,0 +1,193 @@
"""Tests for the Cambridge Audio integration."""
from unittest.mock import AsyncMock
from aiostreammagic import TransportControl
import pytest
from homeassistant.components.media_player import (
DOMAIN as MP_DOMAIN,
MediaPlayerEntityFeature,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK,
STATE_BUFFERING,
STATE_IDLE,
STATE_OFF,
STATE_PAUSED,
STATE_PLAYING,
STATE_STANDBY,
)
from homeassistant.core import HomeAssistant
from . import setup_integration
from .const import ENTITY_ID
from tests.common import MockConfigEntry
async def mock_state_update(client: AsyncMock) -> None:
"""Trigger a callback in the media player."""
await client.register_state_update_callbacks.call_args[0][0](client)
async def test_entity_supported_features(
hass: HomeAssistant,
mock_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test entity attributes."""
await setup_integration(hass, mock_config_entry)
await mock_state_update(mock_stream_magic_client)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
attrs = state.attributes
# Ensure volume isn't available when pre-amp is disabled
assert not mock_stream_magic_client.state.pre_amp_mode
assert (
MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.VOLUME_MUTE
not in attrs[ATTR_SUPPORTED_FEATURES]
)
# Check for basic media controls
assert {
TransportControl.PLAY_PAUSE,
TransportControl.TRACK_NEXT,
TransportControl.TRACK_PREVIOUS,
}.issubset(mock_stream_magic_client.now_playing.controls)
assert (
MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.PREVIOUS_TRACK
in attrs[ATTR_SUPPORTED_FEATURES]
)
assert (
MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.REPEAT_SET
| MediaPlayerEntityFeature.SEEK
not in attrs[ATTR_SUPPORTED_FEATURES]
)
mock_stream_magic_client.now_playing.controls = [
TransportControl.TOGGLE_REPEAT,
TransportControl.TOGGLE_SHUFFLE,
TransportControl.SEEK,
]
await mock_state_update(mock_stream_magic_client)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
attrs = state.attributes
assert (
MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.REPEAT_SET
| MediaPlayerEntityFeature.SEEK
in attrs[ATTR_SUPPORTED_FEATURES]
)
mock_stream_magic_client.state.pre_amp_mode = True
await mock_state_update(mock_stream_magic_client)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
attrs = state.attributes
assert (
MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.VOLUME_MUTE
in attrs[ATTR_SUPPORTED_FEATURES]
)
@pytest.mark.parametrize(
("power_state", "play_state", "media_player_state"),
[
(True, "NETWORK", STATE_STANDBY),
(False, "NETWORK", STATE_STANDBY),
(False, "play", STATE_OFF),
(True, "play", STATE_PLAYING),
(True, "pause", STATE_PAUSED),
(True, "connecting", STATE_BUFFERING),
(True, "stop", STATE_IDLE),
(True, "ready", STATE_IDLE),
],
)
async def test_entity_state(
hass: HomeAssistant,
mock_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
power_state: bool,
play_state: str,
media_player_state: str,
) -> None:
"""Test media player state."""
await setup_integration(hass, mock_config_entry)
mock_stream_magic_client.state.power = power_state
mock_stream_magic_client.play_state.state = play_state
await mock_state_update(mock_stream_magic_client)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state.state == media_player_state
async def test_media_play_pause_stop(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_stream_magic_client: AsyncMock,
) -> None:
"""Test media next previous track service."""
await setup_integration(hass, mock_config_entry)
data = {ATTR_ENTITY_ID: ENTITY_ID}
# Test for play/pause command when separate play and pause controls are unavailable
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_PAUSE, data, True)
mock_stream_magic_client.play_pause.assert_called_once()
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_PLAY, data, True)
assert mock_stream_magic_client.play_pause.call_count == 2
# Test for separate play and pause controls
mock_stream_magic_client.now_playing.controls = [
TransportControl.PLAY,
TransportControl.PAUSE,
]
await mock_state_update(mock_stream_magic_client)
await hass.async_block_till_done()
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_PAUSE, data, True)
mock_stream_magic_client.pause.assert_called_once()
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_PLAY, data, True)
mock_stream_magic_client.play.assert_called_once()
async def test_media_next_previous_track(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_stream_magic_client: AsyncMock,
) -> None:
"""Test media next previous track service."""
await setup_integration(hass, mock_config_entry)
data = {ATTR_ENTITY_ID: ENTITY_ID}
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data, True)
mock_stream_magic_client.next_track.assert_called_once()
await hass.services.async_call(MP_DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data, True)
mock_stream_magic_client.previous_track.assert_called_once()