1205 lines
43 KiB
Python
1205 lines
43 KiB
Python
"""The tests for the Universal Media player platform."""
|
|
import asyncio
|
|
from copy import copy
|
|
from os import path
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from voluptuous.error import MultipleInvalid
|
|
|
|
from homeassistant import config as hass_config
|
|
import homeassistant.components.input_number as input_number
|
|
import homeassistant.components.input_select as input_select
|
|
import homeassistant.components.media_player as media_player
|
|
import homeassistant.components.switch as switch
|
|
import homeassistant.components.universal.media_player as universal
|
|
from homeassistant.const import (
|
|
SERVICE_RELOAD,
|
|
STATE_OFF,
|
|
STATE_ON,
|
|
STATE_PAUSED,
|
|
STATE_PLAYING,
|
|
STATE_UNKNOWN,
|
|
)
|
|
from homeassistant.core import Context, callback
|
|
from homeassistant.setup import async_setup_component, setup_component
|
|
|
|
from tests.common import get_test_home_assistant, mock_service
|
|
|
|
|
|
def validate_config(config):
|
|
"""Use the platform schema to validate configuration."""
|
|
validated_config = universal.PLATFORM_SCHEMA(config)
|
|
validated_config.pop("platform")
|
|
return validated_config
|
|
|
|
|
|
class MockMediaPlayer(media_player.MediaPlayerEntity):
|
|
"""Mock media player for testing."""
|
|
|
|
def __init__(self, hass, name):
|
|
"""Initialize the media player."""
|
|
self.hass = hass
|
|
self._name = name
|
|
self.entity_id = media_player.ENTITY_ID_FORMAT.format(name)
|
|
self._state = STATE_OFF
|
|
self._volume_level = 0
|
|
self._is_volume_muted = False
|
|
self._media_title = None
|
|
self._supported_features = 0
|
|
self._source = None
|
|
self._tracks = 12
|
|
self._media_image_url = None
|
|
self._shuffle = False
|
|
self._sound_mode = None
|
|
|
|
self.service_calls = {
|
|
"turn_on": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_TURN_ON
|
|
),
|
|
"turn_off": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF
|
|
),
|
|
"mute_volume": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_MUTE
|
|
),
|
|
"set_volume_level": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET
|
|
),
|
|
"media_play": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY
|
|
),
|
|
"media_pause": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE
|
|
),
|
|
"media_stop": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_STOP
|
|
),
|
|
"media_previous_track": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PREVIOUS_TRACK
|
|
),
|
|
"media_next_track": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_NEXT_TRACK
|
|
),
|
|
"media_seek": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK
|
|
),
|
|
"play_media": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_PLAY_MEDIA
|
|
),
|
|
"volume_up": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_UP
|
|
),
|
|
"volume_down": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_DOWN
|
|
),
|
|
"media_play_pause": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY_PAUSE
|
|
),
|
|
"select_sound_mode": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOUND_MODE
|
|
),
|
|
"select_source": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE
|
|
),
|
|
"toggle": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_TOGGLE
|
|
),
|
|
"clear_playlist": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_CLEAR_PLAYLIST
|
|
),
|
|
"repeat_set": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_REPEAT_SET
|
|
),
|
|
"shuffle_set": mock_service(
|
|
hass, media_player.DOMAIN, media_player.SERVICE_SHUFFLE_SET
|
|
),
|
|
}
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of player."""
|
|
return self._name
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the player."""
|
|
return self._state
|
|
|
|
@property
|
|
def volume_level(self):
|
|
"""Return the volume level of player."""
|
|
return self._volume_level
|
|
|
|
@property
|
|
def is_volume_muted(self):
|
|
"""Return true if the media player is muted."""
|
|
return self._is_volume_muted
|
|
|
|
@property
|
|
def supported_features(self):
|
|
"""Flag media player features that are supported."""
|
|
return self._supported_features
|
|
|
|
@property
|
|
def media_image_url(self):
|
|
"""Image url of current playing media."""
|
|
return self._media_image_url
|
|
|
|
@property
|
|
def shuffle(self):
|
|
"""Return true if the media player is shuffling."""
|
|
return self._shuffle
|
|
|
|
def turn_on(self):
|
|
"""Mock turn_on function."""
|
|
self._state = None
|
|
|
|
def turn_off(self):
|
|
"""Mock turn_off function."""
|
|
self._state = STATE_OFF
|
|
|
|
def mute_volume(self, mute):
|
|
"""Mock mute function."""
|
|
self._is_volume_muted = mute
|
|
|
|
def set_volume_level(self, volume):
|
|
"""Mock set volume level."""
|
|
self._volume_level = volume
|
|
|
|
def media_play(self):
|
|
"""Mock play."""
|
|
self._state = STATE_PLAYING
|
|
|
|
def media_pause(self):
|
|
"""Mock pause."""
|
|
self._state = STATE_PAUSED
|
|
|
|
def select_sound_mode(self, sound_mode):
|
|
"""Set the sound mode."""
|
|
self._sound_mode = sound_mode
|
|
|
|
def select_source(self, source):
|
|
"""Set the input source."""
|
|
self._source = source
|
|
|
|
def async_toggle(self):
|
|
"""Toggle the power on the media player."""
|
|
self._state = STATE_OFF if self._state == STATE_ON else STATE_ON
|
|
|
|
def clear_playlist(self):
|
|
"""Clear players playlist."""
|
|
self._tracks = 0
|
|
|
|
def set_shuffle(self, shuffle):
|
|
"""Enable/disable shuffle mode."""
|
|
self._shuffle = shuffle
|
|
|
|
def set_repeat(self, repeat):
|
|
"""Enable/disable repeat mode."""
|
|
self._repeat = repeat
|
|
|
|
|
|
class TestMediaPlayer(unittest.TestCase):
|
|
"""Test the media_player module."""
|
|
|
|
def setUp(self): # pylint: disable=invalid-name
|
|
"""Set up things to be run when tests are started."""
|
|
self.hass = get_test_home_assistant()
|
|
|
|
self.mock_mp_1 = MockMediaPlayer(self.hass, "mock1")
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
|
|
self.mock_mp_2 = MockMediaPlayer(self.hass, "mock2")
|
|
self.mock_mp_2.schedule_update_ha_state()
|
|
|
|
self.hass.block_till_done()
|
|
|
|
self.mock_mute_switch_id = switch.ENTITY_ID_FORMAT.format("mute")
|
|
self.hass.states.set(self.mock_mute_switch_id, STATE_OFF)
|
|
|
|
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format("state")
|
|
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
|
|
|
|
self.mock_volume_id = f"{input_number.DOMAIN}.volume_level"
|
|
self.hass.states.set(self.mock_volume_id, 0)
|
|
|
|
self.mock_source_list_id = f"{input_select.DOMAIN}.source_list"
|
|
self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc"])
|
|
|
|
self.mock_source_id = f"{input_select.DOMAIN}.source"
|
|
self.hass.states.set(self.mock_source_id, "dvd")
|
|
|
|
self.mock_sound_mode_list_id = f"{input_select.DOMAIN}.sound_mode_list"
|
|
self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie"])
|
|
|
|
self.mock_sound_mode_id = f"{input_select.DOMAIN}.sound_mode"
|
|
self.hass.states.set(self.mock_sound_mode_id, "music")
|
|
|
|
self.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format("shuffle")
|
|
self.hass.states.set(self.mock_shuffle_switch_id, STATE_OFF)
|
|
|
|
self.mock_repeat_switch_id = switch.ENTITY_ID_FORMAT.format("repeat")
|
|
self.hass.states.set(self.mock_repeat_switch_id, STATE_OFF)
|
|
|
|
self.config_children_only = {
|
|
"name": "test",
|
|
"platform": "universal",
|
|
"children": [
|
|
media_player.ENTITY_ID_FORMAT.format("mock1"),
|
|
media_player.ENTITY_ID_FORMAT.format("mock2"),
|
|
],
|
|
}
|
|
self.config_children_and_attr = {
|
|
"name": "test",
|
|
"platform": "universal",
|
|
"children": [
|
|
media_player.ENTITY_ID_FORMAT.format("mock1"),
|
|
media_player.ENTITY_ID_FORMAT.format("mock2"),
|
|
],
|
|
"attributes": {
|
|
"is_volume_muted": self.mock_mute_switch_id,
|
|
"volume_level": self.mock_volume_id,
|
|
"source": self.mock_source_id,
|
|
"source_list": self.mock_source_list_id,
|
|
"state": self.mock_state_switch_id,
|
|
"shuffle": self.mock_shuffle_switch_id,
|
|
"repeat": self.mock_repeat_switch_id,
|
|
"sound_mode_list": self.mock_sound_mode_list_id,
|
|
"sound_mode": self.mock_sound_mode_id,
|
|
},
|
|
}
|
|
self.addCleanup(self.tear_down_cleanup)
|
|
|
|
def tear_down_cleanup(self):
|
|
"""Stop everything that was started."""
|
|
self.hass.stop()
|
|
|
|
def test_config_children_only(self):
|
|
"""Check config with only children."""
|
|
config_start = copy(self.config_children_only)
|
|
del config_start["platform"]
|
|
config_start["commands"] = {}
|
|
config_start["attributes"] = {}
|
|
|
|
config = validate_config(self.config_children_only)
|
|
assert config_start == config
|
|
|
|
def test_config_children_and_attr(self):
|
|
"""Check config with children and attributes."""
|
|
config_start = copy(self.config_children_and_attr)
|
|
del config_start["platform"]
|
|
config_start["commands"] = {}
|
|
|
|
config = validate_config(self.config_children_and_attr)
|
|
assert config_start == config
|
|
|
|
def test_config_no_name(self):
|
|
"""Check config with no Name entry."""
|
|
response = True
|
|
try:
|
|
validate_config({"platform": "universal"})
|
|
except MultipleInvalid:
|
|
response = False
|
|
assert not response
|
|
|
|
def test_config_bad_children(self):
|
|
"""Check config with bad children entry."""
|
|
config_no_children = {"name": "test", "platform": "universal"}
|
|
config_bad_children = {"name": "test", "children": {}, "platform": "universal"}
|
|
|
|
config_no_children = validate_config(config_no_children)
|
|
assert [] == config_no_children["children"]
|
|
|
|
config_bad_children = validate_config(config_bad_children)
|
|
assert [] == config_bad_children["children"]
|
|
|
|
def test_config_bad_commands(self):
|
|
"""Check config with bad commands entry."""
|
|
config = {"name": "test", "platform": "universal"}
|
|
|
|
config = validate_config(config)
|
|
assert {} == config["commands"]
|
|
|
|
def test_config_bad_attributes(self):
|
|
"""Check config with bad attributes."""
|
|
config = {"name": "test", "platform": "universal"}
|
|
|
|
config = validate_config(config)
|
|
assert {} == config["attributes"]
|
|
|
|
def test_config_bad_key(self):
|
|
"""Check config with bad key."""
|
|
config = {"name": "test", "asdf": 5, "platform": "universal"}
|
|
|
|
config = validate_config(config)
|
|
assert "asdf" not in config
|
|
|
|
def test_platform_setup(self):
|
|
"""Test platform setup."""
|
|
config = {"name": "test", "platform": "universal"}
|
|
bad_config = {"platform": "universal"}
|
|
entities = []
|
|
|
|
def add_entities(new_entities):
|
|
"""Add devices to list."""
|
|
for dev in new_entities:
|
|
entities.append(dev)
|
|
|
|
setup_ok = True
|
|
try:
|
|
asyncio.run_coroutine_threadsafe(
|
|
universal.async_setup_platform(
|
|
self.hass, validate_config(bad_config), add_entities
|
|
),
|
|
self.hass.loop,
|
|
).result()
|
|
except MultipleInvalid:
|
|
setup_ok = False
|
|
assert not setup_ok
|
|
assert len(entities) == 0
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
universal.async_setup_platform(
|
|
self.hass, validate_config(config), add_entities
|
|
),
|
|
self.hass.loop,
|
|
).result()
|
|
assert len(entities) == 1
|
|
assert entities[0].name == "test"
|
|
|
|
def test_master_state(self):
|
|
"""Test master state property."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.master_state is None
|
|
|
|
def test_master_state_with_attrs(self):
|
|
"""Test master state property."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.master_state == STATE_OFF
|
|
self.hass.states.set(self.mock_state_switch_id, STATE_ON)
|
|
assert ump.master_state == STATE_ON
|
|
|
|
def test_master_state_with_bad_attrs(self):
|
|
"""Test master state property."""
|
|
config = copy(self.config_children_and_attr)
|
|
config["attributes"]["state"] = "bad.entity_id"
|
|
config = validate_config(config)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.master_state == STATE_OFF
|
|
|
|
def test_active_child_state(self):
|
|
"""Test active child state property."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump._child_state is None
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert self.mock_mp_1.entity_id == ump._child_state.entity_id
|
|
|
|
self.mock_mp_2._state = STATE_PLAYING
|
|
self.mock_mp_2.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert self.mock_mp_1.entity_id == ump._child_state.entity_id
|
|
|
|
self.mock_mp_1._state = STATE_OFF
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert self.mock_mp_2.entity_id == ump._child_state.entity_id
|
|
|
|
def test_name(self):
|
|
"""Test name property."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert config["name"] == ump.name
|
|
|
|
def test_polling(self):
|
|
"""Test should_poll property."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.should_poll is False
|
|
|
|
def test_state_children_only(self):
|
|
"""Test media player state with only children."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump.state, STATE_OFF
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.state == STATE_PLAYING
|
|
|
|
def test_state_with_children_and_attrs(self):
|
|
"""Test media player with children and master state."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump.state == STATE_OFF
|
|
|
|
self.hass.states.set(self.mock_state_switch_id, STATE_ON)
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.state == STATE_ON
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.state == STATE_PLAYING
|
|
|
|
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.state == STATE_OFF
|
|
|
|
def test_volume_level(self):
|
|
"""Test volume level property."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump.volume_level is None
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.volume_level == 0
|
|
|
|
self.mock_mp_1._volume_level = 1
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.volume_level == 1
|
|
|
|
def test_media_image_url(self):
|
|
"""Test media_image_url property."""
|
|
test_url = "test_url"
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump.media_image_url is None
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1._media_image_url = test_url
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
# mock_mp_1 will convert the url to the api proxy url. This test
|
|
# ensures ump passes through the same url without an additional proxy.
|
|
assert self.mock_mp_1.entity_picture == ump.entity_picture
|
|
|
|
def test_is_volume_muted_children_only(self):
|
|
"""Test is volume muted property w/ children only."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert not ump.is_volume_muted
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert not ump.is_volume_muted
|
|
|
|
self.mock_mp_1._is_volume_muted = True
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.is_volume_muted
|
|
|
|
def test_sound_mode_list_children_and_attr(self):
|
|
"""Test sound mode list property w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.sound_mode_list == "['music', 'movie']"
|
|
|
|
self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie", "game"])
|
|
assert ump.sound_mode_list == "['music', 'movie', 'game']"
|
|
|
|
def test_source_list_children_and_attr(self):
|
|
"""Test source list property w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.source_list == "['dvd', 'htpc']"
|
|
|
|
self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc", "game"])
|
|
assert ump.source_list == "['dvd', 'htpc', 'game']"
|
|
|
|
def test_sound_mode_children_and_attr(self):
|
|
"""Test sound modeproperty w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.sound_mode == "music"
|
|
|
|
self.hass.states.set(self.mock_sound_mode_id, "movie")
|
|
assert ump.sound_mode == "movie"
|
|
|
|
def test_source_children_and_attr(self):
|
|
"""Test source property w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.source == "dvd"
|
|
|
|
self.hass.states.set(self.mock_source_id, "htpc")
|
|
assert ump.source == "htpc"
|
|
|
|
def test_volume_level_children_and_attr(self):
|
|
"""Test volume level property w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert ump.volume_level == 0
|
|
|
|
self.hass.states.set(self.mock_volume_id, 100)
|
|
assert ump.volume_level == 100
|
|
|
|
def test_is_volume_muted_children_and_attr(self):
|
|
"""Test is volume muted property w/ children and attrs."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
|
|
assert not ump.is_volume_muted
|
|
|
|
self.hass.states.set(self.mock_mute_switch_id, STATE_ON)
|
|
assert ump.is_volume_muted
|
|
|
|
def test_supported_features_children_only(self):
|
|
"""Test supported media commands with only children."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
assert ump.supported_features == 0
|
|
|
|
self.mock_mp_1._supported_features = 512
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
assert ump.supported_features == 512
|
|
|
|
def test_supported_features_children_and_cmds(self):
|
|
"""Test supported media commands with children and attrs."""
|
|
config = copy(self.config_children_and_attr)
|
|
excmd = {"service": "media_player.test", "data": {}}
|
|
config["commands"] = {
|
|
"turn_on": excmd,
|
|
"turn_off": excmd,
|
|
"volume_up": excmd,
|
|
"volume_down": excmd,
|
|
"volume_mute": excmd,
|
|
"volume_set": excmd,
|
|
"select_sound_mode": excmd,
|
|
"select_source": excmd,
|
|
"repeat_set": excmd,
|
|
"shuffle_set": excmd,
|
|
"media_play": excmd,
|
|
"media_pause": excmd,
|
|
"media_stop": excmd,
|
|
"media_next_track": excmd,
|
|
"media_previous_track": excmd,
|
|
"toggle": excmd,
|
|
"play_media": excmd,
|
|
"clear_playlist": excmd,
|
|
}
|
|
config = validate_config(config)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
check_flags = (
|
|
universal.SUPPORT_TURN_ON
|
|
| universal.SUPPORT_TURN_OFF
|
|
| universal.SUPPORT_VOLUME_STEP
|
|
| universal.SUPPORT_VOLUME_MUTE
|
|
| universal.SUPPORT_SELECT_SOUND_MODE
|
|
| universal.SUPPORT_SELECT_SOURCE
|
|
| universal.SUPPORT_REPEAT_SET
|
|
| universal.SUPPORT_SHUFFLE_SET
|
|
| universal.SUPPORT_VOLUME_SET
|
|
| universal.SUPPORT_PLAY
|
|
| universal.SUPPORT_PAUSE
|
|
| universal.SUPPORT_STOP
|
|
| universal.SUPPORT_NEXT_TRACK
|
|
| universal.SUPPORT_PREVIOUS_TRACK
|
|
| universal.SUPPORT_PLAY_MEDIA
|
|
| universal.SUPPORT_CLEAR_PLAYLIST
|
|
)
|
|
|
|
assert check_flags == ump.supported_features
|
|
|
|
def test_overrides(self):
|
|
"""Test overrides."""
|
|
config = copy(self.config_children_and_attr)
|
|
excmd = {"service": "test.override", "data": {}}
|
|
config["name"] = "overridden"
|
|
config["commands"] = {
|
|
"turn_on": excmd,
|
|
"turn_off": excmd,
|
|
"volume_up": excmd,
|
|
"volume_down": excmd,
|
|
"volume_mute": excmd,
|
|
"volume_set": excmd,
|
|
"select_sound_mode": excmd,
|
|
"select_source": excmd,
|
|
"repeat_set": excmd,
|
|
"shuffle_set": excmd,
|
|
"media_play": excmd,
|
|
"media_play_pause": excmd,
|
|
"media_pause": excmd,
|
|
"media_stop": excmd,
|
|
"media_next_track": excmd,
|
|
"media_previous_track": excmd,
|
|
"clear_playlist": excmd,
|
|
"play_media": excmd,
|
|
"toggle": excmd,
|
|
}
|
|
setup_component(self.hass, "media_player", {"media_player": config})
|
|
|
|
service = mock_service(self.hass, "test", "override")
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"turn_on",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 1
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"turn_off",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 2
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"volume_up",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 3
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"volume_down",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 4
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"volume_mute",
|
|
service_data={
|
|
"entity_id": "media_player.overridden",
|
|
"is_volume_muted": True,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 5
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"volume_set",
|
|
service_data={"entity_id": "media_player.overridden", "volume_level": 1},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 6
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"select_sound_mode",
|
|
service_data={
|
|
"entity_id": "media_player.overridden",
|
|
"sound_mode": "music",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 7
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"select_source",
|
|
service_data={"entity_id": "media_player.overridden", "source": "video1"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 8
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"repeat_set",
|
|
service_data={"entity_id": "media_player.overridden", "repeat": "all"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 9
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"shuffle_set",
|
|
service_data={"entity_id": "media_player.overridden", "shuffle": True},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 10
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_play",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 11
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_pause",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 12
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_stop",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 13
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_next_track",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 14
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_previous_track",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 15
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"clear_playlist",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 16
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"media_play_pause",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 17
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"play_media",
|
|
service_data={
|
|
"entity_id": "media_player.overridden",
|
|
"media_content_id": 1,
|
|
"media_content_type": "channel",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 18
|
|
self.hass.services.call(
|
|
"media_player",
|
|
"toggle",
|
|
service_data={"entity_id": "media_player.overridden"},
|
|
blocking=True,
|
|
)
|
|
assert len(service) == 19
|
|
|
|
def test_supported_features_play_pause(self):
|
|
"""Test supported media commands with play_pause function."""
|
|
config = copy(self.config_children_and_attr)
|
|
excmd = {"service": "media_player.test", "data": {"entity_id": "test"}}
|
|
config["commands"] = {"media_play_pause": excmd}
|
|
config = validate_config(config)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
self.mock_mp_1._state = STATE_PLAYING
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
check_flags = universal.SUPPORT_PLAY | universal.SUPPORT_PAUSE
|
|
|
|
assert check_flags == ump.supported_features
|
|
|
|
def test_service_call_no_active_child(self):
|
|
"""Test a service call to children with no active child."""
|
|
config = validate_config(self.config_children_and_attr)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
self.mock_mp_1._state = STATE_OFF
|
|
self.mock_mp_1.schedule_update_ha_state()
|
|
self.mock_mp_2._state = STATE_OFF
|
|
self.mock_mp_2.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result()
|
|
assert len(self.mock_mp_1.service_calls["turn_off"]) == 0
|
|
assert len(self.mock_mp_2.service_calls["turn_off"]) == 0
|
|
|
|
def test_service_call_to_child(self):
|
|
"""Test service calls that should be routed to a child."""
|
|
config = validate_config(self.config_children_only)
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
self.mock_mp_2._state = STATE_PLAYING
|
|
self.mock_mp_2.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result()
|
|
assert len(self.mock_mp_2.service_calls["turn_off"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_turn_on(), self.hass.loop).result()
|
|
assert len(self.mock_mp_2.service_calls["turn_on"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_mute_volume(True), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["mute_volume"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_set_volume_level(0.5), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["set_volume_level"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_play(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_play"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_pause(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_pause"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_stop(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_stop"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_previous_track(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_previous_track"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_next_track(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_next_track"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_seek(100), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_seek"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_play_media("movie", "batman"), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["play_media"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_volume_up(), self.hass.loop).result()
|
|
assert len(self.mock_mp_2.service_calls["volume_up"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_volume_down(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["volume_down"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_media_play_pause(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["media_play_pause"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_select_sound_mode("music"), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["select_sound_mode"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_select_source("dvd"), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["select_source"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_clear_playlist(), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["clear_playlist"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_set_repeat(True), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["repeat_set"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(
|
|
ump.async_set_shuffle(True), self.hass.loop
|
|
).result()
|
|
assert len(self.mock_mp_2.service_calls["shuffle_set"]) == 1
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_toggle(), self.hass.loop).result()
|
|
assert len(self.mock_mp_2.service_calls["toggle"]) == 1
|
|
|
|
def test_service_call_to_command(self):
|
|
"""Test service call to command."""
|
|
config = copy(self.config_children_only)
|
|
config["commands"] = {"turn_off": {"service": "test.turn_off", "data": {}}}
|
|
config = validate_config(config)
|
|
|
|
service = mock_service(self.hass, "test", "turn_off")
|
|
|
|
ump = universal.UniversalMediaPlayer(self.hass, **config)
|
|
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
self.mock_mp_2._state = STATE_PLAYING
|
|
self.mock_mp_2.schedule_update_ha_state()
|
|
self.hass.block_till_done()
|
|
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
|
|
|
|
asyncio.run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result()
|
|
assert len(service) == 1
|
|
|
|
|
|
async def test_state_template(hass):
|
|
"""Test with a simple valid state template."""
|
|
hass.states.async_set("sensor.test_sensor", STATE_ON)
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"media_player",
|
|
{
|
|
"media_player": {
|
|
"platform": "universal",
|
|
"name": "tv",
|
|
"state_template": "{{ states.sensor.test_sensor.state }}",
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert len(hass.states.async_all()) == 2
|
|
await hass.async_start()
|
|
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_ON
|
|
hass.states.async_set("sensor.test_sensor", STATE_OFF)
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_OFF
|
|
|
|
|
|
async def test_device_class(hass):
|
|
"""Test device_class property."""
|
|
hass.states.async_set("sensor.test_sensor", "on")
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"media_player",
|
|
{
|
|
"media_player": {
|
|
"platform": "universal",
|
|
"name": "tv",
|
|
"device_class": "tv",
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").attributes["device_class"] == "tv"
|
|
|
|
|
|
async def test_invalid_state_template(hass):
|
|
"""Test invalid state template sets state to None."""
|
|
hass.states.async_set("sensor.test_sensor", "on")
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"media_player",
|
|
{
|
|
"media_player": {
|
|
"platform": "universal",
|
|
"name": "tv",
|
|
"state_template": "{{ states.sensor.test_sensor.state + x }}",
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert len(hass.states.async_all()) == 2
|
|
await hass.async_start()
|
|
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_UNKNOWN
|
|
hass.states.async_set("sensor.test_sensor", "off")
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_UNKNOWN
|
|
|
|
|
|
async def test_master_state_with_template(hass):
|
|
"""Test the state_template option."""
|
|
hass.states.async_set("input_boolean.test", STATE_OFF)
|
|
hass.states.async_set("media_player.mock1", STATE_OFF)
|
|
|
|
templ = (
|
|
'{% if states.input_boolean.test.state == "off" %}on'
|
|
"{% else %}{{ states.media_player.mock1.state }}{% endif %}"
|
|
)
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"media_player",
|
|
{
|
|
"media_player": {
|
|
"platform": "universal",
|
|
"name": "tv",
|
|
"state_template": templ,
|
|
}
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
assert len(hass.states.async_all()) == 3
|
|
await hass.async_start()
|
|
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_ON
|
|
|
|
events = []
|
|
|
|
hass.helpers.event.async_track_state_change_event(
|
|
"media_player.tv", callback(lambda event: events.append(event))
|
|
)
|
|
|
|
context = Context()
|
|
hass.states.async_set("input_boolean.test", STATE_ON, context=context)
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.states.get("media_player.tv").state == STATE_OFF
|
|
assert events[0].context == context
|
|
|
|
|
|
async def test_reload(hass):
|
|
"""Test reloading the media player from yaml."""
|
|
hass.states.async_set("input_boolean.test", STATE_OFF)
|
|
hass.states.async_set("media_player.mock1", STATE_OFF)
|
|
|
|
templ = (
|
|
'{% if states.input_boolean.test.state == "off" %}on'
|
|
"{% else %}{{ states.media_player.mock1.state }}{% endif %}"
|
|
)
|
|
|
|
await async_setup_component(
|
|
hass,
|
|
"media_player",
|
|
{
|
|
"media_player": {
|
|
"platform": "universal",
|
|
"name": "tv",
|
|
"state_template": templ,
|
|
}
|
|
},
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
assert len(hass.states.async_all()) == 3
|
|
await hass.async_start()
|
|
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get("media_player.tv").state == STATE_ON
|
|
|
|
hass.states.async_set("input_boolean.test", STATE_ON)
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.states.get("media_player.tv").state == STATE_OFF
|
|
|
|
hass.states.async_set("media_player.master_bedroom_2", STATE_OFF)
|
|
hass.states.async_set(
|
|
"remote.alexander_master_bedroom",
|
|
STATE_ON,
|
|
{"activity_list": ["act1", "act2"], "current_activity": "act2"},
|
|
)
|
|
|
|
yaml_path = path.join(
|
|
_get_fixtures_base_path(),
|
|
"fixtures",
|
|
"universal/configuration.yaml",
|
|
)
|
|
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
|
|
await hass.services.async_call(
|
|
"universal",
|
|
SERVICE_RELOAD,
|
|
{},
|
|
blocking=True,
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.states.async_all()) == 5
|
|
|
|
assert hass.states.get("media_player.tv") is None
|
|
assert hass.states.get("media_player.master_bed_tv").state == "on"
|
|
assert hass.states.get("media_player.master_bed_tv").attributes["source"] == "act2"
|
|
assert (
|
|
"device_class" not in hass.states.get("media_player.master_bed_tv").attributes
|
|
)
|
|
|
|
|
|
def _get_fixtures_base_path():
|
|
return path.dirname(path.dirname(path.dirname(__file__)))
|