mirror of https://github.com/home-assistant/core
376 lines
11 KiB
Python
376 lines
11 KiB
Python
"""Tests for the Bluesound Media Player platform."""
|
|
|
|
import dataclasses
|
|
from unittest.mock import call
|
|
|
|
from pyblu import PairedPlayer
|
|
from pyblu.errors import PlayerUnreachableError
|
|
import pytest
|
|
from syrupy.assertion import SnapshotAssertion
|
|
from syrupy.filters import props
|
|
|
|
from homeassistant.components.bluesound import DOMAIN as BLUESOUND_DOMAIN
|
|
from homeassistant.components.bluesound.const import ATTR_MASTER
|
|
from homeassistant.components.bluesound.services import (
|
|
SERVICE_CLEAR_TIMER,
|
|
SERVICE_JOIN,
|
|
SERVICE_SET_TIMER,
|
|
)
|
|
from homeassistant.components.media_player import (
|
|
ATTR_MEDIA_VOLUME_LEVEL,
|
|
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_MEDIA_NEXT_TRACK,
|
|
SERVICE_MEDIA_PAUSE,
|
|
SERVICE_MEDIA_PLAY,
|
|
SERVICE_MEDIA_PREVIOUS_TRACK,
|
|
SERVICE_VOLUME_DOWN,
|
|
SERVICE_VOLUME_MUTE,
|
|
SERVICE_VOLUME_SET,
|
|
SERVICE_VOLUME_UP,
|
|
MediaPlayerState,
|
|
)
|
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
|
|
from .conftest import PlayerMocks
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("service", "method"),
|
|
[
|
|
(SERVICE_MEDIA_PAUSE, "pause"),
|
|
(SERVICE_MEDIA_PLAY, "play"),
|
|
(SERVICE_MEDIA_NEXT_TRACK, "skip"),
|
|
(SERVICE_MEDIA_PREVIOUS_TRACK, "back"),
|
|
],
|
|
)
|
|
async def test_simple_actions(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
service: str,
|
|
method: str,
|
|
) -> None:
|
|
"""Test the media player simple actions."""
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
service,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
getattr(player_mocks.player_data.player, method).assert_called_once_with()
|
|
|
|
|
|
async def test_volume_set(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the media player volume set."""
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_VOLUME_SET,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111", ATTR_MEDIA_VOLUME_LEVEL: 0.5},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.volume.assert_called_once_with(level=50)
|
|
|
|
|
|
async def test_volume_mute(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the media player volume mute."""
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_VOLUME_MUTE,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111", "is_volume_muted": True},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.volume.assert_called_once_with(mute=True)
|
|
|
|
|
|
async def test_volume_up(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the media player volume up."""
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_VOLUME_UP,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.volume.assert_called_once_with(level=11)
|
|
|
|
|
|
async def test_volume_down(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the media player volume down."""
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_VOLUME_DOWN,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.volume.assert_called_once_with(level=9)
|
|
|
|
|
|
async def test_attributes_set(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
snapshot: SnapshotAssertion,
|
|
) -> None:
|
|
"""Test the media player attributes set."""
|
|
state = hass.states.get("media_player.player_name1111")
|
|
assert state == snapshot(exclude=props("media_position_updated_at"))
|
|
|
|
|
|
async def test_stop_maps_to_idle(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the media player stop maps to idle."""
|
|
player_mocks.player_data.status_long_polling_mock.set(
|
|
dataclasses.replace(
|
|
player_mocks.player_data.status_long_polling_mock.get(), state="stop"
|
|
)
|
|
)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
assert (
|
|
hass.states.get("media_player.player_name1111").state == MediaPlayerState.IDLE
|
|
)
|
|
|
|
|
|
async def test_status_updated(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the media player status updated."""
|
|
pre_state = hass.states.get("media_player.player_name1111")
|
|
assert pre_state.state == "playing"
|
|
assert pre_state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.1
|
|
|
|
status = player_mocks.player_data.status_long_polling_mock.get()
|
|
status = dataclasses.replace(status, state="pause", volume=50, etag="changed")
|
|
player_mocks.player_data.status_long_polling_mock.set(status)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
post_state = hass.states.get("media_player.player_name1111")
|
|
|
|
assert post_state.state == MediaPlayerState.PAUSED
|
|
assert post_state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.5
|
|
|
|
|
|
async def test_unavailable_when_offline(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test that the media player goes unavailable when the player is unreachable."""
|
|
pre_state = hass.states.get("media_player.player_name1111")
|
|
assert pre_state.state == "playing"
|
|
|
|
player_mocks.player_data.status_long_polling_mock.set_error(
|
|
PlayerUnreachableError("Player not reachable")
|
|
)
|
|
player_mocks.player_data.status_long_polling_mock.trigger()
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
post_state = hass.states.get("media_player.player_name1111")
|
|
|
|
assert post_state.state == STATE_UNAVAILABLE
|
|
|
|
|
|
async def test_set_sleep_timer(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the set sleep timer action."""
|
|
await hass.services.async_call(
|
|
BLUESOUND_DOMAIN,
|
|
SERVICE_SET_TIMER,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.sleep_timer.assert_called_once()
|
|
|
|
|
|
async def test_clear_sleep_timer(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test the clear sleep timer action."""
|
|
|
|
player_mocks.player_data.player.sleep_timer.side_effect = [15, 30, 45, 60, 90, 0]
|
|
|
|
await hass.services.async_call(
|
|
BLUESOUND_DOMAIN,
|
|
SERVICE_CLEAR_TIMER,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.sleep_timer.assert_has_calls([call()] * 6)
|
|
|
|
|
|
async def test_join_cannot_join_to_self(
|
|
hass: HomeAssistant, setup_config_entry: None, player_mocks: PlayerMocks
|
|
) -> None:
|
|
"""Test that joining to self is not allowed."""
|
|
with pytest.raises(ServiceValidationError, match="Cannot join player to itself"):
|
|
await hass.services.async_call(
|
|
BLUESOUND_DOMAIN,
|
|
SERVICE_JOIN,
|
|
{
|
|
ATTR_ENTITY_ID: "media_player.player_name1111",
|
|
ATTR_MASTER: "media_player.player_name1111",
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
|
|
async def test_join(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
setup_config_entry_secondary: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the join action."""
|
|
await hass.services.async_call(
|
|
BLUESOUND_DOMAIN,
|
|
SERVICE_JOIN,
|
|
{
|
|
ATTR_ENTITY_ID: "media_player.player_name1111",
|
|
ATTR_MASTER: "media_player.player_name2222",
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data_secondary.player.add_slave.assert_called_once_with(
|
|
"1.1.1.1", 11000
|
|
)
|
|
|
|
|
|
async def test_unjoin(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
setup_config_entry_secondary: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the unjoin action."""
|
|
updated_sync_status = dataclasses.replace(
|
|
player_mocks.player_data.sync_status_long_polling_mock.get(),
|
|
master=PairedPlayer("2.2.2.2", 11000),
|
|
)
|
|
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
BLUESOUND_DOMAIN,
|
|
"unjoin",
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data_secondary.player.remove_slave.assert_called_once_with(
|
|
"1.1.1.1", 11000
|
|
)
|
|
|
|
|
|
async def test_attr_master(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the media player master."""
|
|
attr_master = hass.states.get("media_player.player_name1111").attributes[
|
|
ATTR_MASTER
|
|
]
|
|
assert attr_master is False
|
|
|
|
updated_sync_status = dataclasses.replace(
|
|
player_mocks.player_data.sync_status_long_polling_mock.get(),
|
|
slaves=[PairedPlayer("2.2.2.2", 11000)],
|
|
)
|
|
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
attr_master = hass.states.get("media_player.player_name1111").attributes[
|
|
ATTR_MASTER
|
|
]
|
|
|
|
assert attr_master is True
|
|
|
|
|
|
async def test_attr_bluesound_group(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
setup_config_entry_secondary: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the media player grouping."""
|
|
attr_bluesound_group = hass.states.get(
|
|
"media_player.player_name1111"
|
|
).attributes.get("bluesound_group")
|
|
assert attr_bluesound_group is None
|
|
|
|
updated_status = dataclasses.replace(
|
|
player_mocks.player_data.status_long_polling_mock.get(),
|
|
group_name="player-name1111+player-name2222",
|
|
)
|
|
player_mocks.player_data.status_long_polling_mock.set(updated_status)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
attr_bluesound_group = hass.states.get(
|
|
"media_player.player_name1111"
|
|
).attributes.get("bluesound_group")
|
|
|
|
assert attr_bluesound_group == ["player-name1111", "player-name2222"]
|
|
|
|
|
|
async def test_volume_up_from_6_to_7(
|
|
hass: HomeAssistant,
|
|
setup_config_entry: None,
|
|
player_mocks: PlayerMocks,
|
|
) -> None:
|
|
"""Test the media player volume up from 6 to 7.
|
|
|
|
This fails if if rounding is not done correctly. See https://github.com/home-assistant/core/issues/129956 for more details.
|
|
"""
|
|
player_mocks.player_data.status_long_polling_mock.set(
|
|
dataclasses.replace(
|
|
player_mocks.player_data.status_long_polling_mock.get(), volume=6
|
|
)
|
|
)
|
|
|
|
# give the long polling loop a chance to update the state; this could be any async call
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
MEDIA_PLAYER_DOMAIN,
|
|
SERVICE_VOLUME_UP,
|
|
{ATTR_ENTITY_ID: "media_player.player_name1111"},
|
|
blocking=True,
|
|
)
|
|
|
|
player_mocks.player_data.player.volume.assert_called_once_with(level=7)
|