core/tests/components/group/test_media_player.py

659 lines
22 KiB
Python

"""The tests for the Media group platform."""
import asyncio
from unittest.mock import MagicMock, Mock, patch
import pytest
from homeassistant.components.group import DOMAIN
from homeassistant.components.media_player import (
ATTR_MEDIA_ANNOUNCE,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_EXTRA,
ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_SHUFFLE,
ATTR_MEDIA_TRACK,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
DOMAIN as MEDIA_DOMAIN,
SERVICE_CLEAR_PLAYLIST,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_SEEK,
SERVICE_PLAY_MEDIA,
SERVICE_SHUFFLE_SET,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_VOLUME_SET,
MediaPlayerEntityFeature,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_STOP,
SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_UP,
STATE_BUFFERING,
STATE_IDLE,
STATE_OFF,
STATE_ON,
STATE_PAUSED,
STATE_PLAYING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform, entity_registry as er
from homeassistant.setup import async_setup_component
@pytest.fixture(name="mock_media_seek")
def media_player_media_seek_fixture():
"""Mock demo YouTube player media seek."""
with patch(
"homeassistant.components.demo.media_player.DemoYoutubePlayer.media_seek",
autospec=True,
) as seek:
yield seek
async def test_default_state(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test media group default state."""
hass.states.async_set("media_player.player_1", "on")
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: {
"platform": DOMAIN,
"entities": ["media_player.player_1", "media_player.player_2"],
"name": "Media group",
"unique_id": "unique_identifier",
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("media_player.media_group")
assert state is not None
assert state.state == STATE_ON
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
assert state.attributes.get(ATTR_ENTITY_ID) == [
"media_player.player_1",
"media_player.player_2",
]
entry = entity_registry.async_get("media_player.media_group")
assert entry
assert entry.unique_id == "unique_identifier"
async def test_state_reporting(hass: HomeAssistant) -> None:
"""Test the state reporting.
The group state is unavailable if all group members are unavailable.
Otherwise, the group state is unknown if all group members are unknown.
Otherwise, the group state is buffering if all group members are buffering.
Otherwise, the group state is idle if all group members are idle.
Otherwise, the group state is paused if all group members are paused.
Otherwise, the group state is playing if all group members are playing.
Otherwise, the group state is on if at least one group member is not off, unavailable or unknown.
Otherwise, the group state is off.
"""
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: {
"platform": DOMAIN,
"entities": ["media_player.player_1", "media_player.player_2"],
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
# Initial state with no group member in the state machine -> unavailable
assert hass.states.get("media_player.media_group").state == STATE_UNAVAILABLE
# All group members unavailable -> unavailable
hass.states.async_set("media_player.player_1", STATE_UNAVAILABLE)
hass.states.async_set("media_player.player_2", STATE_UNAVAILABLE)
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_UNAVAILABLE
# The group state is unknown if all group members are unknown or unavailable.
for state_1 in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
hass.states.async_set("media_player.player_1", state_1)
hass.states.async_set("media_player.player_2", STATE_UNKNOWN)
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_UNKNOWN
# All group members buffering -> buffering
# All group members idle -> idle
# All group members paused -> paused
# All group members playing -> playing
# All group members unavailable -> unavailable
# All group members unknown -> unknown
for state in (
STATE_BUFFERING,
STATE_IDLE,
STATE_PAUSED,
STATE_PLAYING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
hass.states.async_set("media_player.player_1", state)
hass.states.async_set("media_player.player_2", state)
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == state
# At least one member not off, unavailable or unknown -> on
for state_1 in (STATE_BUFFERING, STATE_IDLE, STATE_ON, STATE_PAUSED, STATE_PLAYING):
for state_2 in (STATE_OFF, STATE_UNAVAILABLE, STATE_UNKNOWN):
hass.states.async_set("media_player.player_1", state_1)
hass.states.async_set("media_player.player_2", state_2)
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_ON
# Otherwise off
for state_1 in (STATE_OFF, STATE_UNAVAILABLE, STATE_UNKNOWN):
hass.states.async_set("media_player.player_1", state_1)
hass.states.async_set("media_player.player_2", STATE_OFF)
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_OFF
# All group members in same invalid state -> unknown
hass.states.async_set("media_player.player_1", "invalid_state")
hass.states.async_set("media_player.player_2", "invalid_state")
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_UNKNOWN
# All group members removed from the state machine -> unavailable
hass.states.async_remove("media_player.player_1")
hass.states.async_remove("media_player.player_2")
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_UNAVAILABLE
async def test_supported_features(hass: HomeAssistant) -> None:
"""Test supported features reporting."""
pause_play_stop = (
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.STOP
)
play_media = (
MediaPlayerEntityFeature.PLAY_MEDIA
| MediaPlayerEntityFeature.MEDIA_ANNOUNCE
| MediaPlayerEntityFeature.MEDIA_ENQUEUE
)
volume = (
MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
)
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: {
"platform": DOMAIN,
"entities": ["media_player.player_1", "media_player.player_2"],
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
hass.states.async_set(
"media_player.player_1", STATE_ON, {ATTR_SUPPORTED_FEATURES: 0}
)
await hass.async_block_till_done()
state = hass.states.get("media_player.media_group")
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
hass.states.async_set(
"media_player.player_1",
STATE_ON,
{ATTR_SUPPORTED_FEATURES: pause_play_stop},
)
await hass.async_block_till_done()
state = hass.states.get("media_player.media_group")
assert state.attributes[ATTR_SUPPORTED_FEATURES] == pause_play_stop
hass.states.async_set(
"media_player.player_2",
STATE_OFF,
{ATTR_SUPPORTED_FEATURES: play_media | volume},
)
await hass.async_block_till_done()
state = hass.states.get("media_player.media_group")
assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== pause_play_stop | play_media | volume
)
hass.states.async_set(
"media_player.player_2", STATE_OFF, {ATTR_SUPPORTED_FEATURES: play_media}
)
await hass.async_block_till_done()
state = hass.states.get("media_player.media_group")
assert state.attributes[ATTR_SUPPORTED_FEATURES] == pause_play_stop | play_media
async def test_service_calls(hass: HomeAssistant, mock_media_seek: Mock) -> None:
"""Test service calls."""
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": [
"media_player.bedroom",
"media_player.kitchen",
"media_player.living_room",
],
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.get("media_player.media_group").state == STATE_PLAYING
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_OFF
assert hass.states.get("media_player.kitchen").state == STATE_OFF
assert hass.states.get("media_player.living_room").state == STATE_OFF
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_PLAYING
assert hass.states.get("media_player.kitchen").state == STATE_PLAYING
assert hass.states.get("media_player.living_room").state == STATE_PLAYING
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_PAUSE,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_PAUSED
assert hass.states.get("media_player.kitchen").state == STATE_PAUSED
assert hass.states.get("media_player.living_room").state == STATE_PAUSED
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_PLAY,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_PLAYING
assert hass.states.get("media_player.kitchen").state == STATE_PLAYING
assert hass.states.get("media_player.living_room").state == STATE_PLAYING
# ATTR_MEDIA_TRACK is not supported by bedroom and living_room players
assert hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_TRACK] == 1
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_NEXT_TRACK,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_TRACK] == 2
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_PREVIOUS_TRACK,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_TRACK] == 1
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.media_group",
ATTR_MEDIA_CONTENT_TYPE: "some_type",
ATTR_MEDIA_CONTENT_ID: "some_id",
},
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_CONTENT_ID]
== "some_id"
)
# media_player.kitchen is skipped because it always returns "bounzz-1"
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_CONTENT_ID]
== "some_id"
)
state = hass.states.get("media_player.media_group")
assert state.attributes[ATTR_SUPPORTED_FEATURES] & MediaPlayerEntityFeature.SEEK
assert not mock_media_seek.called
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_SEEK,
{
ATTR_ENTITY_ID: "media_player.media_group",
ATTR_MEDIA_SEEK_POSITION: 100,
},
)
await hass.async_block_till_done()
assert mock_media_seek.called
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_LEVEL] == 1
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_LEVEL] == 1
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 1
)
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_VOLUME_SET,
{
ATTR_ENTITY_ID: "media_player.media_group",
ATTR_MEDIA_VOLUME_LEVEL: 0.5,
},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_VOLUME_UP,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.6
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.6
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.6
)
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_VOLUME_DOWN,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_MUTED]
is False
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_MUTED]
is False
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_MUTED]
is False
)
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: "media_player.media_group", ATTR_MEDIA_VOLUME_MUTED: True},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_VOLUME_MUTED]
is True
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_VOLUME_MUTED]
is True
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_VOLUME_MUTED]
is True
)
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_SHUFFLE] is False
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_SHUFFLE] is False
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_SHUFFLE]
is False
)
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_SHUFFLE_SET,
{ATTR_ENTITY_ID: "media_player.media_group", ATTR_MEDIA_SHUFFLE: True},
blocking=True,
)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.bedroom").attributes[ATTR_MEDIA_SHUFFLE] is True
)
assert (
hass.states.get("media_player.kitchen").attributes[ATTR_MEDIA_SHUFFLE] is True
)
assert (
hass.states.get("media_player.living_room").attributes[ATTR_MEDIA_SHUFFLE]
is True
)
assert hass.states.get("media_player.bedroom").state == STATE_PLAYING
assert hass.states.get("media_player.kitchen").state == STATE_PLAYING
assert hass.states.get("media_player.living_room").state == STATE_PLAYING
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_CLEAR_PLAYLIST,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
# SERVICE_CLEAR_PLAYLIST is not supported by bedroom and living_room players
assert hass.states.get("media_player.kitchen").state == STATE_OFF
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_PLAY,
{ATTR_ENTITY_ID: "media_player.kitchen"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_PLAYING
assert hass.states.get("media_player.kitchen").state == STATE_PLAYING
assert hass.states.get("media_player.living_room").state == STATE_PLAYING
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_MEDIA_STOP,
{ATTR_ENTITY_ID: "media_player.media_group"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_OFF
assert hass.states.get("media_player.kitchen").state == STATE_OFF
assert hass.states.get("media_player.living_room").state == STATE_OFF
async def test_nested_group(hass: HomeAssistant) -> None:
"""Test nested media group."""
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": ["media_player.group_1"],
"name": "Nested Group",
},
{
"platform": DOMAIN,
"entities": ["media_player.bedroom", "media_player.kitchen"],
"name": "Group 1",
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("media_player.group_1")
assert state is not None
assert state.state == STATE_PLAYING
assert state.attributes.get(ATTR_ENTITY_ID) == [
"media_player.bedroom",
"media_player.kitchen",
]
state = hass.states.get("media_player.nested_group")
assert state is not None
assert state.state == STATE_PLAYING
assert state.attributes.get(ATTR_ENTITY_ID) == ["media_player.group_1"]
# Test controlling the nested group
async with asyncio.timeout(0.5):
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "media_player.group_1"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("media_player.bedroom").state == STATE_OFF
assert hass.states.get("media_player.kitchen").state == STATE_OFF
assert hass.states.get("media_player.group_1").state == STATE_OFF
assert hass.states.get("media_player.nested_group").state == STATE_OFF
async def test_service_play_media_kwargs(hass: HomeAssistant) -> None:
"""Test that kwargs get passed through on play_media service call."""
await async_setup_component(
hass,
MEDIA_DOMAIN,
{
MEDIA_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": [
"media_player.bedroom",
"media_player.living_room",
],
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
platform = entity_platform.async_get_platforms(hass, "media_player")[0]
mp_bedroom = platform.domain_entities["media_player.bedroom"]
mp_bedroom.play_media = MagicMock()
mp_living_room = platform.domain_entities["media_player.living_room"]
mp_living_room.play_media = MagicMock()
await hass.services.async_call(
MEDIA_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.media_group",
ATTR_MEDIA_CONTENT_TYPE: "some_type",
ATTR_MEDIA_CONTENT_ID: "some_id",
ATTR_MEDIA_ANNOUNCE: "true",
ATTR_MEDIA_EXTRA: {
"volume": 20,
},
},
)
await hass.async_block_till_done()
assert mp_bedroom.play_media.call_count == 1
mp_bedroom.play_media.assert_called_with(
"some_type", "some_id", announce=True, extra={"volume": 20}
)
assert mp_living_room.play_media.call_count == 1
mp_living_room.play_media.assert_called_with(
"some_type", "some_id", announce=True, extra={"volume": 20}
)