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>
This commit is contained in:
parent
98728d37a6
commit
610e9239a4
7 changed files with 381 additions and 2 deletions
|
@ -3,13 +3,13 @@
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from aiostreammagic.models import Info
|
from aiostreammagic.models import Info, NowPlaying, PlayState, Source, State
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.cambridge_audio.const import DOMAIN
|
from homeassistant.components.cambridge_audio.const import DOMAIN
|
||||||
from homeassistant.const import CONF_HOST
|
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
|
from tests.components.smhi.common import AsyncMock
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,20 @@ def mock_stream_magic_client() -> Generator[AsyncMock]:
|
||||||
client = mock_client.return_value
|
client = mock_client.return_value
|
||||||
client.host = "192.168.20.218"
|
client.host = "192.168.20.218"
|
||||||
client.info = Info.from_json(load_fixture("get_info.json", DOMAIN))
|
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.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
|
yield client
|
||||||
|
|
||||||
|
|
6
tests/components/cambridge_audio/const.py
Normal file
6
tests/components/cambridge_audio/const.py
Normal 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}"
|
|
@ -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"]
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
113
tests/components/cambridge_audio/fixtures/get_sources.json
Normal file
113
tests/components/cambridge_audio/fixtures/get_sources.json
Normal 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
|
||||||
|
}
|
||||||
|
]
|
7
tests/components/cambridge_audio/fixtures/get_state.json
Normal file
7
tests/components/cambridge_audio/fixtures/get_state.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"source": "AIRPLAY",
|
||||||
|
"power": true,
|
||||||
|
"pre_amp_mode": false,
|
||||||
|
"pre_amp_state": "disabled_user",
|
||||||
|
"cbus": "off"
|
||||||
|
}
|
193
tests/components/cambridge_audio/test_media_player.py
Normal file
193
tests/components/cambridge_audio/test_media_player.py
Normal 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()
|
Loading…
Add table
Reference in a new issue