mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
Check for supported features in media_player services (#22878)
* Add check for supported features * Move logic to service helper * Fix hacked in test for seek * Test for service required features
This commit is contained in:
parent
fc7a187dd6
commit
7624d0e79f
@ -1,14 +1,13 @@
|
|||||||
"""Demo implementation of the media player."""
|
"""Demo implementation of the media player."""
|
||||||
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
|
||||||
import homeassistant.util.dt as dt_util
|
|
||||||
|
|
||||||
from homeassistant.components.media_player import MediaPlayerDevice
|
from homeassistant.components.media_player import MediaPlayerDevice
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
||||||
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||||
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOUND_MODE,
|
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||||
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_TURN_OFF,
|
SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET,
|
||||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
||||||
|
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@ -30,7 +29,8 @@ DEFAULT_SOUND_MODE = 'Dummy Music'
|
|||||||
YOUTUBE_PLAYER_SUPPORT = \
|
YOUTUBE_PLAYER_SUPPORT = \
|
||||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | \
|
||||||
SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOUND_MODE | SUPPORT_SELECT_SOURCE
|
SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOUND_MODE | SUPPORT_SELECT_SOURCE | \
|
||||||
|
SUPPORT_SEEK
|
||||||
|
|
||||||
MUSIC_PLAYER_SUPPORT = \
|
MUSIC_PLAYER_SUPPORT = \
|
||||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||||
|
@ -32,51 +32,20 @@ from homeassistant.helpers.entity_component import EntityComponent
|
|||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_APP_ID,
|
ATTR_APP_ID, ATTR_APP_NAME, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST,
|
||||||
ATTR_APP_NAME,
|
ATTR_MEDIA_ALBUM_ARTIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_ARTIST,
|
||||||
ATTR_INPUT_SOURCE,
|
ATTR_MEDIA_CHANNEL, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE,
|
||||||
ATTR_INPUT_SOURCE_LIST,
|
ATTR_MEDIA_DURATION, ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_EPISODE,
|
||||||
ATTR_MEDIA_ALBUM_ARTIST,
|
ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT,
|
||||||
ATTR_MEDIA_ALBUM_NAME,
|
ATTR_MEDIA_SEASON, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_SERIES_TITLE,
|
||||||
ATTR_MEDIA_ARTIST,
|
ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_TITLE, ATTR_MEDIA_TRACK,
|
||||||
ATTR_MEDIA_CHANNEL,
|
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE,
|
||||||
ATTR_MEDIA_CONTENT_ID,
|
ATTR_SOUND_MODE_LIST, DOMAIN, SERVICE_CLEAR_PLAYLIST, SERVICE_PLAY_MEDIA,
|
||||||
ATTR_MEDIA_CONTENT_TYPE,
|
SERVICE_SELECT_SOUND_MODE, SERVICE_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST,
|
||||||
ATTR_MEDIA_DURATION,
|
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
||||||
ATTR_MEDIA_ENQUEUE,
|
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOUND_MODE,
|
||||||
ATTR_MEDIA_EPISODE,
|
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF,
|
||||||
ATTR_MEDIA_PLAYLIST,
|
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
||||||
ATTR_MEDIA_POSITION,
|
|
||||||
ATTR_MEDIA_POSITION_UPDATED_AT,
|
|
||||||
ATTR_MEDIA_SEASON,
|
|
||||||
ATTR_MEDIA_SEEK_POSITION,
|
|
||||||
ATTR_MEDIA_SERIES_TITLE,
|
|
||||||
ATTR_MEDIA_SHUFFLE,
|
|
||||||
ATTR_MEDIA_TITLE,
|
|
||||||
ATTR_MEDIA_TRACK,
|
|
||||||
ATTR_MEDIA_VOLUME_LEVEL,
|
|
||||||
ATTR_MEDIA_VOLUME_MUTED,
|
|
||||||
ATTR_SOUND_MODE,
|
|
||||||
ATTR_SOUND_MODE_LIST,
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_CLEAR_PLAYLIST,
|
|
||||||
SERVICE_PLAY_MEDIA,
|
|
||||||
SERVICE_SELECT_SOUND_MODE,
|
|
||||||
SERVICE_SELECT_SOURCE,
|
|
||||||
SUPPORT_PAUSE,
|
|
||||||
SUPPORT_SEEK,
|
|
||||||
SUPPORT_VOLUME_SET,
|
|
||||||
SUPPORT_VOLUME_MUTE,
|
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
|
||||||
SUPPORT_NEXT_TRACK,
|
|
||||||
SUPPORT_PLAY_MEDIA,
|
|
||||||
SUPPORT_SELECT_SOURCE,
|
|
||||||
SUPPORT_STOP,
|
|
||||||
SUPPORT_CLEAR_PLAYLIST,
|
|
||||||
SUPPORT_PLAY,
|
|
||||||
SUPPORT_SHUFFLE_SET,
|
|
||||||
SUPPORT_SELECT_SOUND_MODE,
|
|
||||||
)
|
|
||||||
from .reproduce_state import async_reproduce_states # noqa
|
from .reproduce_state import async_reproduce_states # noqa
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -197,74 +166,77 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_TURN_ON, MEDIA_PLAYER_SCHEMA,
|
SERVICE_TURN_ON, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_turn_on'
|
'async_turn_on', SUPPORT_TURN_ON
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_TURN_OFF, MEDIA_PLAYER_SCHEMA,
|
SERVICE_TURN_OFF, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_turn_off'
|
'async_turn_off', SUPPORT_TURN_OFF
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_TOGGLE, MEDIA_PLAYER_SCHEMA,
|
SERVICE_TOGGLE, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_toggle'
|
'async_toggle', SUPPORT_TURN_OFF | SUPPORT_TURN_ON
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_VOLUME_UP, MEDIA_PLAYER_SCHEMA,
|
SERVICE_VOLUME_UP, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_volume_up'
|
'async_volume_up', SUPPORT_VOLUME_SET
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_VOLUME_DOWN, MEDIA_PLAYER_SCHEMA,
|
SERVICE_VOLUME_DOWN, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_volume_down'
|
'async_volume_down', SUPPORT_VOLUME_SET
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_PLAY_PAUSE, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_PLAY_PAUSE, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_play_pause'
|
'async_media_play_pause', SUPPORT_PLAY | SUPPORT_PAUSE
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_PLAY, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_PLAY, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_play'
|
'async_media_play', SUPPORT_PLAY
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_PAUSE, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_PAUSE, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_pause'
|
'async_media_pause', SUPPORT_PAUSE
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_STOP, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_STOP, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_stop'
|
'async_media_stop', SUPPORT_STOP
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_NEXT_TRACK, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_NEXT_TRACK, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_next_track'
|
'async_media_next_track', SUPPORT_NEXT_TRACK
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_PREVIOUS_TRACK, MEDIA_PLAYER_SCHEMA,
|
SERVICE_MEDIA_PREVIOUS_TRACK, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_media_previous_track'
|
'async_media_previous_track', SUPPORT_PREVIOUS_TRACK
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_CLEAR_PLAYLIST, MEDIA_PLAYER_SCHEMA,
|
SERVICE_CLEAR_PLAYLIST, MEDIA_PLAYER_SCHEMA,
|
||||||
'async_clear_playlist'
|
'async_clear_playlist', SUPPORT_CLEAR_PLAYLIST
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_VOLUME_SET, MEDIA_PLAYER_SET_VOLUME_SCHEMA,
|
SERVICE_VOLUME_SET, MEDIA_PLAYER_SET_VOLUME_SCHEMA,
|
||||||
lambda entity, call: entity.async_set_volume_level(
|
lambda entity, call: entity.async_set_volume_level(
|
||||||
volume=call.data[ATTR_MEDIA_VOLUME_LEVEL])
|
volume=call.data[ATTR_MEDIA_VOLUME_LEVEL]),
|
||||||
|
SUPPORT_VOLUME_SET
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_VOLUME_MUTE, MEDIA_PLAYER_MUTE_VOLUME_SCHEMA,
|
SERVICE_VOLUME_MUTE, MEDIA_PLAYER_MUTE_VOLUME_SCHEMA,
|
||||||
lambda entity, call: entity.async_mute_volume(
|
lambda entity, call: entity.async_mute_volume(
|
||||||
mute=call.data[ATTR_MEDIA_VOLUME_MUTED])
|
mute=call.data[ATTR_MEDIA_VOLUME_MUTED]),
|
||||||
|
SUPPORT_VOLUME_MUTE
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_MEDIA_SEEK, MEDIA_PLAYER_MEDIA_SEEK_SCHEMA,
|
SERVICE_MEDIA_SEEK, MEDIA_PLAYER_MEDIA_SEEK_SCHEMA,
|
||||||
lambda entity, call: entity.async_media_seek(
|
lambda entity, call: entity.async_media_seek(
|
||||||
position=call.data[ATTR_MEDIA_SEEK_POSITION])
|
position=call.data[ATTR_MEDIA_SEEK_POSITION]),
|
||||||
|
SUPPORT_SEEK
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SELECT_SOURCE, MEDIA_PLAYER_SELECT_SOURCE_SCHEMA,
|
SERVICE_SELECT_SOURCE, MEDIA_PLAYER_SELECT_SOURCE_SCHEMA,
|
||||||
'async_select_source'
|
'async_select_source', SUPPORT_SELECT_SOURCE
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SELECT_SOUND_MODE, MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA,
|
SERVICE_SELECT_SOUND_MODE, MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA,
|
||||||
'async_select_sound_mode'
|
'async_select_sound_mode', SUPPORT_SELECT_SOUND_MODE
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_PLAY_MEDIA, MEDIA_PLAYER_PLAY_MEDIA_SCHEMA,
|
SERVICE_PLAY_MEDIA, MEDIA_PLAYER_PLAY_MEDIA_SCHEMA,
|
||||||
@ -272,11 +244,11 @@ async def async_setup(hass, config):
|
|||||||
media_type=call.data[ATTR_MEDIA_CONTENT_TYPE],
|
media_type=call.data[ATTR_MEDIA_CONTENT_TYPE],
|
||||||
media_id=call.data[ATTR_MEDIA_CONTENT_ID],
|
media_id=call.data[ATTR_MEDIA_CONTENT_ID],
|
||||||
enqueue=call.data.get(ATTR_MEDIA_ENQUEUE)
|
enqueue=call.data.get(ATTR_MEDIA_ENQUEUE)
|
||||||
)
|
), SUPPORT_PLAY_MEDIA
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SHUFFLE_SET, MEDIA_PLAYER_SET_SHUFFLE_SCHEMA,
|
SERVICE_SHUFFLE_SET, MEDIA_PLAYER_SET_SHUFFLE_SCHEMA,
|
||||||
'async_set_shuffle'
|
'async_set_shuffle', SUPPORT_SHUFFLE_SET
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -179,13 +179,15 @@ class EntityComponent:
|
|||||||
if entity.available and entity.entity_id in entity_ids]
|
if entity.available and entity.entity_id in entity_ids]
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_register_entity_service(self, name, schema, func):
|
def async_register_entity_service(self, name, schema, func,
|
||||||
|
required_features=None):
|
||||||
"""Register an entity service."""
|
"""Register an entity service."""
|
||||||
async def handle_service(call):
|
async def handle_service(call):
|
||||||
"""Handle the service."""
|
"""Handle the service."""
|
||||||
service_name = "{}.{}".format(self.domain, name)
|
service_name = "{}.{}".format(self.domain, name)
|
||||||
await self.hass.helpers.service.entity_service_call(
|
await self.hass.helpers.service.entity_service_call(
|
||||||
self._platforms.values(), func, call, service_name
|
self._platforms.values(), func, call, service_name,
|
||||||
|
required_features
|
||||||
)
|
)
|
||||||
|
|
||||||
self.hass.services.async_register(
|
self.hass.services.async_register(
|
||||||
|
@ -216,7 +216,8 @@ async def async_get_all_descriptions(hass):
|
|||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def entity_service_call(hass, platforms, func, call, service_name=''):
|
async def entity_service_call(hass, platforms, func, call, service_name='',
|
||||||
|
required_features=None):
|
||||||
"""Handle an entity service call.
|
"""Handle an entity service call.
|
||||||
|
|
||||||
Calls all platforms simultaneously.
|
Calls all platforms simultaneously.
|
||||||
@ -295,7 +296,8 @@ async def entity_service_call(hass, platforms, func, call, service_name=''):
|
|||||||
platforms_entities.append(platform_entities)
|
platforms_entities.append(platform_entities)
|
||||||
|
|
||||||
tasks = [
|
tasks = [
|
||||||
_handle_service_platform_call(func, data, entities, call.context)
|
_handle_service_platform_call(func, data, entities, call.context,
|
||||||
|
required_features)
|
||||||
for platform, entities in zip(platforms, platforms_entities)
|
for platform, entities in zip(platforms, platforms_entities)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -306,7 +308,8 @@ async def entity_service_call(hass, platforms, func, call, service_name=''):
|
|||||||
future.result() # pop exception if have
|
future.result() # pop exception if have
|
||||||
|
|
||||||
|
|
||||||
async def _handle_service_platform_call(func, data, entities, context):
|
async def _handle_service_platform_call(func, data, entities, context,
|
||||||
|
required_features):
|
||||||
"""Handle a function call."""
|
"""Handle a function call."""
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
@ -314,6 +317,11 @@ async def _handle_service_platform_call(func, data, entities, context):
|
|||||||
if not entity.available:
|
if not entity.available:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip entities that don't have the required feature.
|
||||||
|
if required_features is not None \
|
||||||
|
and not entity.supported_features & required_features:
|
||||||
|
continue
|
||||||
|
|
||||||
entity.async_set_context(context)
|
entity.async_set_context(context)
|
||||||
|
|
||||||
if isinstance(func, str):
|
if isinstance(func, str):
|
||||||
|
@ -184,9 +184,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
state = self.hass.states.get(ent_id)
|
state = self.hass.states.get(ent_id)
|
||||||
assert 1 == state.attributes.get('media_episode')
|
assert 1 == state.attributes.get('media_episode')
|
||||||
|
|
||||||
@patch('homeassistant.components.demo.media_player.DemoYoutubePlayer.'
|
def test_play_media(self):
|
||||||
'media_seek', autospec=True)
|
|
||||||
def test_play_media(self, mock_seek):
|
|
||||||
"""Test play_media ."""
|
"""Test play_media ."""
|
||||||
assert setup_component(
|
assert setup_component(
|
||||||
self.hass, mp.DOMAIN,
|
self.hass, mp.DOMAIN,
|
||||||
@ -212,6 +210,16 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
|||||||
state.attributes.get('supported_features'))
|
state.attributes.get('supported_features'))
|
||||||
assert 'some_id' == state.attributes.get('media_content_id')
|
assert 'some_id' == state.attributes.get('media_content_id')
|
||||||
|
|
||||||
|
@patch('homeassistant.components.demo.media_player.DemoYoutubePlayer.'
|
||||||
|
'media_seek', autospec=True)
|
||||||
|
def test_seek(self, mock_seek):
|
||||||
|
"""Test seek."""
|
||||||
|
assert setup_component(
|
||||||
|
self.hass, mp.DOMAIN,
|
||||||
|
{'media_player': {'platform': 'demo'}})
|
||||||
|
ent_id = 'media_player.living_room'
|
||||||
|
state = self.hass.states.get(ent_id)
|
||||||
|
assert state.attributes['supported_features'] & mp.SUPPORT_SEEK
|
||||||
assert not mock_seek.called
|
assert not mock_seek.called
|
||||||
with pytest.raises(vol.Invalid):
|
with pytest.raises(vol.Invalid):
|
||||||
common.media_seek(self.hass, None, ent_id)
|
common.media_seek(self.hass, None, ent_id)
|
||||||
|
@ -37,11 +37,13 @@ def mock_entities():
|
|||||||
entity_id='light.kitchen',
|
entity_id='light.kitchen',
|
||||||
available=True,
|
available=True,
|
||||||
should_poll=False,
|
should_poll=False,
|
||||||
|
supported_features=1,
|
||||||
)
|
)
|
||||||
living_room = Mock(
|
living_room = Mock(
|
||||||
entity_id='light.living_room',
|
entity_id='light.living_room',
|
||||||
available=True,
|
available=True,
|
||||||
should_poll=False,
|
should_poll=False,
|
||||||
|
supported_features=0,
|
||||||
)
|
)
|
||||||
entities = OrderedDict()
|
entities = OrderedDict()
|
||||||
entities[kitchen.entity_id] = kitchen
|
entities[kitchen.entity_id] = kitchen
|
||||||
@ -269,6 +271,19 @@ def test_async_get_all_descriptions(hass):
|
|||||||
assert 'fields' in descriptions[logger.DOMAIN]['set_level']
|
assert 'fields' in descriptions[logger.DOMAIN]['set_level']
|
||||||
|
|
||||||
|
|
||||||
|
async def test_call_with_required_features(hass, mock_entities):
|
||||||
|
"""Test service calls invoked only if entity has required feautres."""
|
||||||
|
test_service_mock = Mock(return_value=mock_coro())
|
||||||
|
await service.entity_service_call(hass, [
|
||||||
|
Mock(entities=mock_entities)
|
||||||
|
], test_service_mock, ha.ServiceCall('test_domain', 'test_service', {
|
||||||
|
'entity_id': 'all'
|
||||||
|
}), required_features=1)
|
||||||
|
assert len(mock_entities) == 2
|
||||||
|
# Called once because only one of the entities had the required features
|
||||||
|
assert test_service_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_call_context_user_not_exist(hass):
|
async def test_call_context_user_not_exist(hass):
|
||||||
"""Check we don't allow deleted users to do things."""
|
"""Check we don't allow deleted users to do things."""
|
||||||
with pytest.raises(exceptions.UnknownUser) as err:
|
with pytest.raises(exceptions.UnknownUser) as err:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user