From c56701baaf312882f33829441a9156fa0ec43e37 Mon Sep 17 00:00:00 2001 From: MartinHjelmare Date: Sun, 6 Mar 2016 04:32:28 +0100 Subject: [PATCH] Refactor reproduce_state for scene component * Add tests to reach full coverage for helpers/state.py. * Refactor reproduce_state function in helpers/state.py. Add two dicts, as global constants, service_attributes and service_to_state. Use these in combination with the dict of services per domain from ServiceRegistry, to find the correct service to use in a scene state change. * Use break statement in for loop, to break if service was selected to update state, in preference to update state attributes, ie state update takes precedence. * Add ATTR_CODE and ATTR_CODE_FORMAT in const. Import these in alarm_control_panel and lock platforms instead of making duplicate constants in multiple modules. * Use ATTR_MEDIA_CONTENT_TYPE and ATTR_MEDIA_CONTENT_ID in media_player platform in SERVICE_PLAY_MEDIA and play_media methods, instead of 'media_type' and 'media_id'. * Fix PEP257 in modified files. --- .../alarm_control_panel/__init__.py | 9 +- homeassistant/components/lock/__init__.py | 8 +- homeassistant/components/lock/verisure.py | 6 +- .../components/media_player/__init__.py | 52 +++---- .../components/media_player/universal.py | 18 +-- homeassistant/components/scene/__init__.py | 6 +- .../components/scene/homeassistant.py | 5 +- homeassistant/const.py | 4 + homeassistant/helpers/state.py | 107 +++++++++++---- tests/helpers/test_state.py | 129 ++++++++++++++++-- 10 files changed, 247 insertions(+), 97 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index a7032a73f60..f70da3d54ec 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -1,5 +1,5 @@ """ -Component to interface with a alarm control panel. +Component to interface with an alarm control panel. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/alarm_control_panel/ @@ -9,7 +9,7 @@ import os from homeassistant.components import verisure from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, + ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity @@ -32,9 +32,6 @@ SERVICE_TO_METHOD = { SERVICE_ALARM_TRIGGER: 'alarm_trigger' } -ATTR_CODE = 'code' -ATTR_CODE_FORMAT = 'code_format' - ATTR_TO_PROPERTY = [ ATTR_CODE, ATTR_CODE_FORMAT @@ -149,5 +146,5 @@ class AlarmControlPanel(Entity): """Return the state attributes.""" state_attr = { ATTR_CODE_FORMAT: self.code_format, - } + } return state_attr diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index d84abb87248..6d21ae310d8 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -13,8 +13,8 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import ( - STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, - ATTR_ENTITY_ID) + ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, + STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) from homeassistant.components import (group, verisure, wink) DOMAIN = 'lock' @@ -25,10 +25,6 @@ ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks') ENTITY_ID_FORMAT = DOMAIN + '.{}' -ATTR_LOCKED = "locked" -ATTR_CODE = 'code' -ATTR_CODE_FORMAT = 'code_format' - MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) # Maps discovered services to their platforms diff --git a/homeassistant/components/lock/verisure.py b/homeassistant/components/lock/verisure.py index 4d42aca666f..a905b6f2d19 100644 --- a/homeassistant/components/lock/verisure.py +++ b/homeassistant/components/lock/verisure.py @@ -8,10 +8,10 @@ import logging from homeassistant.components.verisure import HUB as hub from homeassistant.components.lock import LockDevice -from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED +from homeassistant.const import ( + ATTR_CODE, STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED) _LOGGER = logging.getLogger(__name__) -ATTR_CODE = 'code' def setup_platform(hass, config, add_devices, discovery_info=None): @@ -22,7 +22,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): locks.extend([ VerisureDoorlock(device_id) for device_id in hub.lock_status.keys() - ]) + ]) add_devices(locks) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index e50b578ff1b..70a29c6b919 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -108,9 +108,10 @@ ATTR_TO_PROPERTY = [ def is_on(hass, entity_id=None): - """Return true if specified media player entity_id is on. + """ + Return true if specified media player entity_id is on. - Will check all media player if no entity_id specified. + Check all media player if no entity_id specified. """ entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) return any(not hass.states.is_state(entity_id, STATE_OFF) @@ -118,19 +119,19 @@ def is_on(hass, entity_id=None): def turn_on(hass, entity_id=None): - """Will turn on specified media player or all.""" + """Turn on specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TURN_ON, data) def turn_off(hass, entity_id=None): - """Will turn off specified media player or all.""" + """Turn off specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) def toggle(hass, entity_id=None): - """Will toggle specified media player or all.""" + """Toggle specified media player or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_TOGGLE, data) @@ -148,7 +149,7 @@ def volume_down(hass, entity_id=None): def mute_volume(hass, mute, entity_id=None): - """Send the media player the command for volume down.""" + """Send the media player the command for muting the volume.""" data = {ATTR_MEDIA_VOLUME_MUTED: mute} if entity_id: @@ -158,7 +159,7 @@ def mute_volume(hass, mute, entity_id=None): def set_volume_level(hass, volume, entity_id=None): - """Send the media player the command for volume down.""" + """Send the media player the command for setting the volume.""" data = {ATTR_MEDIA_VOLUME_LEVEL: volume} if entity_id: @@ -180,7 +181,7 @@ def media_play(hass, entity_id=None): def media_pause(hass, entity_id=None): - """Send the media player the command for play/pause.""" + """Send the media player the command for pause.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data) @@ -206,7 +207,8 @@ def media_seek(hass, position, entity_id=None): def play_media(hass, media_type, media_id, entity_id=None): """Send the media player the command for playing media.""" - data = {"media_type": media_type, "media_id": media_id} + data = {ATTR_MEDIA_CONTENT_TYPE: media_type, + ATTR_MEDIA_CONTENT_ID: media_id} if entity_id: data[ATTR_ENTITY_ID] = entity_id @@ -297,8 +299,8 @@ def setup(hass, config): def play_media_service(service): """Play specified media_id on the media player.""" - media_type = service.data.get('media_type') - media_id = service.data.get('media_id') + media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) + media_id = service.data.get(ATTR_MEDIA_CONTENT_ID) if media_type is None: return @@ -320,10 +322,12 @@ def setup(hass, config): class MediaPlayerDevice(Entity): - """An abstract class for media player devices.""" + """ABC for media player devices.""" # pylint: disable=too-many-public-methods,no-self-use + # Implement these for your media player + @property def state(self): """State of the player.""" @@ -366,37 +370,37 @@ class MediaPlayerDevice(Entity): @property def media_artist(self): - """Artist of current playing media (Music track only).""" + """Artist of current playing media, music track only.""" return None @property def media_album_name(self): - """Album name of current playing media (Music track only).""" + """Album name of current playing media, music track only.""" return None @property def media_album_artist(self): - """Album artist of current playing media (Music track only).""" + """Album artist of current playing media, music track only.""" return None @property def media_track(self): - """Track number of current playing media (Music track only).""" + """Track number of current playing media, music track only.""" return None @property def media_series_title(self): - """The title of the series of current playing media (TV Show only).""" + """Title of series of current playing media, TV show only.""" return None @property def media_season(self): - """Season of current playing media (TV Show only).""" + """Season of current playing media, TV show only.""" return None @property def media_episode(self): - """Episode of current playing media (TV Show only).""" + """Episode of current playing media, TV show only.""" return None @property @@ -421,7 +425,7 @@ class MediaPlayerDevice(Entity): @property def supported_media_commands(self): - """Flag of media commands that are supported.""" + """Flag media commands that are supported.""" return 0 def turn_on(self): @@ -508,17 +512,17 @@ class MediaPlayerDevice(Entity): self.turn_off() def volume_up(self): - """volume_up media player.""" + """Turn volume up for media player.""" if self.volume_level < 1: self.set_volume_level(min(1, self.volume_level + .1)) def volume_down(self): - """volume_down media player.""" + """Turn volume down for media player.""" if self.volume_level > 0: self.set_volume_level(max(0, self.volume_level - .1)) def media_play_pause(self): - """media_play_pause media player.""" + """Play or pause the media player.""" if self.state == STATE_PLAYING: self.media_pause() else: @@ -526,7 +530,7 @@ class MediaPlayerDevice(Entity): @property def entity_picture(self): - """Return the image of the media playing.""" + """Return image of the media playing.""" return None if self.state == STATE_OFF else self.media_image_url @property diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 8c981c42e0d..0c37a372328 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -43,7 +43,6 @@ REQUIREMENTS = [] _LOGGER = logging.getLogger(__name__) -# pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the universal media players.""" if not validate_config(config): @@ -200,7 +199,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def master_state(self): - """Get the master state from entity or none.""" + """Return the master state for entity or None.""" if CONF_STATE in self._attrs: master_state = self._entity_lkp(self._attrs[CONF_STATE][0], self._attrs[CONF_STATE][1]) @@ -324,7 +323,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def supported_media_commands(self): - """Flag of media commands that are supported.""" + """Flag media commands that are supported.""" flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0 if SERVICE_TURN_ON in self._cmds: @@ -345,7 +344,7 @@ class UniversalMediaPlayer(MediaPlayerDevice): @property def device_state_attributes(self): - """Extra attributes a device wants to expose.""" + """Return device specific state attributes.""" active_child = self._child_state return {ATTR_ACTIVE_CHILD: active_child.entity_id} \ if active_child else {} @@ -391,23 +390,24 @@ class UniversalMediaPlayer(MediaPlayerDevice): def play_media(self, media_type, media_id): """Play a piece of media.""" - data = {'media_type': media_type, 'media_id': media_id} + data = {ATTR_MEDIA_CONTENT_TYPE: media_type, + ATTR_MEDIA_CONTENT_ID: media_id} self._call_service(SERVICE_PLAY_MEDIA, data) def volume_up(self): - """Volume up media player.""" + """Turn volume up for media player.""" self._call_service(SERVICE_VOLUME_UP, allow_override=True) def volume_down(self): - """Volume down media player.""" + """Turn volume down for media player.""" self._call_service(SERVICE_VOLUME_DOWN, allow_override=True) def media_play_pause(self): - """Send play/pause command media player.""" + """Play or pause the media player.""" self._call_service(SERVICE_MEDIA_PLAY_PAUSE) def update(self): - """Event to trigger a state update.""" + """Update state in HA.""" for child_name in self._children: child_state = self.hass.states.get(child_name) if child_state and child_state.state not in OFF_STATES: diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index c67a522d9d2..03bb7c265c3 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -33,7 +33,7 @@ def activate(hass, entity_id=None): def setup(hass, config): - """Setup the scenes.""" + """Setup scenes.""" logger = logging.getLogger(__name__) # You are not allowed to mutate the original config so make a copy @@ -76,9 +76,9 @@ class Scene(Entity): @property def state(self): - """Return the state.""" + """Return the state of the scene.""" return STATE def activate(self): - """Activate scene. Tries to get entities into requested state.""" + """Activate scene. Try to get entities into requested state.""" raise NotImplementedError diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py index e0ccad08a76..e507c664bef 100644 --- a/homeassistant/components/scene/homeassistant.py +++ b/homeassistant/components/scene/homeassistant.py @@ -1,5 +1,5 @@ """ -Allows users to set and activate scenes. +Allow users to set and activate scenes. For more details about this component, please refer to the documentation at https://home-assistant.io/components/scene/ @@ -20,7 +20,6 @@ CONF_ENTITIES = "entities" SceneConfig = namedtuple('SceneConfig', ['name', 'states']) -# pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup home assistant scene entries.""" scene_config = config.get("states") @@ -83,5 +82,5 @@ class HomeAssistantScene(Scene): } def activate(self): - """Activate scene. Tries to get entities into requested state.""" + """Activate scene. Try to get entities into requested state.""" reproduce_state(self.hass, self.scene_config.states.values(), True) diff --git a/homeassistant/const.py b/homeassistant/const.py index 88987e25a2d..6ba23b6ec17 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -102,6 +102,10 @@ ATTR_LOCATION = "location" ATTR_BATTERY_LEVEL = "battery_level" +# For devices which support a code attribute +ATTR_CODE = 'code' +ATTR_CODE_FORMAT = 'code_format' + # For devices which support an armed state ATTR_ARMED = "device_armed" diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 2abe8e1555e..8d9f62b644d 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -4,24 +4,73 @@ import logging from collections import defaultdict import homeassistant.util.dt as dt_util -from homeassistant.components.media_player import SERVICE_PLAY_MEDIA +from homeassistant.components.media_player import ( + ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_SEEK_POSITION, + ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, SERVICE_PLAY_MEDIA) +from homeassistant.components.notify import ( + ATTR_MESSAGE, SERVICE_NOTIFY) from homeassistant.components.sun import ( STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) +from homeassistant.components.thermostat import ( + ATTR_AWAY_MODE, ATTR_FAN, SERVICE_SET_AWAY_MODE, SERVICE_SET_FAN_MODE, + SERVICE_SET_TEMPERATURE) from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_TURN_OFF, - SERVICE_TURN_ON, STATE_CLOSED, STATE_LOCKED, STATE_OFF, STATE_ON, - STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_UNLOCKED) + ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER, + SERVICE_CLOSE, SERVICE_LOCK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, + SERVICE_MEDIA_SEEK, SERVICE_MOVE_DOWN, SERVICE_MOVE_UP, SERVICE_OPEN, + SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, SERVICE_VOLUME_MUTE, + SERVICE_VOLUME_SET, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_CLOSED, STATE_LOCKED, + STATE_OFF, STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING, + STATE_UNKNOWN, STATE_UNLOCKED) from homeassistant.core import State _LOGGER = logging.getLogger(__name__) +GROUP_DOMAIN = 'group' +HASS_DOMAIN = 'homeassistant' + +# Update this dict of lists when new services are added to HA. +# Each item is a service with a list of required attributes. +SERVICE_ATTRIBUTES = { + SERVICE_PLAY_MEDIA: [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID], + SERVICE_MEDIA_SEEK: [ATTR_MEDIA_SEEK_POSITION], + SERVICE_VOLUME_MUTE: [ATTR_MEDIA_VOLUME_MUTED], + SERVICE_VOLUME_SET: [ATTR_MEDIA_VOLUME_LEVEL], + SERVICE_NOTIFY: [ATTR_MESSAGE], + SERVICE_SET_AWAY_MODE: [ATTR_AWAY_MODE], + SERVICE_SET_FAN_MODE: [ATTR_FAN], + SERVICE_SET_TEMPERATURE: [ATTR_TEMPERATURE], +} + +# Update this dict when new services are added to HA. +# Each item is a service with a corresponding state. +SERVICE_TO_STATE = { + SERVICE_TURN_ON: STATE_ON, + SERVICE_TURN_OFF: STATE_OFF, + SERVICE_MEDIA_PLAY: STATE_PLAYING, + SERVICE_MEDIA_PAUSE: STATE_PAUSED, + SERVICE_ALARM_ARM_AWAY: STATE_ALARM_ARMED_AWAY, + SERVICE_ALARM_ARM_HOME: STATE_ALARM_ARMED_HOME, + SERVICE_ALARM_DISARM: STATE_ALARM_DISARMED, + SERVICE_ALARM_TRIGGER: STATE_ALARM_TRIGGERED, + SERVICE_LOCK: STATE_LOCKED, + SERVICE_UNLOCK: STATE_UNLOCKED, + SERVICE_CLOSE: STATE_CLOSED, + SERVICE_OPEN: STATE_OPEN, + SERVICE_MOVE_UP: STATE_OPEN, + SERVICE_MOVE_DOWN: STATE_CLOSED, +} + # pylint: disable=too-few-public-methods, attribute-defined-outside-init class TrackStates(object): - """Record the time when the with-block is entered. + """ + Record the time when the with-block is entered. - Will add all states that have changed since the start time to the return - list when with-block is exited. + Add all states that have changed since the start time to the return list + when with-block is exited. """ def __init__(self, hass): @@ -40,7 +89,7 @@ class TrackStates(object): def get_changed_since(states, utc_point_in_time): - """List of states that have been changed since utc_point_in_time.""" + """Return list of states that have been changed since utc_point_in_time.""" point_in_time = dt_util.strip_microseconds(utc_point_in_time) return [state for state in states if state.last_updated >= point_in_time] @@ -54,35 +103,36 @@ def reproduce_state(hass, states, blocking=False): to_call = defaultdict(list) for state in states: - current_state = hass.states.get(state.entity_id) - if current_state is None: + if hass.states.get(state.entity_id) is None: _LOGGER.warning('reproduce_state: Unable to find entity %s', state.entity_id) continue - if state.domain == 'media_player' and state.attributes and \ - 'media_type' in state.attributes and \ - 'media_id' in state.attributes: - service = SERVICE_PLAY_MEDIA - elif state.domain == 'media_player' and state.state == STATE_PAUSED: - service = SERVICE_MEDIA_PAUSE - elif state.domain == 'media_player' and state.state == STATE_PLAYING: - service = SERVICE_MEDIA_PLAY - elif state.state == STATE_ON: - service = SERVICE_TURN_ON - elif state.state == STATE_OFF: - service = SERVICE_TURN_OFF + if state.domain == GROUP_DOMAIN: + service_domain = HASS_DOMAIN else: + service_domain = state.domain + + domain_services = hass.services.services[service_domain] + + service = None + for _service in domain_services.keys(): + if (_service in SERVICE_ATTRIBUTES and + all(attr in state.attributes + for attr in SERVICE_ATTRIBUTES[_service]) or + _service in SERVICE_TO_STATE and + SERVICE_TO_STATE[_service] == state.state): + service = _service + if (_service in SERVICE_TO_STATE and + SERVICE_TO_STATE[_service] == state.state): + break + + if not service: _LOGGER.warning("reproduce_state: Unable to reproduce state %s", state) continue - if state.domain == 'group': - service_domain = 'homeassistant' - else: - service_domain = state.domain - # We group service calls for entities by service call # json used to create a hashable version of dict with maybe lists in it key = (service_domain, service, @@ -96,7 +146,8 @@ def reproduce_state(hass, states, blocking=False): def state_as_number(state): - """Try to coerce our state to a number. + """ + Try to coerce our state to a number. Raises ValueError if this is not possible. """ diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index d84f82c1be6..5b374866d61 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -5,13 +5,15 @@ from unittest.mock import patch import homeassistant.core as ha import homeassistant.components as core_components -from homeassistant.const import SERVICE_TURN_ON +from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TURN_OFF) from homeassistant.util import dt as dt_util from homeassistant.helpers import state from homeassistant.const import ( STATE_OPEN, STATE_CLOSED, STATE_LOCKED, STATE_UNLOCKED, STATE_ON, STATE_OFF) +from homeassistant.components.media_player import ( + SERVICE_PLAY_MEDIA, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE) from homeassistant.components.sun import (STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) @@ -22,16 +24,16 @@ class TestStateHelpers(unittest.TestCase): """Test the Home Assistant event helpers.""" def setUp(self): # pylint: disable=invalid-name - """Setup things to be run when tests are started.""" + """Run when tests are started.""" self.hass = get_test_home_assistant() core_components.setup(self.hass, {}) def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" + """Stop when tests are finished.""" self.hass.stop() def test_get_changed_since(self): - """Test for changes since.""" + """Test get_changed_since.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=5) point3 = point2 + timedelta(seconds=5) @@ -77,8 +79,19 @@ class TestStateHelpers(unittest.TestCase): sorted([state2, state3], key=lambda state: state.entity_id), sorted(states, key=lambda state: state.entity_id)) - def test_reproduce_state_with_turn_on(self): - """Test reproduction of state with turn_on.""" + def test_reproduce_with_no_entity(self): + """Test reproduce_state with no entity.""" + calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) + + state.reproduce_state(self.hass, ha.State('light.test', 'on')) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) == 0) + self.assertEqual(None, self.hass.states.get('light.test')) + + def test_reproduce_turn_on(self): + """Test reproduce_state with SERVICE_TURN_ON.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) self.hass.states.set('light.test', 'off') @@ -93,8 +106,24 @@ class TestStateHelpers(unittest.TestCase): self.assertEqual(SERVICE_TURN_ON, last_call.service) self.assertEqual(['light.test'], last_call.data.get('entity_id')) - def test_reproduce_state_with_complex_service_data(self): - """Test reproduction of state with complex service data.""" + def test_reproduce_turn_off(self): + """Test reproduce_state with SERVICE_TURN_OFF.""" + calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF) + + self.hass.states.set('light.test', 'on') + + state.reproduce_state(self.hass, ha.State('light.test', 'off')) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) > 0) + last_call = calls[-1] + self.assertEqual('light', last_call.domain) + self.assertEqual(SERVICE_TURN_OFF, last_call.service) + self.assertEqual(['light.test'], last_call.data.get('entity_id')) + + def test_reproduce_complex_data(self): + """Test reproduce_state with complex service data.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) self.hass.states.set('light.test', 'off') @@ -113,8 +142,78 @@ class TestStateHelpers(unittest.TestCase): self.assertEqual(SERVICE_TURN_ON, last_call.service) self.assertEqual(complex_data, last_call.data.get('complex')) - def test_reproduce_state_with_group(self): - """Test reproduction of state with group.""" + def test_reproduce_media_data(self): + """Test reproduce_state with SERVICE_PLAY_MEDIA.""" + calls = mock_service(self.hass, 'media_player', SERVICE_PLAY_MEDIA) + + self.hass.states.set('media_player.test', 'off') + + media_attributes = {'media_content_type': 'movie', + 'media_content_id': 'batman'} + + state.reproduce_state(self.hass, ha.State('media_player.test', 'None', + media_attributes)) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) > 0) + last_call = calls[-1] + self.assertEqual('media_player', last_call.domain) + self.assertEqual(SERVICE_PLAY_MEDIA, last_call.service) + self.assertEqual('movie', last_call.data.get('media_content_type')) + self.assertEqual('batman', last_call.data.get('media_content_id')) + + def test_reproduce_media_play(self): + """Test reproduce_state with SERVICE_MEDIA_PLAY.""" + calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PLAY) + + self.hass.states.set('media_player.test', 'off') + + state.reproduce_state( + self.hass, ha.State('media_player.test', 'playing')) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) > 0) + last_call = calls[-1] + self.assertEqual('media_player', last_call.domain) + self.assertEqual(SERVICE_MEDIA_PLAY, last_call.service) + self.assertEqual(['media_player.test'], + last_call.data.get('entity_id')) + + def test_reproduce_media_pause(self): + """Test reproduce_state with SERVICE_MEDIA_PAUSE.""" + calls = mock_service(self.hass, 'media_player', SERVICE_MEDIA_PAUSE) + + self.hass.states.set('media_player.test', 'playing') + + state.reproduce_state( + self.hass, ha.State('media_player.test', 'paused')) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) > 0) + last_call = calls[-1] + self.assertEqual('media_player', last_call.domain) + self.assertEqual(SERVICE_MEDIA_PAUSE, last_call.service) + self.assertEqual(['media_player.test'], + last_call.data.get('entity_id')) + + def test_reproduce_bad_state(self): + """Test reproduce_state with bad state.""" + calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) + + self.hass.states.set('light.test', 'off') + + state.reproduce_state(self.hass, ha.State('light.test', 'bad')) + + self.hass.pool.block_till_done() + + self.assertTrue(len(calls) == 0) + self.assertEqual('off', self.hass.states.get('light.test').state) + + def test_reproduce_group(self): + """Test reproduce_state with group.""" light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) self.hass.states.set('group.test', 'off', { @@ -131,8 +230,8 @@ class TestStateHelpers(unittest.TestCase): self.assertEqual(['light.test1', 'light.test2'], last_call.data.get('entity_id')) - def test_reproduce_state_group_states_with_same_domain_and_data(self): - """Test reproduction of state with the dame domain.""" + def test_reproduce_group_same_data(self): + """Test reproduce_state with group with same domain and data.""" light_calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) self.hass.states.set('light.test1', 'off') @@ -153,7 +252,7 @@ class TestStateHelpers(unittest.TestCase): self.assertEqual(95, last_call.data.get('brightness')) def test_as_number_states(self): - """Test number as states.""" + """Test state_as_number with states.""" zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED, STATE_BELOW_HORIZON) one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON) @@ -165,7 +264,7 @@ class TestStateHelpers(unittest.TestCase): ha.State('domain.test', _state, {}))) def test_as_number_coercion(self): - """Test numbers.""" + """Test state_as_number with number.""" for _state in ('0', '0.0', 0, 0.0): self.assertEqual( 0.0, state.state_as_number( @@ -176,7 +275,7 @@ class TestStateHelpers(unittest.TestCase): ha.State('domain.test', _state, {}))) def test_as_number_invalid_cases(self): - """.""" + """Test state_as_number with invalid cases.""" for _state in ('', 'foo', 'foo.bar', None, False, True, object, object()): self.assertRaises(ValueError,