From 0524c51c1a5e2afe1874801c93e01448395a5558 Mon Sep 17 00:00:00 2001 From: Matt Snyder Date: Mon, 22 Oct 2018 00:04:47 -0500 Subject: [PATCH 001/230] Update flux library version (#17677) --- homeassistant/components/light/flux_led.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index f389d34cd5d..cab6957c265 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -18,7 +18,7 @@ from homeassistant.components.light import ( import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util -REQUIREMENTS = ['flux_led==0.21'] +REQUIREMENTS = ['flux_led==0.22'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 84b8b2d57ce..f3f5fb4cd6c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -376,7 +376,7 @@ fitbit==0.3.0 fixerio==1.0.0a0 # homeassistant.components.light.flux_led -flux_led==0.21 +flux_led==0.22 # homeassistant.components.sensor.foobot foobot_async==0.3.1 From 96105ef6e74e57fd7c02582018639bcdec66a111 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 22 Oct 2018 14:45:13 +0200 Subject: [PATCH 002/230] Add lovelace websocket get and set card (#17600) * Add ws get, set card * lint+fix test * Add test for set * Added more tests, catch unsupported yaml constructors Like !include will now give an error in the frontend. * lint --- homeassistant/components/lovelace/__init__.py | 187 +++++++++++++++++- tests/components/lovelace/test_init.py | 174 +++++++++++++++- 2 files changed, 348 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index e3f4522580b..2c28b52ec6e 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -2,9 +2,10 @@ import logging import uuid import os -from os import O_WRONLY, O_CREAT, O_TRUNC +from os import O_CREAT, O_TRUNC, O_WRONLY from collections import OrderedDict -from typing import Union, List, Dict +from typing import Dict, List, Union + import voluptuous as vol from homeassistant.components import websocket_api @@ -14,21 +15,45 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'lovelace' REQUIREMENTS = ['ruamel.yaml==0.15.72'] +LOVELACE_CONFIG_FILE = 'ui-lovelace.yaml' +JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name + OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' +WS_TYPE_GET_CARD = 'lovelace/config/card/get' +WS_TYPE_SET_CARD = 'lovelace/config/card/set' SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, OLD_WS_TYPE_GET_LOVELACE_UI), }) -JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name +SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_GET_CARD, + vol.Required('card_id'): str, + vol.Optional('format', default='yaml'): str, +}) + +SCHEMA_SET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_SET_CARD, + vol.Required('card_id'): str, + vol.Required('card_config'): vol.Any(str, Dict), + vol.Optional('format', default='yaml'): str, +}) class WriteError(HomeAssistantError): """Error writing the data.""" +class CardNotFoundError(HomeAssistantError): + """Card not found in data.""" + + +class UnsupportedYamlError(HomeAssistantError): + """Unsupported YAML.""" + + def save_yaml(fname: str, data: JSON_TYPE): """Save a YAML file.""" from ruamel.yaml import YAML @@ -45,7 +70,7 @@ def save_yaml(fname: str, data: JSON_TYPE): _LOGGER.error(str(exc)) raise HomeAssistantError(exc) except OSError as exc: - _LOGGER.exception('Saving YAML file failed: %s', fname) + _LOGGER.exception('Saving YAML file %s failed: %s', fname, exc) raise WriteError(exc) finally: if os.path.exists(tmp_fname): @@ -57,18 +82,29 @@ def save_yaml(fname: str, data: JSON_TYPE): _LOGGER.error("YAML replacement cleanup failed: %s", exc) +def _yaml_unsupported(loader, node): + raise UnsupportedYamlError( + 'Unsupported YAML, you can not use {} in ui-lovelace.yaml' + .format(node.tag)) + + def load_yaml(fname: str) -> JSON_TYPE: """Load a YAML file.""" from ruamel.yaml import YAML + from ruamel.yaml.constructor import RoundTripConstructor from ruamel.yaml.error import YAMLError + + RoundTripConstructor.add_constructor(None, _yaml_unsupported) + yaml = YAML(typ='rt') + try: with open(fname, encoding='utf-8') as conf_file: # If configuration file is empty YAML returns None # We convert that to an empty dict return yaml.load(conf_file) or OrderedDict() except YAMLError as exc: - _LOGGER.error("YAML error: %s", exc) + _LOGGER.error("YAML error in %s: %s", fname, exc) raise HomeAssistantError(exc) except UnicodeDecodeError as exc: _LOGGER.error("Unable to read file %s: %s", fname, exc) @@ -76,21 +112,86 @@ def load_yaml(fname: str) -> JSON_TYPE: def load_config(fname: str) -> JSON_TYPE: - """Load a YAML file and adds id to card if not present.""" + """Load a YAML file and adds id to views and cards if not present.""" config = load_yaml(fname) - # Check if all cards have an ID or else add one + # Check if all views and cards have an id or else add one updated = False + index = 0 for view in config.get('views', []): + if 'id' not in view: + updated = True + view.insert(0, 'id', index, + comment="Automatically created id") for card in view.get('cards', []): if 'id' not in card: updated = True - card['id'] = uuid.uuid4().hex - card.move_to_end('id', last=False) + card.insert(0, 'id', uuid.uuid4().hex, + comment="Automatically created id") + index += 1 if updated: save_yaml(fname, config) return config +def object_to_yaml(data: JSON_TYPE) -> str: + """Create yaml string from object.""" + from ruamel.yaml import YAML + from ruamel.yaml.error import YAMLError + from ruamel.yaml.compat import StringIO + yaml = YAML(typ='rt') + yaml.indent(sequence=4, offset=2) + stream = StringIO() + try: + yaml.dump(data, stream) + return stream.getvalue() + except YAMLError as exc: + _LOGGER.error("YAML error: %s", exc) + raise HomeAssistantError(exc) + + +def yaml_to_object(data: str) -> JSON_TYPE: + """Create object from yaml string.""" + from ruamel.yaml import YAML + from ruamel.yaml.error import YAMLError + yaml = YAML(typ='rt') + try: + return yaml.load(data) + except YAMLError as exc: + _LOGGER.error("YAML error: %s", exc) + raise HomeAssistantError(exc) + + +def get_card(fname: str, card_id: str, data_format: str) -> JSON_TYPE: + """Load a specific card config for id.""" + config = load_yaml(fname) + for view in config.get('views', []): + for card in view.get('cards', []): + if card.get('id') == card_id: + if data_format == 'yaml': + return object_to_yaml(card) + return card + + raise CardNotFoundError( + "Card with ID: {} was not found in {}.".format(card_id, fname)) + + +def set_card(fname: str, card_id: str, card_config: str, data_format: str)\ + -> bool: + """Save a specific card config for id.""" + config = load_yaml(fname) + for view in config.get('views', []): + for card in view.get('cards', []): + if card.get('id') == card_id: + if data_format == 'yaml': + card_config = yaml_to_object(card_config) + card.update(card_config) + save_yaml(fname, config) + return True + + raise CardNotFoundError( + "Card with ID: {} was not found in {}.".format(card_id, fname)) + + async def async_setup(hass, config): """Set up the Lovelace commands.""" # Backwards compat. Added in 0.80. Remove after 0.85 @@ -102,6 +203,14 @@ async def async_setup(hass, config): WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) + hass.components.websocket_api.async_register_command( + WS_TYPE_GET_CARD, websocket_lovelace_get_card, + SCHEMA_GET_CARD) + + hass.components.websocket_api.async_register_command( + WS_TYPE_SET_CARD, websocket_lovelace_set_card, + SCHEMA_SET_CARD) + return True @@ -111,13 +220,15 @@ async def websocket_lovelace_config(hass, connection, msg): error = None try: config = await hass.async_add_executor_job( - load_config, hass.config.path('ui-lovelace.yaml')) + load_config, hass.config.path(LOVELACE_CONFIG_FILE)) message = websocket_api.result_message( msg['id'], config ) except FileNotFoundError: error = ('file_not_found', 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) except HomeAssistantError as err: error = 'load_error', str(err) @@ -125,3 +236,59 @@ async def websocket_lovelace_config(hass, connection, msg): message = websocket_api.error_message(msg['id'], *error) connection.send_message(message) + + +@websocket_api.async_response +async def websocket_lovelace_get_card(hass, connection, msg): + """Send lovelace card config over websocket config.""" + error = None + try: + card = await hass.async_add_executor_job( + get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], + msg.get('format', 'yaml')) + message = websocket_api.result_message( + msg['id'], card + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except CardNotFoundError: + error = ('card_not_found', + 'Could not find card in ui-lovelace.yaml.') + except HomeAssistantError as err: + error = 'load_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) + + +@websocket_api.async_response +async def websocket_lovelace_set_card(hass, connection, msg): + """Receive lovelace card config over websocket and save.""" + error = None + try: + result = await hass.async_add_executor_job( + set_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg['card_config'], msg.get('format', 'yaml')) + message = websocket_api.result_message( + msg['id'], result + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except CardNotFoundError: + error = ('card_not_found', + 'Could not find card in ui-lovelace.yaml.') + except HomeAssistantError as err: + error = 'save_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 5e4cf2d8037..c637267cc7e 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -9,7 +9,8 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.lovelace import (load_yaml, - save_yaml, load_config) + save_yaml, load_config, + UnsupportedYamlError) TEST_YAML_A = """\ title: My Awesome Home @@ -55,6 +56,8 @@ views: # Title of the view. Will be used as the tooltip for tab icon title: Second view cards: + - id: test + type: entities # Entities card will take a list of entities and show their state. - type: entities # Title of the entities card @@ -79,6 +82,7 @@ TEST_YAML_B = """\ title: Home views: - title: Dashboard + id: dashboard icon: mdi:home cards: - id: testid @@ -102,6 +106,15 @@ views: type: vertical-stack """ +# Test unsupported YAML +TEST_UNSUP_YAML = """\ +title: Home +views: + - title: Dashboard + icon: mdi:home + cards: !include cards.yaml +""" + class TestYAML(unittest.TestCase): """Test lovelace.yaml save and load.""" @@ -147,9 +160,11 @@ class TestYAML(unittest.TestCase): """Test if id is added.""" fname = self._path_for("test6") with patch('homeassistant.components.lovelace.load_yaml', - return_value=self.yaml.load(TEST_YAML_A)): + return_value=self.yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml'): data = load_config(fname) assert 'id' in data['views'][0]['cards'][0] + assert 'id' in data['views'][1] def test_id_not_changed(self): """Test if id is not changed if already exists.""" @@ -256,7 +271,7 @@ async def test_lovelace_ui_not_found(hass, hass_ws_client): async def test_lovelace_ui_load_err(hass, hass_ws_client): - """Test lovelace_ui command cannot find file.""" + """Test lovelace_ui command load error.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) @@ -272,3 +287,156 @@ async def test_lovelace_ui_load_err(hass, hass_ws_client): assert msg['type'] == TYPE_RESULT assert msg['success'] is False assert msg['error']['code'] == 'load_error' + + +async def test_lovelace_ui_load_json_err(hass, hass_ws_client): + """Test lovelace_ui command load error.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + + with patch('homeassistant.components.lovelace.load_config', + side_effect=UnsupportedYamlError): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'unsupported_error' + + +async def test_lovelace_get_card(hass, hass_ws_client): + """Test get_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/get', + 'card_id': 'test', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + assert msg['result'] == 'id: test\ntype: entities\n' + + +async def test_lovelace_get_card_not_found(hass, hass_ws_client): + """Test get_card command cannot find card.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/get', + 'card_id': 'not_found', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'card_not_found' + + +async def test_lovelace_get_card_bad_yaml(hass, hass_ws_client): + """Test get_card command bad yaml.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + + with patch('homeassistant.components.lovelace.load_yaml', + side_effect=HomeAssistantError): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/get', + 'card_id': 'testid', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'load_error' + + +async def test_lovelace_set_card(hass, hass_ws_client): + """Test set_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/set', + 'card_id': 'test', + 'card_config': 'id: test\ntype: glance\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 1, 'cards', 0, 'type'], + list_ok=True) == 'glance' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_set_card_not_found(hass, hass_ws_client): + """Test set_card command cannot find card.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/set', + 'card_id': 'not_found', + 'card_config': 'id: test\ntype: glance\n', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'card_not_found' + + +async def test_lovelace_set_card_bad_yaml(hass, hass_ws_client): + """Test set_card command bad yaml.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.yaml_to_object', + side_effect=HomeAssistantError): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/set', + 'card_id': 'test', + 'card_config': 'id: test\ntype: glance\n', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'save_error' From 61a96aecc015f54f28d46a330b28b03820c1c440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Mon, 22 Oct 2018 19:32:19 +0200 Subject: [PATCH 003/230] Mill, support more heater types (#17676) * mill, suport more heater types * mill requirements --- homeassistant/components/climate/mill.py | 3 ++- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/climate/mill.py index 763e239689b..11ad83bdbcc 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -17,7 +17,7 @@ from homeassistant.const import ( from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -REQUIREMENTS = ['millheater==0.1.2'] +REQUIREMENTS = ['millheater==0.2.0'] _LOGGER = logging.getLogger(__name__) @@ -43,6 +43,7 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Failed to connect to Mill") return + await mill_data_connection.update_rooms() await mill_data_connection.update_heaters() dev = [] diff --git a/requirements_all.txt b/requirements_all.txt index f3f5fb4cd6c..095aa107e1c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -606,7 +606,7 @@ mficlient==0.3.0 miflora==0.4.0 # homeassistant.components.climate.mill -millheater==0.1.2 +millheater==0.2.0 # homeassistant.components.sensor.mitemp_bt mitemp_bt==0.0.1 From 42a444712bffcf452c559f6c8e5f13aa23c07fa2 Mon Sep 17 00:00:00 2001 From: Tommy Jonsson Date: Mon, 22 Oct 2018 19:36:29 +0200 Subject: [PATCH 004/230] Add missing hangouts data/image to notify service (#17576) * add missing hangouts image_file/url to notify services Missed adding support for hangouts image to notify service * default in schema --- homeassistant/components/notify/hangouts.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/notify/hangouts.py b/homeassistant/components/notify/hangouts.py index eb2880e8a46..01f98146f4c 100644 --- a/homeassistant/components/notify/hangouts.py +++ b/homeassistant/components/notify/hangouts.py @@ -11,10 +11,10 @@ import voluptuous as vol from homeassistant.components.notify import (ATTR_TARGET, PLATFORM_SCHEMA, NOTIFY_SERVICE_SCHEMA, BaseNotificationService, - ATTR_MESSAGE) + ATTR_MESSAGE, ATTR_DATA) from homeassistant.components.hangouts.const \ - import (DOMAIN, SERVICE_SEND_MESSAGE, + import (DOMAIN, SERVICE_SEND_MESSAGE, MESSAGE_DATA_SCHEMA, TARGETS_SCHEMA, CONF_DEFAULT_CONVERSATIONS) _LOGGER = logging.getLogger(__name__) @@ -26,7 +26,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) NOTIFY_SERVICE_SCHEMA = NOTIFY_SERVICE_SCHEMA.extend({ - vol.Optional(ATTR_TARGET): [TARGETS_SCHEMA] + vol.Optional(ATTR_TARGET): [TARGETS_SCHEMA], + vol.Optional(ATTR_DATA, default={}): MESSAGE_DATA_SCHEMA }) @@ -59,7 +60,8 @@ class HangoutsNotificationService(BaseNotificationService): messages.append({'text': message, 'parse_str': True}) service_data = { ATTR_TARGET: target_conversations, - ATTR_MESSAGE: messages + ATTR_MESSAGE: messages, + ATTR_DATA: kwargs[ATTR_DATA] } return self.hass.services.call( From 75e42acfe752ea0d5d3edf80ff4a51ca9fa79441 Mon Sep 17 00:00:00 2001 From: Malte Franken Date: Tue, 23 Oct 2018 05:01:01 +1100 Subject: [PATCH 005/230] Geo location trigger added (#16967) * zone trigger supports entity id pattern * fixed lint error * fixed test code * initial version of new geo_location trigger * revert to original * simplified code and added tests * refactored geo_location trigger to be based on a source defined by the entity * amended test cases * small refactorings --- .../components/automation/geo_location.py | 74 +++++ homeassistant/const.py | 1 + .../automation/test_geo_location.py | 271 ++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 homeassistant/components/automation/geo_location.py create mode 100644 tests/components/automation/test_geo_location.py diff --git a/homeassistant/components/automation/geo_location.py b/homeassistant/components/automation/geo_location.py new file mode 100644 index 00000000000..b2c9a9c093a --- /dev/null +++ b/homeassistant/components/automation/geo_location.py @@ -0,0 +1,74 @@ +""" +Offer geo location automation rules. + +For more details about this automation trigger, please refer to the +documentation at +https://home-assistant.io/docs/automation/trigger/#geo-location-trigger +""" +import voluptuous as vol + +from homeassistant.components.geo_location import DOMAIN +from homeassistant.core import callback +from homeassistant.const import ( + CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE, EVENT_STATE_CHANGED) +from homeassistant.helpers import ( + condition, config_validation as cv) +from homeassistant.helpers.config_validation import entity_domain + +EVENT_ENTER = 'enter' +EVENT_LEAVE = 'leave' +DEFAULT_EVENT = EVENT_ENTER + +TRIGGER_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'geo_location', + vol.Required(CONF_SOURCE): cv.string, + vol.Required(CONF_ZONE): entity_domain('zone'), + vol.Required(CONF_EVENT, default=DEFAULT_EVENT): + vol.Any(EVENT_ENTER, EVENT_LEAVE), +}) + + +def source_match(state, source): + """Check if the state matches the provided source.""" + return state and state.attributes.get('source') == source + + +async def async_trigger(hass, config, action): + """Listen for state changes based on configuration.""" + source = config.get(CONF_SOURCE).lower() + zone_entity_id = config.get(CONF_ZONE) + trigger_event = config.get(CONF_EVENT) + + @callback + def state_change_listener(event): + """Handle specific state changes.""" + # Skip if the event is not a geo_location entity. + if not event.data.get('entity_id').startswith(DOMAIN): + return + # Skip if the event's source does not match the trigger's source. + from_state = event.data.get('old_state') + to_state = event.data.get('new_state') + if not source_match(from_state, source) \ + and not source_match(to_state, source): + return + + zone_state = hass.states.get(zone_entity_id) + from_match = condition.zone(hass, zone_state, from_state) + to_match = condition.zone(hass, zone_state, to_state) + + # pylint: disable=too-many-boolean-expressions + if trigger_event == EVENT_ENTER and not from_match and to_match or \ + trigger_event == EVENT_LEAVE and from_match and not to_match: + hass.async_run_job(action({ + 'trigger': { + 'platform': 'geo_location', + 'source': source, + 'entity_id': event.data.get('entity_id'), + 'from_state': from_state, + 'to_state': to_state, + 'zone': zone_state, + 'event': trigger_event, + }, + }, context=event.context)) + + return hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4dcc171d35c..b5ca708f1b2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -129,6 +129,7 @@ CONF_SENSOR_TYPE = 'sensor_type' CONF_SENSORS = 'sensors' CONF_SHOW_ON_MAP = 'show_on_map' CONF_SLAVE = 'slave' +CONF_SOURCE = 'source' CONF_SSL = 'ssl' CONF_STATE = 'state' CONF_STATE_TEMPLATE = 'state_template' diff --git a/tests/components/automation/test_geo_location.py b/tests/components/automation/test_geo_location.py new file mode 100644 index 00000000000..130cdeef99c --- /dev/null +++ b/tests/components/automation/test_geo_location.py @@ -0,0 +1,271 @@ +"""The tests for the geo location trigger.""" +import unittest + +from homeassistant.components import automation, zone +from homeassistant.core import callback, Context +from homeassistant.setup import setup_component + +from tests.common import get_test_home_assistant, mock_component +from tests.components.automation import common + + +class TestAutomationGeoLocation(unittest.TestCase): + """Test the geo location trigger.""" + + def setUp(self): + """Set up things to be run when tests are started.""" + self.hass = get_test_home_assistant() + mock_component(self.hass, 'group') + assert setup_component(self.hass, zone.DOMAIN, { + 'zone': { + 'name': 'test', + 'latitude': 32.880837, + 'longitude': -117.237561, + 'radius': 250, + } + }) + + self.calls = [] + + @callback + def record_call(service): + """Record calls.""" + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_if_fires_on_zone_enter(self): + """Test for firing on zone enter.""" + context = Context() + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + self.hass.block_till_done() + + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) + }, + + } + } + }) + + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }, context=context) + self.hass.block_till_done() + + self.assertEqual(1, len(self.calls)) + assert self.calls[0].context is context + self.assertEqual( + 'geo_location - geo_location.entity - hello - hello - test', + self.calls[0].data['some']) + + # Set out of zone again so we can trigger call + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + self.hass.block_till_done() + + common.turn_off(self.hass) + self.hass.block_till_done() + + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + self.hass.block_till_done() + + self.assertEqual(1, len(self.calls)) + + def test_if_not_fires_for_enter_on_zone_leave(self): + """Test for not firing on zone leave.""" + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + self.hass.block_till_done() + + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + self.hass.block_till_done() + + self.assertEqual(0, len(self.calls)) + + def test_if_fires_on_zone_leave(self): + """Test for firing on zone leave.""" + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + self.hass.block_till_done() + + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + self.hass.block_till_done() + + self.assertEqual(1, len(self.calls)) + + def test_if_not_fires_for_leave_on_zone_enter(self): + """Test for not firing on zone enter.""" + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + self.hass.block_till_done() + + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + self.hass.block_till_done() + + self.assertEqual(0, len(self.calls)) + + def test_if_fires_on_zone_appear(self): + """Test for firing if entity appears in zone.""" + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) + }, + + } + } + }) + + # Entity appears in zone without previously existing outside the zone. + context = Context() + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }, context=context) + self.hass.block_till_done() + + self.assertEqual(1, len(self.calls)) + assert self.calls[0].context is context + self.assertEqual( + 'geo_location - geo_location.entity - - hello - test', + self.calls[0].data['some']) + + def test_if_fires_on_zone_disappear(self): + """Test for firing if entity disappears from zone.""" + self.hass.states.set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + self.hass.block_till_done() + + assert setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) + }, + + } + } + }) + + # Entity disappears from zone without new coordinates outside the zone. + self.hass.states.async_remove('geo_location.entity') + self.hass.block_till_done() + + self.assertEqual(1, len(self.calls)) + self.assertEqual( + 'geo_location - geo_location.entity - hello - - test', + self.calls[0].data['some']) From 399f8a72c34e401b8e0be8429a5b732133c45518 Mon Sep 17 00:00:00 2001 From: Manuel de la Rosa Date: Mon, 22 Oct 2018 13:02:55 -0500 Subject: [PATCH 006/230] Fix Mexican Spanish identifier (#17674) Mexican Spanish identifier is "es-MX" instead of "en-MX". --- homeassistant/components/tts/microsoft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tts/microsoft.py b/homeassistant/components/tts/microsoft.py index 3043e9f418b..3cce7c1a78d 100644 --- a/homeassistant/components/tts/microsoft.py +++ b/homeassistant/components/tts/microsoft.py @@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) SUPPORTED_LANGUAGES = [ 'ar-eg', 'ar-sa', 'ca-es', 'cs-cz', 'da-dk', 'de-at', 'de-ch', 'de-de', 'el-gr', 'en-au', 'en-ca', 'en-gb', 'en-ie', 'en-in', 'en-us', 'es-es', - 'en-mx', 'fi-fi', 'fr-ca', 'fr-ch', 'fr-fr', 'he-il', 'hi-in', 'hu-hu', + 'es-mx', 'fi-fi', 'fr-ca', 'fr-ch', 'fr-fr', 'he-il', 'hi-in', 'hu-hu', 'id-id', 'it-it', 'ja-jp', 'ko-kr', 'nb-no', 'nl-nl', 'pl-pl', 'pt-br', 'pt-pt', 'ro-ro', 'ru-ru', 'sk-sk', 'sv-se', 'th-th', 'tr-tr', 'zh-cn', 'zh-hk', 'zh-tw', From fd9370da399fef95eaee4cb3a32e4d029d0f49d3 Mon Sep 17 00:00:00 2001 From: Tom Monck JR Date: Mon, 22 Oct 2018 14:05:00 -0400 Subject: [PATCH 007/230] Add readthedoc.yml file to specify the version of python to run during documentation building. (#17642) --- readthedocs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 readthedocs.yml diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 00000000000..6a06f655513 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,8 @@ +# .readthedocs.yml + +build: + image: latest + +python: + version: 3.6 + setup_py_install: true \ No newline at end of file From 4e8cd7281c43d4a6c3f2adc9211b84506092a8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20RAMAGE?= Date: Mon, 22 Oct 2018 20:07:11 +0200 Subject: [PATCH 008/230] All supported domains should be exposed by default (#17579) According to documentation, all supported domains should be exposed by default https://www.home-assistant.io/components/google_assistant/#expose_by_default --- homeassistant/components/google_assistant/const.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 485b98e8e22..d8ab231c96b 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -14,7 +14,8 @@ CONF_ROOM_HINT = 'room' DEFAULT_EXPOSE_BY_DEFAULT = True DEFAULT_EXPOSED_DOMAINS = [ - 'switch', 'light', 'group', 'media_player', 'fan', 'cover', 'climate' + 'climate', 'cover', 'fan', 'group', 'input_boolean', 'light', + 'media_player', 'scene', 'script', 'switch' ] CLIMATE_MODE_HEATCOOL = 'heatcool' CLIMATE_SUPPORTED_MODES = {'heat', 'cool', 'off', 'on', CLIMATE_MODE_HEATCOOL} From b773a9049c7937327da174ee822318d69bbe4547 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 22 Oct 2018 14:49:12 -0600 Subject: [PATCH 009/230] Updated simplisafe-python to 3.1.13 (#17696) --- homeassistant/components/simplisafe/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index de6277c2ef1..aaa8e3a19f9 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -23,7 +23,7 @@ from homeassistant.helpers import config_validation as cv from .config_flow import configured_instances from .const import DATA_CLIENT, DEFAULT_SCAN_INTERVAL, DOMAIN, TOPIC_UPDATE -REQUIREMENTS = ['simplisafe-python==3.1.12'] +REQUIREMENTS = ['simplisafe-python==3.1.13'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 095aa107e1c..0d67b5ea9e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1349,7 +1349,7 @@ shodan==1.10.4 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==3.1.12 +simplisafe-python==3.1.13 # homeassistant.components.sisyphus sisyphus-control==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1568fd95607..a6b7f14f06e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -222,7 +222,7 @@ ruamel.yaml==0.15.72 rxv==0.5.1 # homeassistant.components.simplisafe -simplisafe-python==3.1.12 +simplisafe-python==3.1.13 # homeassistant.components.sleepiq sleepyq==0.6 From 301493037186aa7eeba8de767dcd5f291f8142ec Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 23 Oct 2018 07:11:55 +0200 Subject: [PATCH 010/230] Update limitlessled to 1.1.3 (#17703) --- homeassistant/components/light/limitlessled.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index a5aeabba84d..2e2971cfdc2 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -20,7 +20,7 @@ from homeassistant.util.color import ( color_temperature_mired_to_kelvin, color_hs_to_RGB) from homeassistant.helpers.restore_state import async_get_last_state -REQUIREMENTS = ['limitlessled==1.1.2'] +REQUIREMENTS = ['limitlessled==1.1.3'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 0d67b5ea9e6..5d7de61eadf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -559,7 +559,7 @@ liffylights==0.9.4 lightify==1.0.6.1 # homeassistant.components.light.limitlessled -limitlessled==1.1.2 +limitlessled==1.1.3 # homeassistant.components.linode linode-api==4.1.9b1 From ad3d0c4e99db3d4248db6ac93092c31592135d8f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Oct 2018 07:12:12 +0200 Subject: [PATCH 011/230] Upgrade Sphinx to 1.8.1 (#17701) --- requirements_docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_docs.txt b/requirements_docs.txt index 1a809c2fb85..cd2eb1a0be6 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -1,3 +1,3 @@ -Sphinx==1.7.8 +Sphinx==1.8.1 sphinx-autodoc-typehints==1.3.0 sphinx-autodoc-annotation==1.0.post1 From 324587b2dbd388536c987ce5b6372cc706fc94e9 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Tue, 23 Oct 2018 10:04:47 +0300 Subject: [PATCH 012/230] Away mode temperature fix for generic thermostat (#17641) * Resolves /home-assistant/home-assistant#17433 Away mode temperature issue fix for generic_thermostat * Debug messages removed from generic_thermostat.py * Test for repeat away_mode set Test for fix of generic thermostat issue when away_mode was set several times in a row. * Code style fix in generic_thermostat * Remove blank line in the end of generic_thermostat * Fix style --- .../components/climate/generic_thermostat.py | 4 ++++ .../climate/test_generic_thermostat.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 258699ff90a..ad8875462fd 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -380,6 +380,8 @@ class GenericThermostat(ClimateDevice): async def async_turn_away_mode_on(self): """Turn away mode on by setting it on away hold indefinitely.""" + if self._is_away: + return self._is_away = True self._saved_target_temp = self._target_temp self._target_temp = self._away_temp @@ -388,6 +390,8 @@ class GenericThermostat(ClimateDevice): async def async_turn_away_mode_off(self): """Turn away off.""" + if not self._is_away: + return self._is_away = False self._target_temp = self._saved_target_temp await self._async_control_heating() diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 47ec621aeb5..8bbcbc8f840 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -221,6 +221,24 @@ class TestClimateGenericThermostat(unittest.TestCase): state = self.hass.states.get(ENTITY) self.assertEqual(23, state.attributes.get('temperature')) + def test_set_away_mode_twice_and_restore_prev_temp(self): + """Test the setting away mode twice in a row. + + Verify original temperature is restored. + """ + common.set_temperature(self.hass, 23) + self.hass.block_till_done() + common.set_away_mode(self.hass, True) + self.hass.block_till_done() + common.set_away_mode(self.hass, True) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY) + self.assertEqual(16, state.attributes.get('temperature')) + common.set_away_mode(self.hass, False) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY) + self.assertEqual(23, state.attributes.get('temperature')) + def test_sensor_bad_value(self): """Test sensor that have None as state.""" state = self.hass.states.get(ENTITY) From 44e35b7f526cac48fd3799d4538321b30e0980dd Mon Sep 17 00:00:00 2001 From: Jaxom Nutt <40261038+JaxomCS@users.noreply.github.com> Date: Tue, 23 Oct 2018 16:28:49 +0800 Subject: [PATCH 013/230] Bug fix for clicksend (#17713) * Bug fix Current version causes 500 error since it is sending an array of from numbers to ClickSend. Changing the from number to 'hass' identifies all messages as coming from Home Assistant making them more recognisable and removes the bug. * Amendment Changed it to use 'hass' as the default instead of defaulting to the recipient which is the array. Would have worked if users set their own name but users who were using the default were experiencing the issue. * Added DEFAULT_SENDER variable --- homeassistant/components/notify/clicksend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/clicksend.py b/homeassistant/components/notify/clicksend.py index c028da2c579..5506d6ed6d0 100644 --- a/homeassistant/components/notify/clicksend.py +++ b/homeassistant/components/notify/clicksend.py @@ -22,6 +22,8 @@ _LOGGER = logging.getLogger(__name__) BASE_API_URL = 'https://rest.clicksend.com/v3' +DEFAULT_SENDER = 'hass' + HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} @@ -29,7 +31,7 @@ def validate_sender(config): """Set the optional sender name if sender name is not provided.""" if CONF_SENDER in config: return config - config[CONF_SENDER] = config[CONF_RECIPIENT] + config[CONF_SENDER] = DEFAULT_SENDER return config @@ -61,7 +63,7 @@ class ClicksendNotificationService(BaseNotificationService): self.username = config.get(CONF_USERNAME) self.api_key = config.get(CONF_API_KEY) self.recipients = config.get(CONF_RECIPIENT) - self.sender = config.get(CONF_SENDER, CONF_RECIPIENT) + self.sender = config.get(CONF_SENDER) def send_message(self, message="", **kwargs): """Send a message to a user.""" From 50f0eac7f37c2339a56f77e12da443edae3bdf9c Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Tue, 23 Oct 2018 04:54:03 -0400 Subject: [PATCH 014/230] Fixed issue #16903 re exception with multiple simultanious writes (#17636) Reworked tests/components/emulated_hue/test_init.py to not be dependent on the specific internal implementation of util/jsonn.py --- homeassistant/util/json.py | 14 ++- tests/components/emulated_hue/test_init.py | 122 ++++++++++----------- 2 files changed, 69 insertions(+), 67 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 0a2a2a1edf3..b002c8e3147 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -4,7 +4,7 @@ from typing import Union, List, Dict import json import os -from os import O_WRONLY, O_CREAT, O_TRUNC +import tempfile from homeassistant.exceptions import HomeAssistantError @@ -46,13 +46,17 @@ def save_json(filename: str, data: Union[List, Dict], Returns True on success. """ - tmp_filename = filename + "__TEMP__" + tmp_filename = "" + tmp_path = os.path.split(filename)[0] try: json_data = json.dumps(data, sort_keys=True, indent=4) - mode = 0o600 if private else 0o644 - with open(os.open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, mode), - 'w', encoding='utf-8') as fdesc: + # Modern versions of Python tempfile create this file with mode 0o600 + with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', + dir=tmp_path, delete=False) as fdesc: fdesc.write(json_data) + tmp_filename = fdesc.name + if not private: + os.chmod(tmp_filename, 0o644) os.replace(tmp_filename, filename) except TypeError as error: _LOGGER.exception('Failed to serialize to JSON: %s', diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 9b0a5cd9052..3de8e969140 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,7 +1,5 @@ """Test the Emulated Hue component.""" -import json - -from unittest.mock import patch, Mock, mock_open, MagicMock +from unittest.mock import patch, Mock, MagicMock from homeassistant.components.emulated_hue import Config @@ -14,30 +12,30 @@ def test_config_google_home_entity_id_to_number(): 'type': 'google_home' }) - mop = mock_open(read_data=json.dumps({'1': 'light.test2'})) - handle = mop() + with patch('homeassistant.components.emulated_hue.load_json', + return_value={'1': 'light.test2'}) as json_loader: + with patch('homeassistant.components.emulated_hue' + '.save_json') as json_saver: + number = conf.entity_id_to_number('light.test') + assert number == '2' - with patch('homeassistant.util.json.open', mop, create=True): - with patch('homeassistant.util.json.os.open', return_value=0): - with patch('homeassistant.util.json.os.replace'): - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test2', - '2': 'light.test', - } + assert json_saver.mock_calls[0][1][1] == { + '1': 'light.test2', '2': 'light.test' + } - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '1' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id('1') - assert entity_id == 'light.test2' + number = conf.entity_id_to_number('light.test2') + assert number == '1' + assert json_saver.call_count == 1 + + entity_id = conf.number_to_entity_id('1') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_altered(): @@ -48,30 +46,30 @@ def test_config_google_home_entity_id_to_number_altered(): 'type': 'google_home' }) - mop = mock_open(read_data=json.dumps({'21': 'light.test2'})) - handle = mop() + with patch('homeassistant.components.emulated_hue.load_json', + return_value={'21': 'light.test2'}) as json_loader: + with patch('homeassistant.components.emulated_hue' + '.save_json') as json_saver: + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - with patch('homeassistant.util.json.open', mop, create=True): - with patch('homeassistant.util.json.os.open', return_value=0): - with patch('homeassistant.util.json.os.replace'): - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '21': 'light.test2', - '22': 'light.test', - } + assert json_saver.mock_calls[0][1][1] == { + '21': 'light.test2', + '22': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert json_saver.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '21' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test2') + assert number == '21' + assert json_saver.call_count == 1 - entity_id = conf.number_to_entity_id('21') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('21') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_empty(): @@ -82,29 +80,29 @@ def test_config_google_home_entity_id_to_number_empty(): 'type': 'google_home' }) - mop = mock_open(read_data='') - handle = mop() + with patch('homeassistant.components.emulated_hue.load_json', + return_value={}) as json_loader: + with patch('homeassistant.components.emulated_hue' + '.save_json') as json_saver: + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert json_saver.call_count == 1 + assert json_loader.call_count == 1 - with patch('homeassistant.util.json.open', mop, create=True): - with patch('homeassistant.util.json.os.open', return_value=0): - with patch('homeassistant.util.json.os.replace'): - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test', - } + assert json_saver.mock_calls[0][1][1] == { + '1': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert json_saver.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '2' - assert handle.write.call_count == 2 + number = conf.entity_id_to_number('light.test2') + assert number == '2' + assert json_saver.call_count == 2 - entity_id = conf.number_to_entity_id('2') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('2') + assert entity_id == 'light.test2' def test_config_alexa_entity_id_to_number(): From 277a9a39955d636b7c828a09d0ad1b94ce3a54b7 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Tue, 23 Oct 2018 11:08:11 +0200 Subject: [PATCH 015/230] Async version for asuswrt (#17692) * Testing async data for asuswrt * Moved to lib --- CODEOWNERS | 1 + .../components/device_tracker/asuswrt.py | 336 +----------- requirements_all.txt | 4 +- requirements_test_all.txt | 1 - .../components/device_tracker/test_asuswrt.py | 478 +----------------- 5 files changed, 26 insertions(+), 794 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c49af4864a9..2bf31378ac3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -58,6 +58,7 @@ homeassistant/components/climate/mill.py @danielhiversen homeassistant/components/climate/sensibo.py @andrey-git homeassistant/components/cover/group.py @cdce8p homeassistant/components/cover/template.py @PhracturedBlue +homeassistant/components/device_tracker/asuswrt.py @kennedyshead homeassistant/components/device_tracker/automatic.py @armills homeassistant/components/device_tracker/huawei_router.py @abmantis homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 710a07f77d3..461380b2c8e 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -5,10 +5,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.asuswrt/ """ import logging -import re -import socket -import telnetlib -from collections import namedtuple import voluptuous as vol @@ -19,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, CONF_PROTOCOL) -REQUIREMENTS = ['pexpect==4.6.0'] +REQUIREMENTS = ['aioasuswrt==1.0.0'] _LOGGER = logging.getLogger(__name__) @@ -44,345 +40,55 @@ PLATFORM_SCHEMA = vol.All( })) -_LEASES_CMD = 'cat /var/lib/misc/dnsmasq.leases' -_LEASES_REGEX = re.compile( - r'\w+\s' + - r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' + - r'(?P([^\s]+))') - -# Command to get both 5GHz and 2.4GHz clients -_WL_CMD = 'for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done' -_WL_REGEX = re.compile( - r'\w+\s' + - r'(?P(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))') - -_IP_NEIGH_CMD = 'ip neigh' -_IP_NEIGH_REGEX = re.compile( - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3}|' - r'([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){1,7})\s' - r'\w+\s' - r'\w+\s' - r'(\w+\s(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' - r'\s?(router)?' - r'\s?(nud)?' - r'(?P(\w+))') - -_ARP_CMD = 'arp -n' -_ARP_REGEX = re.compile( - r'.+\s' + - r'\((?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' + - r'.+\s' + - r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' + - r'\s' + - r'.*') - - -def get_scanner(hass, config): +async def async_get_scanner(hass, config): """Validate the configuration and return an ASUS-WRT scanner.""" scanner = AsusWrtDeviceScanner(config[DOMAIN]) - + await scanner.async_connect() return scanner if scanner.success_init else None -def _parse_lines(lines, regex): - """Parse the lines using the given regular expression. - - If a line can't be parsed it is logged and skipped in the output. - """ - results = [] - for line in lines: - match = regex.search(line) - if not match: - _LOGGER.debug("Could not parse row: %s", line) - continue - results.append(match.groupdict()) - return results - - -Device = namedtuple('Device', ['mac', 'ip', 'name']) - - class AsusWrtDeviceScanner(DeviceScanner): """This class queries a router running ASUSWRT firmware.""" # Eighth attribute needed for mode (AP mode vs router mode) def __init__(self, config): """Initialize the scanner.""" - self.host = config[CONF_HOST] - self.username = config[CONF_USERNAME] - self.password = config.get(CONF_PASSWORD, '') - self.ssh_key = config.get('ssh_key', config.get('pub_key', '')) - self.protocol = config[CONF_PROTOCOL] - self.mode = config[CONF_MODE] - self.port = config[CONF_PORT] - self.require_ip = config[CONF_REQUIRE_IP] + from aioasuswrt.asuswrt import AsusWrt - if self.protocol == 'ssh': - self.connection = SshConnection( - self.host, self.port, self.username, self.password, - self.ssh_key) - else: - self.connection = TelnetConnection( - self.host, self.port, self.username, self.password) + self.last_results = {} + self.success_init = False + self.connection = AsusWrt(config[CONF_HOST], config[CONF_PORT], + config[CONF_PROTOCOL] == 'telnet', + config[CONF_USERNAME], + config.get(CONF_PASSWORD, ''), + config.get('ssh_key', + config.get('pub_key', '')), + config[CONF_MODE], config[CONF_REQUIRE_IP]) + async def async_connect(self): + """Initialize connection to the router.""" self.last_results = {} # Test the router is accessible. - data = self.get_asuswrt_data() + data = await self.connection.async_get_connected_devices() self.success_init = data is not None - def scan_devices(self): + async def async_scan_devices(self): """Scan for new devices and return a list with found device IDs.""" - self._update_info() + await self.async_update_info() return list(self.last_results.keys()) - def get_device_name(self, device): + async def async_get_device_name(self, device): """Return the name of the given device or None if we don't know.""" if device not in self.last_results: return None return self.last_results[device].name - def _update_info(self): + async def async_update_info(self): """Ensure the information from the ASUSWRT router is up to date. Return boolean if scanning successful. """ - if not self.success_init: - return False - _LOGGER.info('Checking Devices') - data = self.get_asuswrt_data() - if not data: - return False - self.last_results = data - return True - - def get_asuswrt_data(self): - """Retrieve data from ASUSWRT. - - Calls various commands on the router and returns the superset of all - responses. Some commands will not work on some routers. - """ - devices = {} - devices.update(self._get_wl()) - devices.update(self._get_arp()) - devices.update(self._get_neigh(devices)) - if not self.mode == 'ap': - devices.update(self._get_leases(devices)) - - ret_devices = {} - for key in devices: - if not self.require_ip or devices[key].ip is not None: - ret_devices[key] = devices[key] - return ret_devices - - def _get_wl(self): - lines = self.connection.run_command(_WL_CMD) - if not lines: - return {} - result = _parse_lines(lines, _WL_REGEX) - devices = {} - for device in result: - mac = device['mac'].upper() - devices[mac] = Device(mac, None, None) - return devices - - def _get_leases(self, cur_devices): - lines = self.connection.run_command(_LEASES_CMD) - if not lines: - return {} - lines = [line for line in lines if not line.startswith('duid ')] - result = _parse_lines(lines, _LEASES_REGEX) - devices = {} - for device in result: - # For leases where the client doesn't set a hostname, ensure it - # is blank and not '*', which breaks entity_id down the line. - host = device['host'] - if host == '*': - host = '' - mac = device['mac'].upper() - if mac in cur_devices: - devices[mac] = Device(mac, device['ip'], host) - return devices - - def _get_neigh(self, cur_devices): - lines = self.connection.run_command(_IP_NEIGH_CMD) - if not lines: - return {} - result = _parse_lines(lines, _IP_NEIGH_REGEX) - devices = {} - for device in result: - status = device['status'] - if status is None or status.upper() != 'REACHABLE': - continue - if device['mac'] is not None: - mac = device['mac'].upper() - old_device = cur_devices.get(mac) - old_ip = old_device.ip if old_device else None - devices[mac] = Device(mac, device.get('ip', old_ip), None) - return devices - - def _get_arp(self): - lines = self.connection.run_command(_ARP_CMD) - if not lines: - return {} - result = _parse_lines(lines, _ARP_REGEX) - devices = {} - for device in result: - if device['mac'] is not None: - mac = device['mac'].upper() - devices[mac] = Device(mac, device['ip'], None) - return devices - - -class _Connection: - def __init__(self): - self._connected = False - - @property - def connected(self): - """Return connection state.""" - return self._connected - - def connect(self): - """Mark current connection state as connected.""" - self._connected = True - - def disconnect(self): - """Mark current connection state as disconnected.""" - self._connected = False - - -class SshConnection(_Connection): - """Maintains an SSH connection to an ASUS-WRT router.""" - - def __init__(self, host, port, username, password, ssh_key): - """Initialize the SSH connection properties.""" - super().__init__() - - self._ssh = None - self._host = host - self._port = port - self._username = username - self._password = password - self._ssh_key = ssh_key - - def run_command(self, command): - """Run commands through an SSH connection. - - Connect to the SSH server if not currently connected, otherwise - use the existing connection. - """ - from pexpect import pxssh, exceptions - - try: - if not self.connected: - self.connect() - self._ssh.sendline(command) - self._ssh.prompt() - lines = self._ssh.before.split(b'\n')[1:-1] - return [line.decode('utf-8') for line in lines] - except exceptions.EOF as err: - _LOGGER.error("Connection refused. %s", self._ssh.before) - self.disconnect() - return None - except pxssh.ExceptionPxssh as err: - _LOGGER.error("Unexpected SSH error: %s", err) - self.disconnect() - return None - except AssertionError as err: - _LOGGER.error("Connection to router unavailable: %s", err) - self.disconnect() - return None - - def connect(self): - """Connect to the ASUS-WRT SSH server.""" - from pexpect import pxssh - - self._ssh = pxssh.pxssh() - if self._ssh_key: - self._ssh.login(self._host, self._username, quiet=False, - ssh_key=self._ssh_key, port=self._port) - else: - self._ssh.login(self._host, self._username, quiet=False, - password=self._password, port=self._port) - - super().connect() - - def disconnect(self): - """Disconnect the current SSH connection.""" - try: - self._ssh.logout() - except Exception: # pylint: disable=broad-except - pass - finally: - self._ssh = None - - super().disconnect() - - -class TelnetConnection(_Connection): - """Maintains a Telnet connection to an ASUS-WRT router.""" - - def __init__(self, host, port, username, password): - """Initialize the Telnet connection properties.""" - super().__init__() - - self._telnet = None - self._host = host - self._port = port - self._username = username - self._password = password - self._prompt_string = None - - def run_command(self, command): - """Run a command through a Telnet connection. - - Connect to the Telnet server if not currently connected, otherwise - use the existing connection. - """ - try: - if not self.connected: - self.connect() - self._telnet.write('{}\n'.format(command).encode('ascii')) - data = (self._telnet.read_until(self._prompt_string). - split(b'\n')[1:-1]) - return [line.decode('utf-8') for line in data] - except EOFError: - _LOGGER.error("Unexpected response from router") - self.disconnect() - return None - except ConnectionRefusedError: - _LOGGER.error("Connection refused by router. Telnet enabled?") - self.disconnect() - return None - except socket.gaierror as exc: - _LOGGER.error("Socket exception: %s", exc) - self.disconnect() - return None - except OSError as exc: - _LOGGER.error("OSError: %s", exc) - self.disconnect() - return None - - def connect(self): - """Connect to the ASUS-WRT Telnet server.""" - self._telnet = telnetlib.Telnet(self._host) - self._telnet.read_until(b'login: ') - self._telnet.write((self._username + '\n').encode('ascii')) - self._telnet.read_until(b'Password: ') - self._telnet.write((self._password + '\n').encode('ascii')) - self._prompt_string = self._telnet.read_until(b'#').split(b'\n')[-1] - - super().connect() - - def disconnect(self): - """Disconnect the current Telnet connection.""" - try: - self._telnet.write('exit\n'.encode('ascii')) - except Exception: # pylint: disable=broad-except - pass - - super().disconnect() + self.last_results = await self.connection.async_get_connected_devices() diff --git a/requirements_all.txt b/requirements_all.txt index 5d7de61eadf..ffd2be0b3bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -87,6 +87,9 @@ abodepy==0.14.0 # homeassistant.components.media_player.frontier_silicon afsapi==0.0.4 +# homeassistant.components.device_tracker.asuswrt +aioasuswrt==1.0.0 + # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 @@ -690,7 +693,6 @@ panasonic_viera==0.3.1 pdunehd==1.3 # homeassistant.components.device_tracker.aruba -# homeassistant.components.device_tracker.asuswrt # homeassistant.components.device_tracker.cisco_ios # homeassistant.components.device_tracker.unifi_direct # homeassistant.components.media_player.pandora diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6b7f14f06e..fe02cb0fab1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -125,7 +125,6 @@ numpy==1.15.2 paho-mqtt==1.4.0 # homeassistant.components.device_tracker.aruba -# homeassistant.components.device_tracker.asuswrt # homeassistant.components.device_tracker.cisco_ios # homeassistant.components.device_tracker.unifi_direct # homeassistant.components.media_player.pandora diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index 8c5af618288..d43a7d53969 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -3,9 +3,6 @@ import os from datetime import timedelta import unittest from unittest import mock -import socket - -import voluptuous as vol from homeassistant.setup import setup_component from homeassistant.components import device_tracker @@ -13,9 +10,7 @@ from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, CONF_TRACK_NEW, CONF_NEW_DEVICE_DEFAULTS, CONF_AWAY_HIDE) from homeassistant.components.device_tracker.asuswrt import ( - CONF_PROTOCOL, CONF_MODE, CONF_PUB_KEY, DOMAIN, _ARP_REGEX, - CONF_PORT, PLATFORM_SCHEMA, Device, get_scanner, AsusWrtDeviceScanner, - _parse_lines, SshConnection, TelnetConnection, CONF_REQUIRE_IP) + CONF_PROTOCOL, CONF_MODE, DOMAIN, CONF_PORT) from homeassistant.const import (CONF_PLATFORM, CONF_PASSWORD, CONF_USERNAME, CONF_HOST) @@ -35,85 +30,6 @@ VALID_CONFIG_ROUTER_SSH = {DOMAIN: { CONF_PORT: '22' }} -WL_DATA = [ - 'assoclist 01:02:03:04:06:08\r', - 'assoclist 08:09:10:11:12:14\r', - 'assoclist 08:09:10:11:12:15\r' -] - -WL_DEVICES = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip=None, name=None), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip=None, name=None), - '08:09:10:11:12:15': Device( - mac='08:09:10:11:12:15', ip=None, name=None) -} - -ARP_DATA = [ - '? (123.123.123.125) at 01:02:03:04:06:08 [ether] on eth0\r', - '? (123.123.123.126) at 08:09:10:11:12:14 [ether] on br0\r', - '? (123.123.123.127) at on br0\r', -] - -ARP_DEVICES = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name=None), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name=None) -} - -NEIGH_DATA = [ - '123.123.123.125 dev eth0 lladdr 01:02:03:04:06:08 REACHABLE\r', - '123.123.123.126 dev br0 lladdr 08:09:10:11:12:14 REACHABLE\r', - '123.123.123.127 dev br0 FAILED\r', - '123.123.123.128 dev br0 lladdr 08:09:15:15:15:15 DELAY\r', - 'fe80::feff:a6ff:feff:12ff dev br0 lladdr fc:ff:a6:ff:12:ff STALE\r', -] - -NEIGH_DEVICES = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name=None), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name=None) -} - -LEASES_DATA = [ - '51910 01:02:03:04:06:08 123.123.123.125 TV 01:02:03:04:06:08\r', - '79986 01:02:03:04:06:10 123.123.123.127 android 01:02:03:04:06:15\r', - '23523 08:09:10:11:12:14 123.123.123.126 * 08:09:10:11:12:14\r', -] - -LEASES_DEVICES = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name='TV'), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name='') -} - -WAKE_DEVICES = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name='TV'), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name='') -} - -WAKE_DEVICES_AP = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name=None), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name=None) -} - -WAKE_DEVICES_NO_IP = { - '01:02:03:04:06:08': Device( - mac='01:02:03:04:06:08', ip='123.123.123.125', name=None), - '08:09:10:11:12:14': Device( - mac='08:09:10:11:12:14', ip='123.123.123.126', name=None), - '08:09:10:11:12:15': Device( - mac='08:09:10:11:12:15', ip=None, name=None) -} - def setup_module(): """Set up the test module.""" @@ -150,24 +66,6 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): except FileNotFoundError: pass - def test_parse_lines_wrong_input(self): - """Testing parse lines.""" - output = _parse_lines("asdf asdfdfsafad", _ARP_REGEX) - self.assertEqual(output, []) - - def test_get_device_name(self): - """Test for getting name.""" - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.last_results = WAKE_DEVICES - self.assertEqual('TV', scanner.get_device_name('01:02:03:04:06:08')) - self.assertEqual(None, scanner.get_device_name('01:02:03:04:08:08')) - - def test_scan_devices(self): - """Test for scan devices.""" - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.last_results = WAKE_DEVICES - self.assertEqual(list(WAKE_DEVICES), scanner.scan_devices()) - def test_password_or_pub_key_required(self): """Test creating an AsusWRT scanner without a pass or pubkey.""" with assert_setup_component(0, DOMAIN): @@ -207,377 +105,3 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): conf_dict[DOMAIN][CONF_PORT] = 22 self.assertEqual(asuswrt_mock.call_count, 1) self.assertEqual(asuswrt_mock.call_args, mock.call(conf_dict[DOMAIN])) - - @mock.patch( - 'homeassistant.components.device_tracker.asuswrt.AsusWrtDeviceScanner', - return_value=mock.MagicMock()) - def test_get_scanner_with_pubkey_no_password(self, asuswrt_mock): - """Test creating an AsusWRT scanner with a pubkey and no password.""" - conf_dict = { - device_tracker.DOMAIN: { - CONF_PLATFORM: 'asuswrt', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - CONF_PUB_KEY: FAKEFILE, - CONF_TRACK_NEW: True, - CONF_CONSIDER_HOME: timedelta(seconds=180), - CONF_NEW_DEVICE_DEFAULTS: { - CONF_TRACK_NEW: True, - CONF_AWAY_HIDE: False - } - } - } - - with assert_setup_component(1, DOMAIN): - assert setup_component(self.hass, DOMAIN, conf_dict) - - conf_dict[DOMAIN][CONF_MODE] = 'router' - conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' - conf_dict[DOMAIN][CONF_PORT] = 22 - self.assertEqual(asuswrt_mock.call_count, 1) - self.assertEqual(asuswrt_mock.call_args, mock.call(conf_dict[DOMAIN])) - - def test_ssh_login_with_pub_key(self): - """Test that login is done with pub_key when configured to.""" - ssh = mock.MagicMock() - ssh_mock = mock.patch('pexpect.pxssh.pxssh', return_value=ssh) - ssh_mock.start() - self.addCleanup(ssh_mock.stop) - conf_dict = PLATFORM_SCHEMA({ - CONF_PLATFORM: 'asuswrt', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - CONF_PUB_KEY: FAKEFILE - }) - update_mock = mock.patch( - 'homeassistant.components.device_tracker.asuswrt.' - 'AsusWrtDeviceScanner.get_asuswrt_data') - update_mock.start() - self.addCleanup(update_mock.stop) - asuswrt = device_tracker.asuswrt.AsusWrtDeviceScanner(conf_dict) - asuswrt.connection.run_command('ls') - self.assertEqual(ssh.login.call_count, 1) - self.assertEqual( - ssh.login.call_args, - mock.call('fake_host', 'fake_user', quiet=False, - ssh_key=FAKEFILE, port=22) - ) - - def test_ssh_login_with_password(self): - """Test that login is done with password when configured to.""" - ssh = mock.MagicMock() - ssh_mock = mock.patch('pexpect.pxssh.pxssh', return_value=ssh) - ssh_mock.start() - self.addCleanup(ssh_mock.stop) - conf_dict = PLATFORM_SCHEMA({ - CONF_PLATFORM: 'asuswrt', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - CONF_PASSWORD: 'fake_pass' - }) - update_mock = mock.patch( - 'homeassistant.components.device_tracker.asuswrt.' - 'AsusWrtDeviceScanner.get_asuswrt_data') - update_mock.start() - self.addCleanup(update_mock.stop) - asuswrt = device_tracker.asuswrt.AsusWrtDeviceScanner(conf_dict) - asuswrt.connection.run_command('ls') - self.assertEqual(ssh.login.call_count, 1) - self.assertEqual( - ssh.login.call_args, - mock.call('fake_host', 'fake_user', quiet=False, - password='fake_pass', port=22) - ) - - def test_ssh_login_without_password_or_pubkey(self): - """Test that login is not called without password or pub_key.""" - ssh = mock.MagicMock() - ssh_mock = mock.patch('pexpect.pxssh.pxssh', return_value=ssh) - ssh_mock.start() - self.addCleanup(ssh_mock.stop) - - conf_dict = { - CONF_PLATFORM: 'asuswrt', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - } - - with self.assertRaises(vol.Invalid): - conf_dict = PLATFORM_SCHEMA(conf_dict) - - update_mock = mock.patch( - 'homeassistant.components.device_tracker.asuswrt.' - 'AsusWrtDeviceScanner.get_asuswrt_data') - update_mock.start() - self.addCleanup(update_mock.stop) - - with assert_setup_component(0, DOMAIN): - assert setup_component(self.hass, DOMAIN, - {DOMAIN: conf_dict}) - ssh.login.assert_not_called() - - def test_telnet_login_with_password(self): - """Test that login is done with password when configured to.""" - telnet = mock.MagicMock() - telnet_mock = mock.patch('telnetlib.Telnet', return_value=telnet) - telnet_mock.start() - self.addCleanup(telnet_mock.stop) - conf_dict = PLATFORM_SCHEMA({ - CONF_PLATFORM: 'asuswrt', - CONF_PROTOCOL: 'telnet', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - CONF_PASSWORD: 'fake_pass' - }) - update_mock = mock.patch( - 'homeassistant.components.device_tracker.asuswrt.' - 'AsusWrtDeviceScanner.get_asuswrt_data') - update_mock.start() - self.addCleanup(update_mock.stop) - asuswrt = device_tracker.asuswrt.AsusWrtDeviceScanner(conf_dict) - asuswrt.connection.run_command('ls') - self.assertEqual(telnet.read_until.call_count, 4) - self.assertEqual(telnet.write.call_count, 3) - self.assertEqual( - telnet.read_until.call_args_list[0], - mock.call(b'login: ') - ) - self.assertEqual( - telnet.write.call_args_list[0], - mock.call(b'fake_user\n') - ) - self.assertEqual( - telnet.read_until.call_args_list[1], - mock.call(b'Password: ') - ) - self.assertEqual( - telnet.write.call_args_list[1], - mock.call(b'fake_pass\n') - ) - self.assertEqual( - telnet.read_until.call_args_list[2], - mock.call(b'#') - ) - - def test_telnet_login_without_password(self): - """Test that login is not called without password or pub_key.""" - telnet = mock.MagicMock() - telnet_mock = mock.patch('telnetlib.Telnet', return_value=telnet) - telnet_mock.start() - self.addCleanup(telnet_mock.stop) - - conf_dict = { - CONF_PLATFORM: 'asuswrt', - CONF_PROTOCOL: 'telnet', - CONF_HOST: 'fake_host', - CONF_USERNAME: 'fake_user', - } - - with self.assertRaises(vol.Invalid): - conf_dict = PLATFORM_SCHEMA(conf_dict) - - update_mock = mock.patch( - 'homeassistant.components.device_tracker.asuswrt.' - 'AsusWrtDeviceScanner.get_asuswrt_data') - update_mock.start() - self.addCleanup(update_mock.stop) - - with assert_setup_component(0, DOMAIN): - assert setup_component(self.hass, DOMAIN, - {DOMAIN: conf_dict}) - telnet.login.assert_not_called() - - def test_get_asuswrt_data(self): - """Test asuswrt data fetch.""" - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner._get_wl = mock.Mock() - scanner._get_arp = mock.Mock() - scanner._get_neigh = mock.Mock() - scanner._get_leases = mock.Mock() - scanner._get_wl.return_value = WL_DEVICES - scanner._get_arp.return_value = ARP_DEVICES - scanner._get_neigh.return_value = NEIGH_DEVICES - scanner._get_leases.return_value = LEASES_DEVICES - self.assertEqual(WAKE_DEVICES, scanner.get_asuswrt_data()) - - def test_get_asuswrt_data_ap(self): - """Test for get asuswrt_data in ap mode.""" - conf = VALID_CONFIG_ROUTER_SSH.copy()[DOMAIN] - conf[CONF_MODE] = 'ap' - scanner = AsusWrtDeviceScanner(conf) - scanner._get_wl = mock.Mock() - scanner._get_arp = mock.Mock() - scanner._get_neigh = mock.Mock() - scanner._get_leases = mock.Mock() - scanner._get_wl.return_value = WL_DEVICES - scanner._get_arp.return_value = ARP_DEVICES - scanner._get_neigh.return_value = NEIGH_DEVICES - scanner._get_leases.return_value = LEASES_DEVICES - self.assertEqual(WAKE_DEVICES_AP, scanner.get_asuswrt_data()) - - def test_get_asuswrt_data_no_ip(self): - """Test for get asuswrt_data and not requiring ip.""" - conf = VALID_CONFIG_ROUTER_SSH.copy()[DOMAIN] - conf[CONF_REQUIRE_IP] = False - scanner = AsusWrtDeviceScanner(conf) - scanner._get_wl = mock.Mock() - scanner._get_arp = mock.Mock() - scanner._get_neigh = mock.Mock() - scanner._get_leases = mock.Mock() - scanner._get_wl.return_value = WL_DEVICES - scanner._get_arp.return_value = ARP_DEVICES - scanner._get_neigh.return_value = NEIGH_DEVICES - scanner._get_leases.return_value = LEASES_DEVICES - self.assertEqual(WAKE_DEVICES_NO_IP, scanner.get_asuswrt_data()) - - def test_update_info(self): - """Test for update info.""" - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.get_asuswrt_data = mock.Mock() - scanner.get_asuswrt_data.return_value = WAKE_DEVICES - self.assertTrue(scanner._update_info()) - self.assertTrue(scanner.last_results, WAKE_DEVICES) - scanner.success_init = False - self.assertFalse(scanner._update_info()) - - @mock.patch( - 'homeassistant.components.device_tracker.asuswrt.SshConnection') - def test_get_wl(self, mocked_ssh): - """Testing wl.""" - mocked_ssh.run_command.return_value = WL_DATA - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.connection = mocked_ssh - self.assertEqual(WL_DEVICES, scanner._get_wl()) - mocked_ssh.run_command.return_value = '' - self.assertEqual({}, scanner._get_wl()) - - @mock.patch( - 'homeassistant.components.device_tracker.asuswrt.SshConnection') - def test_get_arp(self, mocked_ssh): - """Testing arp.""" - mocked_ssh.run_command.return_value = ARP_DATA - - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.connection = mocked_ssh - self.assertEqual(ARP_DEVICES, scanner._get_arp()) - mocked_ssh.run_command.return_value = '' - self.assertEqual({}, scanner._get_arp()) - - @mock.patch( - 'homeassistant.components.device_tracker.asuswrt.SshConnection') - def test_get_neigh(self, mocked_ssh): - """Testing neigh.""" - mocked_ssh.run_command.return_value = NEIGH_DATA - - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.connection = mocked_ssh - self.assertEqual(NEIGH_DEVICES, scanner._get_neigh(ARP_DEVICES.copy())) - self.assertEqual(NEIGH_DEVICES, scanner._get_neigh({ - 'UN:KN:WN:DE:VI:CE': Device('UN:KN:WN:DE:VI:CE', None, None), - })) - mocked_ssh.run_command.return_value = '' - self.assertEqual({}, scanner._get_neigh(ARP_DEVICES.copy())) - - @mock.patch( - 'homeassistant.components.device_tracker.asuswrt.SshConnection') - def test_get_leases(self, mocked_ssh): - """Testing leases.""" - mocked_ssh.run_command.return_value = LEASES_DATA - - scanner = get_scanner(self.hass, VALID_CONFIG_ROUTER_SSH) - scanner.connection = mocked_ssh - self.assertEqual( - LEASES_DEVICES, scanner._get_leases(NEIGH_DEVICES.copy())) - mocked_ssh.run_command.return_value = '' - self.assertEqual({}, scanner._get_leases(NEIGH_DEVICES.copy())) - - -@pytest.mark.skip( - reason="These tests are performing actual failing network calls. They " - "need to be cleaned up before they are re-enabled. They're frequently " - "failing in Travis.") -class TestSshConnection(unittest.TestCase): - """Testing SshConnection.""" - - def setUp(self): - """Set up test env.""" - self.connection = SshConnection( - 'fake', 'fake', 'fake', 'fake', 'fake') - self.connection._connected = True - - def test_run_command_exception_eof(self): - """Testing exception in run_command.""" - from pexpect import exceptions - self.connection._ssh = mock.Mock() - self.connection._ssh.sendline = mock.Mock() - self.connection._ssh.sendline.side_effect = exceptions.EOF('except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - self.assertIsNone(self.connection._ssh) - - def test_run_command_exception_pxssh(self): - """Testing exception in run_command.""" - from pexpect import pxssh - self.connection._ssh = mock.Mock() - self.connection._ssh.sendline = mock.Mock() - self.connection._ssh.sendline.side_effect = pxssh.ExceptionPxssh( - 'except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - self.assertIsNone(self.connection._ssh) - - def test_run_command_assertion_error(self): - """Testing exception in run_command.""" - self.connection._ssh = mock.Mock() - self.connection._ssh.sendline = mock.Mock() - self.connection._ssh.sendline.side_effect = AssertionError('except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - self.assertIsNone(self.connection._ssh) - - -@pytest.mark.skip( - reason="These tests are performing actual failing network calls. They " - "need to be cleaned up before they are re-enabled. They're frequently " - "failing in Travis.") -class TestTelnetConnection(unittest.TestCase): - """Testing TelnetConnection.""" - - def setUp(self): - """Set up test env.""" - self.connection = TelnetConnection( - 'fake', 'fake', 'fake', 'fake') - self.connection._connected = True - - def test_run_command_exception_eof(self): - """Testing EOFException in run_command.""" - self.connection._telnet = mock.Mock() - self.connection._telnet.write = mock.Mock() - self.connection._telnet.write.side_effect = EOFError('except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - - def test_run_command_exception_connection_refused(self): - """Testing ConnectionRefusedError in run_command.""" - self.connection._telnet = mock.Mock() - self.connection._telnet.write = mock.Mock() - self.connection._telnet.write.side_effect = ConnectionRefusedError( - 'except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - - def test_run_command_exception_gaierror(self): - """Testing socket.gaierror in run_command.""" - self.connection._telnet = mock.Mock() - self.connection._telnet.write = mock.Mock() - self.connection._telnet.write.side_effect = socket.gaierror('except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) - - def test_run_command_exception_oserror(self): - """Testing OSError in run_command.""" - self.connection._telnet = mock.Mock() - self.connection._telnet.write = mock.Mock() - self.connection._telnet.write.side_effect = OSError('except') - self.connection.run_command('test') - self.assertFalse(self.connection._connected) From d5a5695411ef65bc2c30b6e5b87f39ee5e388035 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Tue, 23 Oct 2018 02:14:46 -0700 Subject: [PATCH 016/230] Migrate Mailgun to use the webhook component (#17464) * Switch mailgun to use webhook api * Generalize webhook_config_entry_flow * Add tests for webhook_config_entry_flow * Add tests for mailgun * Remove old mailgun file from .coveragerc * Refactor WebhookFlowHandler into config_entry_flow * Remove test of helper func from IFTTT * Lint --- .coveragerc | 1 - homeassistant/components/ifttt/__init__.py | 53 ++--------- homeassistant/components/mailgun.py | 50 ----------- .../components/mailgun/.translations/en.json | 18 ++++ homeassistant/components/mailgun/__init__.py | 67 ++++++++++++++ homeassistant/components/mailgun/strings.json | 18 ++++ homeassistant/components/notify/mailgun.py | 5 +- homeassistant/config_entries.py | 1 + homeassistant/helpers/config_entry_flow.py | 58 ++++++++++++ tests/components/ifttt/test_init.py | 12 +-- tests/components/mailgun/__init__.py | 1 + tests/components/mailgun/test_init.py | 39 ++++++++ tests/helpers/test_config_entry_flow.py | 88 ++++++++++++++++--- 13 files changed, 289 insertions(+), 122 deletions(-) delete mode 100644 homeassistant/components/mailgun.py create mode 100644 homeassistant/components/mailgun/.translations/en.json create mode 100644 homeassistant/components/mailgun/__init__.py create mode 100644 homeassistant/components/mailgun/strings.json create mode 100644 tests/components/mailgun/__init__.py create mode 100644 tests/components/mailgun/test_init.py diff --git a/.coveragerc b/.coveragerc index 0049349cfff..25aa405035b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -209,7 +209,6 @@ omit = homeassistant/components/lutron_caseta.py homeassistant/components/*/lutron_caseta.py - homeassistant/components/mailgun.py homeassistant/components/*/mailgun.py homeassistant/components/matrix.py diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py index 76f01ad0aca..85ee6b9fa1c 100644 --- a/homeassistant/components/ifttt/__init__.py +++ b/homeassistant/components/ifttt/__init__.py @@ -4,18 +4,15 @@ Support to trigger Maker IFTTT recipes. For more details about this component, please refer to the documentation at https://home-assistant.io/components/ifttt/ """ -from ipaddress import ip_address import json import logging -from urllib.parse import urlparse import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant import config_entries from homeassistant.const import CONF_WEBHOOK_ID -from homeassistant.util.network import is_local +from homeassistant.helpers import config_entry_flow REQUIREMENTS = ['pyfttt==0.3'] DEPENDENCIES = ['webhook'] @@ -100,43 +97,11 @@ async def async_unload_entry(hass, entry): hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) return True - -@config_entries.HANDLERS.register(DOMAIN) -class ConfigFlow(config_entries.ConfigFlow): - """Handle an IFTTT config flow.""" - - async def async_step_user(self, user_input=None): - """Handle a user initiated set up flow.""" - if self._async_current_entries(): - return self.async_abort(reason='one_instance_allowed') - - try: - url_parts = urlparse(self.hass.config.api.base_url) - - if is_local(ip_address(url_parts.hostname)): - return self.async_abort(reason='not_internet_accessible') - except ValueError: - # If it's not an IP address, it's very likely publicly accessible - pass - - if user_input is None: - return self.async_show_form( - step_id='user', - ) - - webhook_id = self.hass.components.webhook.async_generate_id() - webhook_url = \ - self.hass.components.webhook.async_generate_url(webhook_id) - - return self.async_create_entry( - title='IFTTT Webhook', - data={ - CONF_WEBHOOK_ID: webhook_id - }, - description_placeholders={ - 'applet_url': 'https://ifttt.com/maker_webhooks', - 'webhook_url': webhook_url, - 'docs_url': - 'https://www.home-assistant.io/components/ifttt/' - } - ) +config_entry_flow.register_webhook_flow( + DOMAIN, + 'IFTTT Webhook', + { + 'applet_url': 'https://ifttt.com/maker_webhooks', + 'docs_url': 'https://www.home-assistant.io/components/ifttt/' + } +) diff --git a/homeassistant/components/mailgun.py b/homeassistant/components/mailgun.py deleted file mode 100644 index 7cb7ef7151d..00000000000 --- a/homeassistant/components/mailgun.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Support for Mailgun. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/mailgun/ -""" -import voluptuous as vol - -import homeassistant.helpers.config_validation as cv -from homeassistant.const import CONF_API_KEY, CONF_DOMAIN -from homeassistant.core import callback -from homeassistant.components.http import HomeAssistantView - - -DOMAIN = 'mailgun' -API_PATH = '/api/{}'.format(DOMAIN) -DATA_MAILGUN = DOMAIN -DEPENDENCIES = ['http'] -MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) -CONF_SANDBOX = 'sandbox' -DEFAULT_SANDBOX = False - -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_DOMAIN): cv.string, - vol.Optional(CONF_SANDBOX, default=DEFAULT_SANDBOX): cv.boolean - }), -}, extra=vol.ALLOW_EXTRA) - - -def setup(hass, config): - """Set up the Mailgun component.""" - hass.data[DATA_MAILGUN] = config[DOMAIN] - hass.http.register_view(MailgunReceiveMessageView()) - return True - - -class MailgunReceiveMessageView(HomeAssistantView): - """Handle data from Mailgun inbound messages.""" - - url = API_PATH - name = 'api:{}'.format(DOMAIN) - - @callback - def post(self, request): # pylint: disable=no-self-use - """Handle Mailgun message POST.""" - hass = request.app['hass'] - data = yield from request.post() - hass.bus.async_fire(MESSAGE_RECEIVED, dict(data)) diff --git a/homeassistant/components/mailgun/.translations/en.json b/homeassistant/components/mailgun/.translations/en.json new file mode 100644 index 00000000000..0e993bef5d4 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Mailgun", + "step": { + "user": { + "title": "Set up the Mailgun Webhook", + "description": "Are you sure you want to set up Mailgun?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + } + } +} diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py new file mode 100644 index 00000000000..25f697084d3 --- /dev/null +++ b/homeassistant/components/mailgun/__init__.py @@ -0,0 +1,67 @@ +""" +Support for Mailgun. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/mailgun/ +""" + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID +from homeassistant.helpers import config_entry_flow + +DOMAIN = 'mailgun' +API_PATH = '/api/{}'.format(DOMAIN) +DEPENDENCIES = ['webhook'] +MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) +CONF_SANDBOX = 'sandbox' +DEFAULT_SANDBOX = False + +CONFIG_SCHEMA = vol.Schema({ + vol.Optional(DOMAIN): vol.Schema({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_DOMAIN): cv.string, + vol.Optional(CONF_SANDBOX, default=DEFAULT_SANDBOX): cv.boolean, + vol.Optional(CONF_WEBHOOK_ID): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Mailgun component.""" + if DOMAIN not in config: + return True + + hass.data[DOMAIN] = config[DOMAIN] + return True + + +async def handle_webhook(hass, webhook_id, request): + """Handle incoming webhook with Mailgun inbound messages.""" + data = dict(await request.post()) + data['webhook_id'] = webhook_id + hass.bus.async_fire(MESSAGE_RECEIVED, data) + + +async def async_setup_entry(hass, entry): + """Configure based on config entry.""" + hass.components.webhook.async_register( + entry.data[CONF_WEBHOOK_ID], handle_webhook) + return True + + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) + return True + +config_entry_flow.register_webhook_flow( + DOMAIN, + 'Mailgun Webhook', + { + 'mailgun_url': + 'https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks', + 'docs_url': 'https://www.home-assistant.io/components/mailgun/' + } +) diff --git a/homeassistant/components/mailgun/strings.json b/homeassistant/components/mailgun/strings.json new file mode 100644 index 00000000000..0e993bef5d4 --- /dev/null +++ b/homeassistant/components/mailgun/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Mailgun", + "step": { + "user": { + "title": "Set up the Mailgun Webhook", + "description": "Are you sure you want to set up Mailgun?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + } + } +} diff --git a/homeassistant/components/notify/mailgun.py b/homeassistant/components/notify/mailgun.py index 1aa403f0ba8..56b0ab7e333 100644 --- a/homeassistant/components/notify/mailgun.py +++ b/homeassistant/components/notify/mailgun.py @@ -8,7 +8,8 @@ import logging import voluptuous as vol -from homeassistant.components.mailgun import CONF_SANDBOX, DATA_MAILGUN +from homeassistant.components.mailgun import ( + CONF_SANDBOX, DOMAIN as MAILGUN_DOMAIN) from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService, ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_DATA) @@ -35,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the Mailgun notification service.""" - data = hass.data[DATA_MAILGUN] + data = hass.data[MAILGUN_DOMAIN] mailgun_service = MailgunNotificationService( data.get(CONF_DOMAIN), data.get(CONF_SANDBOX), data.get(CONF_API_KEY), config.get(CONF_SENDER), diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index c1c0fbbf775..e00215b8126 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -143,6 +143,7 @@ FLOWS = [ 'ifttt', 'ios', 'lifx', + 'mailgun', 'mqtt', 'nest', 'openuv', diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 569a101b3dd..31d9907d315 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -1,7 +1,10 @@ """Helpers for data entry flows for config entries.""" from functools import partial +from ipaddress import ip_address +from urllib.parse import urlparse from homeassistant import config_entries +from homeassistant.util.network import is_local def register_discovery_flow(domain, title, discovery_function, @@ -12,6 +15,14 @@ def register_discovery_flow(domain, title, discovery_function, connection_class)) +def register_webhook_flow(domain, title, description_placeholder, + allow_multiple=False): + """Register flow for webhook integrations.""" + config_entries.HANDLERS.register(domain)( + partial(WebhookFlowHandler, domain, title, description_placeholder, + allow_multiple)) + + class DiscoveryFlowHandler(config_entries.ConfigFlow): """Handle a discovery config flow.""" @@ -84,3 +95,50 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): title=self._title, data={}, ) + + +class WebhookFlowHandler(config_entries.ConfigFlow): + """Handle a webhook config flow.""" + + VERSION = 1 + + def __init__(self, domain, title, description_placeholder, + allow_multiple): + """Initialize the discovery config flow.""" + self._domain = domain + self._title = title + self._description_placeholder = description_placeholder + self._allow_multiple = allow_multiple + + async def async_step_user(self, user_input=None): + """Handle a user initiated set up flow to create a webhook.""" + if not self._allow_multiple and self._async_current_entries(): + return self.async_abort(reason='one_instance_allowed') + + try: + url_parts = urlparse(self.hass.config.api.base_url) + + if is_local(ip_address(url_parts.hostname)): + return self.async_abort(reason='not_internet_accessible') + except ValueError: + # If it's not an IP address, it's very likely publicly accessible + pass + + if user_input is None: + return self.async_show_form( + step_id='user', + ) + + webhook_id = self.hass.components.webhook.async_generate_id() + webhook_url = \ + self.hass.components.webhook.async_generate_url(webhook_id) + + self._description_placeholder['webhook_url'] = webhook_url + + return self.async_create_entry( + title=self._title, + data={ + 'webhook_id': webhook_id + }, + description_placeholders=self._description_placeholder + ) diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py index 61d6654ba55..21417c99c5b 100644 --- a/tests/components/ifttt/test_init.py +++ b/tests/components/ifttt/test_init.py @@ -1,5 +1,5 @@ """Test the init file of IFTTT.""" -from unittest.mock import Mock, patch +from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.core import callback @@ -36,13 +36,3 @@ async def test_config_flow_registers_webhook(hass, aiohttp_client): assert len(ifttt_events) == 1 assert ifttt_events[0].data['webhook_id'] == webhook_id assert ifttt_events[0].data['hello'] == 'ifttt' - - -async def test_config_flow_aborts_external_url(hass, aiohttp_client): - """Test setting up IFTTT and sending webhook.""" - hass.config.api = Mock(base_url='http://192.168.1.10') - result = await hass.config_entries.flow.async_init('ifttt', context={ - 'source': 'user' - }) - assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT - assert result['reason'] == 'not_internet_accessible' diff --git a/tests/components/mailgun/__init__.py b/tests/components/mailgun/__init__.py new file mode 100644 index 00000000000..3999bce717c --- /dev/null +++ b/tests/components/mailgun/__init__.py @@ -0,0 +1 @@ +"""Tests for the Mailgun component.""" diff --git a/tests/components/mailgun/test_init.py b/tests/components/mailgun/test_init.py new file mode 100644 index 00000000000..312e3e22bfd --- /dev/null +++ b/tests/components/mailgun/test_init.py @@ -0,0 +1,39 @@ +"""Test the init file of Mailgun.""" +from unittest.mock import patch + +from homeassistant import data_entry_flow +from homeassistant.components import mailgun + +from homeassistant.core import callback + + +async def test_config_flow_registers_webhook(hass, aiohttp_client): + """Test setting up Mailgun and sending webhook.""" + with patch('homeassistant.util.get_local_ip', return_value='example.com'): + result = await hass.config_entries.flow.async_init('mailgun', context={ + 'source': 'user' + }) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], {}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + webhook_id = result['result'].data['webhook_id'] + + mailgun_events = [] + + @callback + def handle_event(event): + """Handle Mailgun event.""" + mailgun_events.append(event) + + hass.bus.async_listen(mailgun.MESSAGE_RECEIVED, handle_event) + + client = await aiohttp_client(hass.http.app) + await client.post('/api/webhook/{}'.format(webhook_id), data={ + 'hello': 'mailgun' + }) + + assert len(mailgun_events) == 1 + assert mailgun_events[0].data['webhook_id'] == webhook_id + assert mailgun_events[0].data['hello'] == 'mailgun' diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 9d858e31a06..8e38f76f1c0 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -1,5 +1,5 @@ """Tests for the Config Entry Flow helper.""" -from unittest.mock import patch +from unittest.mock import patch, Mock import pytest @@ -9,7 +9,7 @@ from tests.common import MockConfigEntry, MockModule @pytest.fixture -def flow_conf(hass): +def discovery_flow_conf(hass): """Register a handler.""" handler_conf = { 'discovered': False, @@ -26,7 +26,18 @@ def flow_conf(hass): yield handler_conf -async def test_single_entry_allowed(hass, flow_conf): +@pytest.fixture +def webhook_flow_conf(hass): + """Register a handler.""" + with patch.dict(config_entries.HANDLERS): + config_entry_flow.register_webhook_flow( + 'test_single', 'Test Single', {}, False) + config_entry_flow.register_webhook_flow( + 'test_multiple', 'Test Multiple', {}, True) + yield {} + + +async def test_single_entry_allowed(hass, discovery_flow_conf): """Test only a single entry is allowed.""" flow = config_entries.HANDLERS['test']() flow.hass = hass @@ -38,7 +49,7 @@ async def test_single_entry_allowed(hass, flow_conf): assert result['reason'] == 'single_instance_allowed' -async def test_user_no_devices_found(hass, flow_conf): +async def test_user_no_devices_found(hass, discovery_flow_conf): """Test if no devices found.""" flow = config_entries.HANDLERS['test']() flow.hass = hass @@ -51,18 +62,18 @@ async def test_user_no_devices_found(hass, flow_conf): assert result['reason'] == 'no_devices_found' -async def test_user_has_confirmation(hass, flow_conf): +async def test_user_has_confirmation(hass, discovery_flow_conf): """Test user requires no confirmation to setup.""" flow = config_entries.HANDLERS['test']() flow.hass = hass - flow_conf['discovered'] = True + discovery_flow_conf['discovered'] = True result = await flow.async_step_user() assert result['type'] == data_entry_flow.RESULT_TYPE_FORM -async def test_discovery_single_instance(hass, flow_conf): +async def test_discovery_single_instance(hass, discovery_flow_conf): """Test we ask for confirmation via discovery.""" flow = config_entries.HANDLERS['test']() flow.hass = hass @@ -74,7 +85,7 @@ async def test_discovery_single_instance(hass, flow_conf): assert result['reason'] == 'single_instance_allowed' -async def test_discovery_confirmation(hass, flow_conf): +async def test_discovery_confirmation(hass, discovery_flow_conf): """Test we ask for confirmation via discovery.""" flow = config_entries.HANDLERS['test']() flow.hass = hass @@ -88,7 +99,7 @@ async def test_discovery_confirmation(hass, flow_conf): assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -async def test_multiple_discoveries(hass, flow_conf): +async def test_multiple_discoveries(hass, discovery_flow_conf): """Test we only create one instance for multiple discoveries.""" loader.set_component(hass, 'test', MockModule('test')) @@ -102,7 +113,7 @@ async def test_multiple_discoveries(hass, flow_conf): assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT -async def test_only_one_in_progress(hass, flow_conf): +async def test_only_one_in_progress(hass, discovery_flow_conf): """Test a user initialized one will finish and cancel discovered one.""" loader.set_component(hass, 'test', MockModule('test')) @@ -127,22 +138,71 @@ async def test_only_one_in_progress(hass, flow_conf): assert len(hass.config_entries.flow.async_progress()) == 0 -async def test_import_no_confirmation(hass, flow_conf): +async def test_import_no_confirmation(hass, discovery_flow_conf): """Test import requires no confirmation to set up.""" flow = config_entries.HANDLERS['test']() flow.hass = hass - flow_conf['discovered'] = True + discovery_flow_conf['discovered'] = True result = await flow.async_step_import(None) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY -async def test_import_single_instance(hass, flow_conf): +async def test_import_single_instance(hass, discovery_flow_conf): """Test import doesn't create second instance.""" flow = config_entries.HANDLERS['test']() flow.hass = hass - flow_conf['discovered'] = True + discovery_flow_conf['discovered'] = True MockConfigEntry(domain='test').add_to_hass(hass) result = await flow.async_step_import(None) assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_webhook_single_entry_allowed(hass, webhook_flow_conf): + """Test only a single entry is allowed.""" + flow = config_entries.HANDLERS['test_single']() + flow.hass = hass + + MockConfigEntry(domain='test_single').add_to_hass(hass) + result = await flow.async_step_user() + + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT + assert result['reason'] == 'one_instance_allowed' + + +async def test_webhook_multiple_entries_allowed(hass, webhook_flow_conf): + """Test multiple entries are allowed when specified.""" + flow = config_entries.HANDLERS['test_multiple']() + flow.hass = hass + + MockConfigEntry(domain='test_multiple').add_to_hass(hass) + hass.config.api = Mock(base_url='http://example.com') + + result = await flow.async_step_user() + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM + + +async def test_webhook_config_flow_aborts_external_url(hass, + webhook_flow_conf): + """Test configuring a webhook without an external url.""" + flow = config_entries.HANDLERS['test_single']() + flow.hass = hass + + hass.config.api = Mock(base_url='http://192.168.1.10') + result = await flow.async_step_user() + + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT + assert result['reason'] == 'not_internet_accessible' + + +async def test_webhook_config_flow_registers_webhook(hass, webhook_flow_conf): + """Test setting up an entry creates a webhook.""" + flow = config_entries.HANDLERS['test_single']() + flow.hass = hass + + hass.config.api = Mock(base_url='http://example.com') + result = await flow.async_step_user(user_input={}) + + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result['data']['webhook_id'] is not None From 7def587c9305646f46ca5f8474bb6d46c7aebfb4 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Tue, 23 Oct 2018 11:33:56 +0100 Subject: [PATCH 017/230] Only strip from the bluetooth name if it isn't None (#17719) This prevents the following traceback that will otherwise occur. Traceback (most recent call last): File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run result = self.fn(*self.args, **self.kwargs) File "/usr/local/lib/python3.6/site-packages/homeassistant/components/device_tracker/bluetooth_le_tracker.py", line 107, in update_ble see_device(address, devs[address], new_device=True) File "/usr/local/lib/python3.6/site-packages/homeassistant/components/device_tracker/bluetooth_le_tracker.py", line 47, in see_device see(mac=BLE_PREFIX + address, host_name=name.strip("\x00"), AttributeError: 'NoneType' object has no attribute 'strip' --- .../components/device_tracker/bluetooth_le_tracker.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 47b86ab9ab2..a07fdfdcf81 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -44,7 +44,10 @@ def setup_scanner(hass, config, see, discovery_info=None): new_devices[address] = 1 return - see(mac=BLE_PREFIX + address, host_name=name.strip("\x00"), + if name is not None: + name = name.strip("\x00") + + see(mac=BLE_PREFIX + address, host_name=name, source_type=SOURCE_TYPE_BLUETOOTH_LE) def discover_ble_devices(): From 0723c7f5dc8479dfb7e6d4a59d2bb2a7f9cccc52 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Tue, 23 Oct 2018 13:03:01 +0200 Subject: [PATCH 018/230] Just use debug instead of error if the binary_sensor does not get data (#17720) --- homeassistant/components/openuv/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index a45d9ceb0d6..52cf0ba75d5 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -210,7 +210,7 @@ class OpenUV: if data.get('from_time') and data.get('to_time'): self.data[DATA_PROTECTION_WINDOW] = data else: - _LOGGER.error( + _LOGGER.debug( 'No valid protection window data for this location') self.data[DATA_PROTECTION_WINDOW] = {} From cf0bd6470aa30cdf94b9d6b2f9aa01ea954157be Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Oct 2018 14:03:38 +0200 Subject: [PATCH 019/230] Update frontend to 20181023.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 36bb3507dda..55aa0700bef 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181021.0'] +REQUIREMENTS = ['home-assistant-frontend==20181023.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index ffd2be0b3bc..26505b2aad2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -469,7 +469,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181021.0 +home-assistant-frontend==20181023.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fe02cb0fab1..d19fd5afa87 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181021.0 +home-assistant-frontend==20181023.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 398ea40189c5b685efc7f73b2cf136990fa8b776 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 23 Oct 2018 14:04:25 +0200 Subject: [PATCH 020/230] Update translations --- .../components/ifttt/.translations/tr.json | 5 +++ .../components/mailgun/.translations/en.json | 32 +++++++++---------- .../components/mailgun/.translations/lb.json | 18 +++++++++++ .../components/mqtt/.translations/tr.json | 11 +++++++ .../simplisafe/.translations/nl.json | 19 +++++++++++ .../simplisafe/.translations/tr.json | 12 +++++++ .../components/smhi/.translations/tr.json | 16 ++++++++++ .../components/unifi/.translations/nl.json | 23 +++++++++++++ .../components/unifi/.translations/tr.json | 12 +++++++ .../components/upnp/.translations/tr.json | 11 +++++++ .../components/zwave/.translations/tr.json | 11 +++++++ 11 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/ifttt/.translations/tr.json create mode 100644 homeassistant/components/mailgun/.translations/lb.json create mode 100644 homeassistant/components/mqtt/.translations/tr.json create mode 100644 homeassistant/components/simplisafe/.translations/nl.json create mode 100644 homeassistant/components/simplisafe/.translations/tr.json create mode 100644 homeassistant/components/smhi/.translations/tr.json create mode 100644 homeassistant/components/unifi/.translations/nl.json create mode 100644 homeassistant/components/unifi/.translations/tr.json create mode 100644 homeassistant/components/upnp/.translations/tr.json create mode 100644 homeassistant/components/zwave/.translations/tr.json diff --git a/homeassistant/components/ifttt/.translations/tr.json b/homeassistant/components/ifttt/.translations/tr.json new file mode 100644 index 00000000000..80188b637f9 --- /dev/null +++ b/homeassistant/components/ifttt/.translations/tr.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "IFTT" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/en.json b/homeassistant/components/mailgun/.translations/en.json index 0e993bef5d4..3abb8aba726 100644 --- a/homeassistant/components/mailgun/.translations/en.json +++ b/homeassistant/components/mailgun/.translations/en.json @@ -1,18 +1,18 @@ { - "config": { - "title": "Mailgun", - "step": { - "user": { - "title": "Set up the Mailgun Webhook", - "description": "Are you sure you want to set up Mailgun?" - } - }, - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages." - }, - "create_entry": { - "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + "config": { + "abort": { + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages.", + "one_instance_allowed": "Only a single instance is necessary." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + }, + "step": { + "user": { + "description": "Are you sure you want to set up Mailgun?", + "title": "Set up the Mailgun Webhook" + } + }, + "title": "Mailgun" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/lb.json b/homeassistant/components/mailgun/.translations/lb.json new file mode 100644 index 00000000000..f84225444d9 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Mailgun Noriichten z'empf\u00e4nken.", + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + }, + "create_entry": { + "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." + }, + "step": { + "user": { + "description": "S\u00e9cher fir Mailgun anzeriichten?", + "title": "Mailgun Webhook ariichten" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/tr.json b/homeassistant/components/mqtt/.translations/tr.json new file mode 100644 index 00000000000..1b73b94d5a4 --- /dev/null +++ b/homeassistant/components/mqtt/.translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "hassio_confirm": { + "data": { + "discovery": "Ke\u015ffetmeyi etkinle\u015ftir" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/nl.json b/homeassistant/components/simplisafe/.translations/nl.json new file mode 100644 index 00000000000..c84593c0b23 --- /dev/null +++ b/homeassistant/components/simplisafe/.translations/nl.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "identifier_exists": "Account bestaat al", + "invalid_credentials": "Ongeldige gebruikersgegevens" + }, + "step": { + "user": { + "data": { + "code": "Code (voor Home Assistant)", + "password": "Wachtwoord", + "username": "E-mailadres" + }, + "title": "Vul uw gegevens in" + } + }, + "title": "SimpliSafe" + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/.translations/tr.json b/homeassistant/components/simplisafe/.translations/tr.json new file mode 100644 index 00000000000..ec84b1b7c1c --- /dev/null +++ b/homeassistant/components/simplisafe/.translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Parola", + "username": "E-posta adresi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/tr.json b/homeassistant/components/smhi/.translations/tr.json new file mode 100644 index 00000000000..bb50f1e2a8d --- /dev/null +++ b/homeassistant/components/smhi/.translations/tr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "name_exists": "Bu ad zaten var", + "wrong_location": "Konum sadece \u0130sve\u00e7" + }, + "step": { + "user": { + "data": { + "latitude": "Enlem", + "longitude": "Boylam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/nl.json b/homeassistant/components/unifi/.translations/nl.json new file mode 100644 index 00000000000..8e87dc4b2a6 --- /dev/null +++ b/homeassistant/components/unifi/.translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "user_privilege": "Gebruiker moet beheerder zijn" + }, + "error": { + "faulty_credentials": "Foutieve gebruikersgegevens", + "service_unavailable": "Geen service beschikbaar" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" + }, + "title": "Stel de UniFi-controller in" + } + }, + "title": "UniFi-controller" + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/tr.json b/homeassistant/components/unifi/.translations/tr.json new file mode 100644 index 00000000000..667a5e676fb --- /dev/null +++ b/homeassistant/components/unifi/.translations/tr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Parola", + "username": "Kullan\u0131c\u0131 ad\u0131" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/tr.json b/homeassistant/components/upnp/.translations/tr.json new file mode 100644 index 00000000000..91503c17a07 --- /dev/null +++ b/homeassistant/components/upnp/.translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "enable_sensors": "Trafik sens\u00f6rleri ekleyin" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/tr.json b/homeassistant/components/zwave/.translations/tr.json new file mode 100644 index 00000000000..c9762784d52 --- /dev/null +++ b/homeassistant/components/zwave/.translations/tr.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "network_key": "A\u011f Anajtar\u0131 (otomatik \u00fcretilmesi i\u00e7in bo\u015f b\u0131rak\u0131n\u0131z)" + } + } + } + } +} \ No newline at end of file From 37a667c2dea174a5323f941bf14c1da79c6fc98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Tue, 23 Oct 2018 14:06:42 +0200 Subject: [PATCH 021/230] clean up clicksend (#17723) --- homeassistant/components/notify/clicksend.py | 63 +++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/notify/clicksend.py b/homeassistant/components/notify/clicksend.py index 5506d6ed6d0..faf30ac7cc6 100644 --- a/homeassistant/components/notify/clicksend.py +++ b/homeassistant/components/notify/clicksend.py @@ -7,51 +7,41 @@ https://home-assistant.io/components/notify.clicksend/ import json import logging -from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol +from aiohttp.hdrs import CONTENT_TYPE +import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( CONF_API_KEY, CONF_RECIPIENT, CONF_SENDER, CONF_USERNAME, CONTENT_TYPE_JSON) -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) BASE_API_URL = 'https://rest.clicksend.com/v3' - DEFAULT_SENDER = 'hass' +TIMEOUT = 5 HEADERS = {CONTENT_TYPE: CONTENT_TYPE_JSON} -def validate_sender(config): - """Set the optional sender name if sender name is not provided.""" - if CONF_SENDER in config: - return config - config[CONF_SENDER] = DEFAULT_SENDER - return config - - PLATFORM_SCHEMA = vol.Schema( vol.All(PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_RECIPIENT, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_SENDER): cv.string, - }), validate_sender)) + vol.Optional(CONF_SENDER, default=DEFAULT_SENDER): cv.string, + }),)) def get_service(hass, config, discovery_info=None): """Get the ClickSend notification service.""" - print("#### ", config) - if _authenticate(config) is False: - _LOGGER.exception("You are not authorized to access ClickSend") + if not _authenticate(config): + _LOGGER.error("You are not authorized to access ClickSend") return None - return ClicksendNotificationService(config) @@ -60,10 +50,10 @@ class ClicksendNotificationService(BaseNotificationService): def __init__(self, config): """Initialize the service.""" - self.username = config.get(CONF_USERNAME) - self.api_key = config.get(CONF_API_KEY) - self.recipients = config.get(CONF_RECIPIENT) - self.sender = config.get(CONF_SENDER) + self.username = config[CONF_USERNAME] + self.api_key = config[CONF_API_KEY] + self.recipients = config[CONF_RECIPIENT] + self.sender = config[CONF_SENDER] def send_message(self, message="", **kwargs): """Send a message to a user.""" @@ -77,28 +67,29 @@ class ClicksendNotificationService(BaseNotificationService): }) api_url = "{}/sms/send".format(BASE_API_URL) - - resp = requests.post( - api_url, data=json.dumps(data), headers=HEADERS, - auth=(self.username, self.api_key), timeout=5) + resp = requests.post(api_url, + data=json.dumps(data), + headers=HEADERS, + auth=(self.username, self.api_key), + timeout=TIMEOUT) + if resp.status_code == 200: + return obj = json.loads(resp.text) - response_msg = obj['response_msg'] - response_code = obj['response_code'] - - if resp.status_code != 200: - _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - response_msg, response_code) + response_msg = obj.get('response_msg') + response_code = obj.get('response_code') + _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, + response_msg, response_code) def _authenticate(config): """Authenticate with ClickSend.""" api_url = '{}/account'.format(BASE_API_URL) - resp = requests.get( - api_url, headers=HEADERS, auth=(config.get(CONF_USERNAME), - config.get(CONF_API_KEY)), timeout=5) - + resp = requests.get(api_url, + headers=HEADERS, + auth=(config[CONF_USERNAME], + config[CONF_API_KEY]), + timeout=TIMEOUT) if resp.status_code != 200: return False - return True From 4a757b79945584e38983ba7822062ddbd36c82e1 Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Tue, 23 Oct 2018 06:09:08 -0600 Subject: [PATCH 022/230] Set available property (#17706) Will set the available property to False if unable to communicate with August lock or doorbell. HTTP request errors (i.e. timeout, connection error, HTTP error) will not result in traceback. Instead an error will be logged. --- homeassistant/components/august.py | 51 ++++++++++++++++--- .../components/binary_sensor/august.py | 17 +++++-- homeassistant/components/lock/august.py | 8 +++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/august.py b/homeassistant/components/august.py index 850d972c373..ce8e3d8de11 100644 --- a/homeassistant/components/august.py +++ b/homeassistant/components/august.py @@ -225,8 +225,17 @@ class AugustData: for doorbell in self._doorbells: _LOGGER.debug("Updating status for %s", doorbell.device_name) - detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail( - self._access_token, doorbell.device_id) + try: + detail_by_id[doorbell.device_id] =\ + self._api.get_doorbell_detail( + self._access_token, doorbell.device_id) + except RequestException as ex: + _LOGGER.error("Request error trying to retrieve doorbell" + " status for %s. %s", doorbell.device_name, ex) + detail_by_id[doorbell.device_id] = None + except Exception: + detail_by_id[doorbell.device_id] = None + raise _LOGGER.debug("Completed retrieving doorbell details") self._doorbell_detail_by_id = detail_by_id @@ -260,8 +269,17 @@ class AugustData: for lock in self._locks: _LOGGER.debug("Updating status for %s", lock.device_name) - state_by_id[lock.device_id] = self._api.get_lock_door_status( - self._access_token, lock.device_id) + + try: + state_by_id[lock.device_id] = self._api.get_lock_door_status( + self._access_token, lock.device_id) + except RequestException as ex: + _LOGGER.error("Request error trying to retrieve door" + " status for %s. %s", lock.device_name, ex) + state_by_id[lock.device_id] = None + except Exception: + state_by_id[lock.device_id] = None + raise _LOGGER.debug("Completed retrieving door status") self._door_state_by_id = state_by_id @@ -275,10 +293,27 @@ class AugustData: for lock in self._locks: _LOGGER.debug("Updating status for %s", lock.device_name) - status_by_id[lock.device_id] = self._api.get_lock_status( - self._access_token, lock.device_id) - detail_by_id[lock.device_id] = self._api.get_lock_detail( - self._access_token, lock.device_id) + try: + status_by_id[lock.device_id] = self._api.get_lock_status( + self._access_token, lock.device_id) + except RequestException as ex: + _LOGGER.error("Request error trying to retrieve door" + " status for %s. %s", lock.device_name, ex) + status_by_id[lock.device_id] = None + except Exception: + status_by_id[lock.device_id] = None + raise + + try: + detail_by_id[lock.device_id] = self._api.get_lock_detail( + self._access_token, lock.device_id) + except RequestException as ex: + _LOGGER.error("Request error trying to retrieve door" + " details for %s. %s", lock.device_name, ex) + detail_by_id[lock.device_id] = None + except Exception: + detail_by_id[lock.device_id] = None + raise _LOGGER.debug("Completed retrieving locks status") self._lock_status_by_id = status_by_id diff --git a/homeassistant/components/binary_sensor/august.py b/homeassistant/components/binary_sensor/august.py index 55b31a6da5f..4116a791b01 100644 --- a/homeassistant/components/binary_sensor/august.py +++ b/homeassistant/components/binary_sensor/august.py @@ -19,14 +19,15 @@ SCAN_INTERVAL = timedelta(seconds=5) def _retrieve_door_state(data, lock): """Get the latest state of the DoorSense sensor.""" - from august.lock import LockDoorStatus - doorstate = data.get_door_state(lock.device_id) - return doorstate == LockDoorStatus.OPEN + return data.get_door_state(lock.device_id) def _retrieve_online_state(data, doorbell): """Get the latest state of the sensor.""" detail = data.get_doorbell_detail(doorbell.device_id) + if detail is None: + return None + return detail.is_online @@ -138,9 +139,10 @@ class AugustDoorBinarySensor(BinarySensorDevice): """Get the latest state of the sensor.""" state_provider = SENSOR_TYPES_DOOR[self._sensor_type][2] self._state = state_provider(self._data, self._door) + self._available = self._state is not None from august.lock import LockDoorStatus - self._available = self._state != LockDoorStatus.UNKNOWN + self._state = self._state == LockDoorStatus.OPEN class AugustDoorbellBinarySensor(BinarySensorDevice): @@ -152,6 +154,12 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): self._sensor_type = sensor_type self._doorbell = doorbell self._state = None + self._available = False + + @property + def available(self): + """Return the availability of this sensor.""" + return self._available @property def is_on(self): @@ -173,3 +181,4 @@ class AugustDoorbellBinarySensor(BinarySensorDevice): """Get the latest state of the sensor.""" state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][2] self._state = state_provider(self._data, self._doorbell) + self._available = self._state is not None diff --git a/homeassistant/components/lock/august.py b/homeassistant/components/lock/august.py index e8949255ee9..ce6792ceb39 100644 --- a/homeassistant/components/lock/august.py +++ b/homeassistant/components/lock/august.py @@ -40,6 +40,7 @@ class AugustLock(LockDevice): self._lock_status = None self._lock_detail = None self._changed_by = None + self._available = False def lock(self, **kwargs): """Lock the device.""" @@ -52,6 +53,8 @@ class AugustLock(LockDevice): def update(self): """Get the latest state of the sensor.""" self._lock_status = self._data.get_lock_status(self._lock.device_id) + self._available = self._lock_status is not None + self._lock_detail = self._data.get_lock_detail(self._lock.device_id) from august.activity import ActivityType @@ -67,6 +70,11 @@ class AugustLock(LockDevice): """Return the name of this device.""" return self._lock.device_name + @property + def available(self): + """Return the availability of this sensor.""" + return self._available + @property def is_locked(self): """Return true if device is on.""" From f6f549dc3cce6ae7882fde523ddedd68d34d6413 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Tue, 23 Oct 2018 14:15:56 +0200 Subject: [PATCH 023/230] Removes re-init (#17724) --- homeassistant/components/device_tracker/asuswrt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 461380b2c8e..2ac3aaee933 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -67,8 +67,6 @@ class AsusWrtDeviceScanner(DeviceScanner): async def async_connect(self): """Initialize connection to the router.""" - self.last_results = {} - # Test the router is accessible. data = await self.connection.async_get_connected_devices() self.success_init = data is not None From 9798ff019f5f8ef4a61fd6eaff086d2d8f4d9a9e Mon Sep 17 00:00:00 2001 From: Andrey Kupreychik Date: Tue, 23 Oct 2018 19:21:03 +0700 Subject: [PATCH 024/230] Don't call off_delay_listener if not needed (#17712) Don't call off_delay_listener if 'OFF' is actually received Moved `off_delay_listener` to be defined once --- .../components/binary_sensor/mqtt.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index beaeb9ce21b..db9ad585999 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -131,7 +131,14 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate, await MqttDiscoveryUpdate.async_added_to_hass(self) @callback - def state_message_received(topic, payload, qos): + def off_delay_listener(now): + """Switch device off after a delay.""" + self._delay_listener = None + self._state = False + self.async_schedule_update_ha_state() + + @callback + def state_message_received(_topic, payload, _qos): """Handle a new received MQTT state message.""" if self._template is not None: payload = self._template.async_render_with_possible_json_value( @@ -146,17 +153,10 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate, self._name, self._state_topic) return + if self._delay_listener is not None: + self._delay_listener() + if (self._state and self._off_delay is not None): - @callback - def off_delay_listener(now): - """Switch device off after a delay.""" - self._delay_listener = None - self._state = False - self.async_schedule_update_ha_state() - - if self._delay_listener is not None: - self._delay_listener() - self._delay_listener = evt.async_call_later( self.hass, self._off_delay, off_delay_listener) From f9973696f3139231fbe23ac5ff798434ac1e3a96 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 23 Oct 2018 17:39:17 +0200 Subject: [PATCH 025/230] Rename readthedocs file (#17718) * Rename file * Add requirements file --- readthedocs.yml => .readthedocs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename readthedocs.yml => .readthedocs.yml (50%) diff --git a/readthedocs.yml b/.readthedocs.yml similarity index 50% rename from readthedocs.yml rename to .readthedocs.yml index 6a06f655513..923a03f03dd 100644 --- a/readthedocs.yml +++ b/.readthedocs.yml @@ -5,4 +5,6 @@ build: python: version: 3.6 - setup_py_install: true \ No newline at end of file + setup_py_install: true + +requirements_file: requirements_docs.txt From 7be7a8de309fdfc39cd409f24fa9503afd11695e Mon Sep 17 00:00:00 2001 From: jxwolstenholme Date: Tue, 23 Oct 2018 17:35:21 +0100 Subject: [PATCH 026/230] Add device tracking for the BT Smart Hub router (#17158) * Support for BT smarthub router device tracking * Update requirements_all.txt for bt_smarthub device tracker * Added bt_smarthub.py exclusion * Update .coveragerc * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Fix linting issues * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update bt_smarthub.py * Update .coveragerc * Update bt_smarthub.py * Minor changes * Update bt_smarthub.py * Update bt_smarthub.py --- .coveragerc | 1 + .../components/device_tracker/bt_smarthub.py | 97 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 101 insertions(+) create mode 100644 homeassistant/components/device_tracker/bt_smarthub.py diff --git a/.coveragerc b/.coveragerc index 25aa405035b..1da53f21cb1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -466,6 +466,7 @@ omit = homeassistant/components/device_tracker/bluetooth_le_tracker.py homeassistant/components/device_tracker/bluetooth_tracker.py homeassistant/components/device_tracker/bt_home_hub_5.py + homeassistant/components/device_tracker/bt_smarthub.py homeassistant/components/device_tracker/cisco_ios.py homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/freebox.py diff --git a/homeassistant/components/device_tracker/bt_smarthub.py b/homeassistant/components/device_tracker/bt_smarthub.py new file mode 100644 index 00000000000..e7d60aaed6d --- /dev/null +++ b/homeassistant/components/device_tracker/bt_smarthub.py @@ -0,0 +1,97 @@ +""" +Support for BT Smart Hub (Sometimes referred to as BT Home Hub 6). + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.bt_smarthub/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + DOMAIN, PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import CONF_HOST + +REQUIREMENTS = ['btsmarthub_devicelist==0.1.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_DEFAULT_IP = '192.168.1.254' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, +}) + + +def get_scanner(hass, config): + """Return a BT Smart Hub scanner if successful.""" + scanner = BTSmartHubScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class BTSmartHubScanner(DeviceScanner): + """This class queries a BT Smart Hub.""" + + def __init__(self, config): + """Initialise the scanner.""" + _LOGGER.debug("Initialising BT Smart Hub") + self.host = config[CONF_HOST] + self.last_results = {} + self.success_init = False + + # Test the router is accessible + data = self.get_bt_smarthub_data() + if data: + self.success_init = True + else: + _LOGGER.info("Failed to connect to %s", self.host) + + def scan_devices(self): + """Scan for new devices and return a list with found device IDs.""" + self._update_info() + return [client['mac'] for client in self.last_results] + + def get_device_name(self, device): + """Return the name of the given device or None if we don't know.""" + if not self.last_results: + return None + for client in self.last_results: + if client['mac'] == device: + return client['host'] + return None + + def _update_info(self): + """Ensure the information from the BT Smart Hub is up to date.""" + if not self.success_init: + return + + _LOGGER.info("Scanning") + data = self.get_bt_smarthub_data() + if not data: + _LOGGER.warning("Error scanning devices") + return + + clients = [client for client in data.values()] + self.last_results = clients + + def get_bt_smarthub_data(self): + """Retrieve data from BT Smart Hub and return parsed result.""" + import btsmarthub_devicelist + # Request data from bt smarthub into a list of dicts. + data = btsmarthub_devicelist.get_devicelist( + router_ip=self.host, only_active_devices=True) + # Renaming keys from parsed result. + devices = {} + for device in data: + try: + devices[device['UserHostName']] = { + 'ip': device['IPAddress'], + 'mac': device['PhysAddress'], + 'host': device['UserHostName'], + 'status': device['Active'] + } + except KeyError: + pass + return devices diff --git a/requirements_all.txt b/requirements_all.txt index 26505b2aad2..d67e512af62 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -231,6 +231,9 @@ bt_proximity==0.1.2 # homeassistant.components.device_tracker.bt_home_hub_5 bthomehub5-devicelist==0.1.1 +# homeassistant.components.device_tracker.bt_smarthub +btsmarthub_devicelist==0.1.1 + # homeassistant.components.sensor.buienradar # homeassistant.components.weather.buienradar buienradar==0.91 From 65a8882426dd3e381fd6ae80c6c3d5d1c6c63dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 23 Oct 2018 22:06:37 +0300 Subject: [PATCH 027/230] Upgrade pytest to 3.9.2 (#17736) --- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 492708cd904..54c4e25d011 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,5 +13,5 @@ pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 pytest-timeout==1.3.2 -pytest==3.9.1 +pytest==3.9.2 requests_mock==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d19fd5afa87..5477664db61 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -14,7 +14,7 @@ pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 pytest-timeout==1.3.2 -pytest==3.9.1 +pytest==3.9.2 requests_mock==1.5.2 From 2734a30f37635270bdafdf4b307f2184c8e464c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 23 Oct 2018 22:06:58 +0300 Subject: [PATCH 028/230] Upgrade mypy to 0.641 (#17734) --- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 54c4e25d011..5d5d1c4fd04 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ coveralls==1.2.0 flake8-docstrings==1.3.0 flake8==3.5 mock-open==1.3.1 -mypy==0.630 +mypy==0.641 pydocstyle==2.1.1 pylint==2.1.1 pytest-aiohttp==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5477664db61..cb3f008c4a9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ coveralls==1.2.0 flake8-docstrings==1.3.0 flake8==3.5 mock-open==1.3.1 -mypy==0.630 +mypy==0.641 pydocstyle==2.1.1 pylint==2.1.1 pytest-aiohttp==0.3.0 From 0f69be117f91367ab755189a83cdc23b54351549 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 23 Oct 2018 21:48:35 +0200 Subject: [PATCH 029/230] Lovelace ws: add card (#17730) * Change set to update * Add 'add card' * Woof. --- homeassistant/components/lovelace/__init__.py | 138 ++++++++++++++---- tests/components/lovelace/test_init.py | 71 +++++++-- 2 files changed, 168 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 2c28b52ec6e..141f3c98334 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -18,10 +18,15 @@ REQUIREMENTS = ['ruamel.yaml==0.15.72'] LOVELACE_CONFIG_FILE = 'ui-lovelace.yaml' JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name +FORMAT_YAML = 'yaml' +FORMAT_JSON = 'json' + OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' + WS_TYPE_GET_CARD = 'lovelace/config/card/get' -WS_TYPE_SET_CARD = 'lovelace/config/card/set' +WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' +WS_TYPE_ADD_CARD = 'lovelace/config/card/add' SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, @@ -31,14 +36,25 @@ SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_CARD, vol.Required('card_id'): str, - vol.Optional('format', default='yaml'): str, + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), }) -SCHEMA_SET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_SET_CARD, +SCHEMA_UPDATE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_UPDATE_CARD, vol.Required('card_id'): str, vol.Required('card_config'): vol.Any(str, Dict), - vol.Optional('format', default='yaml'): str, + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), +}) + +SCHEMA_ADD_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_ADD_CARD, + vol.Required('view_id'): str, + vol.Required('card_config'): vol.Any(str, Dict), + vol.Optional('position'): int, + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), }) @@ -50,6 +66,10 @@ class CardNotFoundError(HomeAssistantError): """Card not found in data.""" +class ViewNotFoundError(HomeAssistantError): + """View not found in data.""" + + class UnsupportedYamlError(HomeAssistantError): """Unsupported YAML.""" @@ -161,37 +181,61 @@ def yaml_to_object(data: str) -> JSON_TYPE: raise HomeAssistantError(exc) -def get_card(fname: str, card_id: str, data_format: str) -> JSON_TYPE: +def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\ + -> JSON_TYPE: """Load a specific card config for id.""" config = load_yaml(fname) for view in config.get('views', []): for card in view.get('cards', []): - if card.get('id') == card_id: - if data_format == 'yaml': - return object_to_yaml(card) - return card + if card.get('id') != card_id: + continue + if data_format == FORMAT_YAML: + return object_to_yaml(card) + return card raise CardNotFoundError( "Card with ID: {} was not found in {}.".format(card_id, fname)) -def set_card(fname: str, card_id: str, card_config: str, data_format: str)\ - -> bool: +def update_card(fname: str, card_id: str, card_config: str, + data_format: str = FORMAT_YAML): """Save a specific card config for id.""" config = load_yaml(fname) for view in config.get('views', []): for card in view.get('cards', []): - if card.get('id') == card_id: - if data_format == 'yaml': - card_config = yaml_to_object(card_config) - card.update(card_config) - save_yaml(fname, config) - return True + if card.get('id') != card_id: + continue + if data_format == FORMAT_YAML: + card_config = yaml_to_object(card_config) + card.update(card_config) + save_yaml(fname, config) + return raise CardNotFoundError( "Card with ID: {} was not found in {}.".format(card_id, fname)) +def add_card(fname: str, view_id: str, card_config: str, + position: int = None, data_format: str = FORMAT_YAML): + """Add a card to a view.""" + config = load_yaml(fname) + for view in config.get('views', []): + if view.get('id') != view_id: + continue + cards = view.get('cards', []) + if data_format == FORMAT_YAML: + card_config = yaml_to_object(card_config) + if position is None: + cards.append(card_config) + else: + cards.insert(position, card_config) + save_yaml(fname, config) + return + + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + + async def async_setup(hass, config): """Set up the Lovelace commands.""" # Backwards compat. Added in 0.80. Remove after 0.85 @@ -208,8 +252,12 @@ async def async_setup(hass, config): SCHEMA_GET_CARD) hass.components.websocket_api.async_register_command( - WS_TYPE_SET_CARD, websocket_lovelace_set_card, - SCHEMA_SET_CARD) + WS_TYPE_UPDATE_CARD, websocket_lovelace_update_card, + SCHEMA_UPDATE_CARD) + + hass.components.websocket_api.async_register_command( + WS_TYPE_ADD_CARD, websocket_lovelace_add_card, + SCHEMA_ADD_CARD) return True @@ -245,7 +293,7 @@ async def websocket_lovelace_get_card(hass, connection, msg): try: card = await hass.async_add_executor_job( get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], - msg.get('format', 'yaml')) + msg.get('format', FORMAT_YAML)) message = websocket_api.result_message( msg['id'], card ) @@ -254,9 +302,8 @@ async def websocket_lovelace_get_card(hass, connection, msg): 'Could not find ui-lovelace.yaml in your config dir.') except UnsupportedYamlError as err: error = 'unsupported_error', str(err) - except CardNotFoundError: - error = ('card_not_found', - 'Could not find card in ui-lovelace.yaml.') + except CardNotFoundError as err: + error = 'card_not_found', str(err) except HomeAssistantError as err: error = 'load_error', str(err) @@ -267,24 +314,51 @@ async def websocket_lovelace_get_card(hass, connection, msg): @websocket_api.async_response -async def websocket_lovelace_set_card(hass, connection, msg): +async def websocket_lovelace_update_card(hass, connection, msg): """Receive lovelace card config over websocket and save.""" error = None try: - result = await hass.async_add_executor_job( - set_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id'], msg['card_config'], msg.get('format', 'yaml')) + await hass.async_add_executor_job( + update_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML)) message = websocket_api.result_message( - msg['id'], result + msg['id'], True ) except FileNotFoundError: error = ('file_not_found', 'Could not find ui-lovelace.yaml in your config dir.') except UnsupportedYamlError as err: error = 'unsupported_error', str(err) - except CardNotFoundError: - error = ('card_not_found', - 'Could not find card in ui-lovelace.yaml.') + except CardNotFoundError as err: + error = 'card_not_found', str(err) + except HomeAssistantError as err: + error = 'save_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) + + +@websocket_api.async_response +async def websocket_lovelace_add_card(hass, connection, msg): + """Add new card to view over websocket and save.""" + error = None + try: + await hass.async_add_executor_job( + add_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_id'], msg['card_config'], msg.get('position'), + msg.get('format', FORMAT_YAML)) + message = websocket_api.result_message( + msg['id'], True + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except ViewNotFoundError as err: + error = 'view_not_found', str(err) except HomeAssistantError as err: error = 'save_error', str(err) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index c637267cc7e..1ce0f9ff602 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -370,8 +370,8 @@ async def test_lovelace_get_card_bad_yaml(hass, hass_ws_client): assert msg['error']['code'] == 'load_error' -async def test_lovelace_set_card(hass, hass_ws_client): - """Test set_card command.""" +async def test_lovelace_update_card(hass, hass_ws_client): + """Test update_card command.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -382,7 +382,7 @@ async def test_lovelace_set_card(hass, hass_ws_client): as save_yaml_mock: await client.send_json({ 'id': 5, - 'type': 'lovelace/config/card/set', + 'type': 'lovelace/config/card/update', 'card_id': 'test', 'card_config': 'id: test\ntype: glance\n', }) @@ -396,8 +396,8 @@ async def test_lovelace_set_card(hass, hass_ws_client): assert msg['success'] -async def test_lovelace_set_card_not_found(hass, hass_ws_client): - """Test set_card command cannot find card.""" +async def test_lovelace_update_card_not_found(hass, hass_ws_client): + """Test update_card command cannot find card.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -406,7 +406,7 @@ async def test_lovelace_set_card_not_found(hass, hass_ws_client): return_value=yaml.load(TEST_YAML_A)): await client.send_json({ 'id': 5, - 'type': 'lovelace/config/card/set', + 'type': 'lovelace/config/card/update', 'card_id': 'not_found', 'card_config': 'id: test\ntype: glance\n', }) @@ -418,8 +418,8 @@ async def test_lovelace_set_card_not_found(hass, hass_ws_client): assert msg['error']['code'] == 'card_not_found' -async def test_lovelace_set_card_bad_yaml(hass, hass_ws_client): - """Test set_card command bad yaml.""" +async def test_lovelace_update_card_bad_yaml(hass, hass_ws_client): + """Test update_card command bad yaml.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -430,7 +430,7 @@ async def test_lovelace_set_card_bad_yaml(hass, hass_ws_client): side_effect=HomeAssistantError): await client.send_json({ 'id': 5, - 'type': 'lovelace/config/card/set', + 'type': 'lovelace/config/card/update', 'card_id': 'test', 'card_config': 'id: test\ntype: glance\n', }) @@ -440,3 +440,56 @@ async def test_lovelace_set_card_bad_yaml(hass, hass_ws_client): assert msg['type'] == TYPE_RESULT assert msg['success'] is False assert msg['error']['code'] == 'save_error' + + +async def test_lovelace_add_card(hass, hass_ws_client): + """Test add_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/add', + 'view_id': 'example', + 'card_config': 'id: test\ntype: added\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 0, 'cards', 2, 'type'], + list_ok=True) == 'added' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_add_card_position(hass, hass_ws_client): + """Test add_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/add', + 'view_id': 'example', + 'position': 0, + 'card_config': 'id: test\ntype: added\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 0, 'cards', 0, 'type'], + list_ok=True) == 'added' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] From fc8af22191d054524c9e124a5fed7ebade32f16c Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Tue, 23 Oct 2018 21:52:01 +0200 Subject: [PATCH 030/230] IGD review fixes (#17400) * Fix discovery-dependency for upnp * Only delete port mappings on EVENT_HOMEASSISTANT_STOP + refactoring MockDevice * Call and store local_ip from async_setup * Don't depend on http-component * Remove discovery dependency --- homeassistant/components/upnp/__init__.py | 59 +++++++---- homeassistant/components/upnp/device.py | 9 +- tests/components/upnp/test_init.py | 117 ++++++++++++++-------- 3 files changed, 118 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 07c8d5f748e..c667327b71b 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers import dispatcher from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.components.discovery import DOMAIN as DISCOVERY_DOMAIN +from homeassistant.util import get_local_ip from .const import ( CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS, @@ -31,7 +31,6 @@ from .device import Device REQUIREMENTS = ['async-upnp-client==0.12.7'] -DEPENDENCIES = ['http'] NOTIFICATION_ID = 'upnp_notification' NOTIFICATION_TITLE = 'UPnP/IGD Setup' @@ -50,18 +49,37 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -def _substitute_hass_ports(ports, hass_port): - """Substitute 'hass' for the hass_port.""" +def _substitute_hass_ports(ports, hass_port=None): + """ + Substitute 'hass' for the hass_port. + + This triggers a warning when hass_port is None. + """ ports = ports.copy() # substitute 'hass' for hass_port, both keys and values if CONF_HASS in ports: - ports[hass_port] = ports[CONF_HASS] + if hass_port is None: + _LOGGER.warning( + 'Could not determine Home Assistant http port, ' + 'not setting up port mapping from %s to %s. ' + 'Enable the http-component.', + CONF_HASS, ports[CONF_HASS]) + else: + ports[hass_port] = ports[CONF_HASS] del ports[CONF_HASS] for port in ports: if ports[port] == CONF_HASS: - ports[port] = hass_port + if hass_port is None: + _LOGGER.warning( + 'Could not determine Home Assistant http port, ' + 'not setting up port mapping from %s to %s. ' + 'Enable the http-component.', + port, ports[port]) + del ports[port] + else: + ports[port] = hass_port return ports @@ -74,15 +92,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): # ensure sane config if DOMAIN not in config: return True - - if DISCOVERY_DOMAIN not in config: - _LOGGER.warning('UPNP needs discovery, please enable it') - return False + upnp_config = config[DOMAIN] # overridden local ip - upnp_config = config[DOMAIN] if CONF_LOCAL_IP in upnp_config: hass.data[DOMAIN]['local_ip'] = upnp_config[CONF_LOCAL_IP] + else: + hass.data[DOMAIN]['local_ip'] = \ + await hass.async_add_executor_job(get_local_ip) # determine ports ports = {CONF_HASS: CONF_HASS} # default, port_mapping disabled by default @@ -119,13 +136,15 @@ async def async_setup_entry(hass: HomeAssistantType, # port mapping if data.get(CONF_ENABLE_PORT_MAPPING): - local_ip = hass.data[DOMAIN].get('local_ip') + local_ip = hass.data[DOMAIN]['local_ip'] ports = hass.data[DOMAIN]['auto_config']['ports'] _LOGGER.debug('Enabling port mappings: %s', ports) - hass_port = hass.http.server_port - ports = _substitute_hass_ports(ports, hass_port) - await device.async_add_port_mappings(ports, local_ip=local_ip) + hass_port = None + if hasattr(hass, 'http'): + hass_port = hass.http.server_port + ports = _substitute_hass_ports(ports, hass_port=hass_port) + await device.async_add_port_mappings(ports, local_ip) # sensors if data.get(CONF_ENABLE_SENSORS): @@ -135,10 +154,12 @@ async def async_setup_entry(hass: HomeAssistantType, hass.async_create_task(hass.config_entries.async_forward_entry_setup( config_entry, 'sensor')) - async def unload_entry(event): - """Unload entry on quit.""" - await async_unload_entry(hass, config_entry) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unload_entry) + async def delete_port_mapping(event): + """Delete port mapping on quit.""" + if data.get(CONF_ENABLE_PORT_MAPPING): + _LOGGER.debug('Deleting port mappings') + await device.async_delete_port_mappings() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, delete_port_mapping) return True diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 4a444aa3087..4a0b7b61dd4 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -6,7 +6,6 @@ import aiohttp from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util import get_local_ip from .const import LOGGER as _LOGGER @@ -51,15 +50,13 @@ class Device: """Get the name.""" return self._igd_device.name - async def async_add_port_mappings(self, ports, local_ip=None): + async def async_add_port_mappings(self, ports, local_ip): """Add port mappings.""" - # determine local ip, ensure sane IP - if local_ip is None: - local_ip = get_local_ip() - if local_ip == '127.0.0.1': _LOGGER.error( 'Could not create port mapping, our IP is 127.0.0.1') + + # determine local ip, ensure sane IP local_ip = IPv4Address(local_ip) # create port mappings diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index ce4656032a6..7f163d5bcef 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -9,6 +9,7 @@ from homeassistant.components.upnp.device import Device from homeassistant.const import EVENT_HOMEASSISTANT_STOP from tests.common import MockConfigEntry +from tests.common import MockDependency from tests.common import mock_coro @@ -17,7 +18,7 @@ class MockDevice(Device): def __init__(self, udn): """Initializer.""" - super().__init__(None) + super().__init__(MagicMock()) self._udn = udn self.added_port_mappings = [] self.removed_port_mappings = [] @@ -49,7 +50,15 @@ class MockDevice(Device): async def test_async_setup_no_auto_config(hass): """Test async_setup.""" # setup component, enable auto_config - await async_setup_component(hass, 'upnp') + config = { + 'discovery': {}, + # no upnp + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() assert hass.data[upnp.DOMAIN]['auto_config'] == { 'active': False, @@ -62,7 +71,15 @@ async def test_async_setup_no_auto_config(hass): async def test_async_setup_auto_config(hass): """Test async_setup.""" # setup component, enable auto_config - await async_setup_component(hass, 'upnp', {'upnp': {}, 'discovery': {}}) + config = { + 'discovery': {}, + 'upnp': {}, + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() assert hass.data[upnp.DOMAIN]['auto_config'] == { 'active': True, @@ -75,12 +92,18 @@ async def test_async_setup_auto_config(hass): async def test_async_setup_auto_config_port_mapping(hass): """Test async_setup.""" # setup component, enable auto_config - await async_setup_component(hass, 'upnp', { + config = { + 'discovery': {}, 'upnp': { 'port_mapping': True, 'ports': {'hass': 'hass'}, }, - 'discovery': {}}) + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() assert hass.data[upnp.DOMAIN]['auto_config'] == { 'active': True, @@ -93,9 +116,15 @@ async def test_async_setup_auto_config_port_mapping(hass): async def test_async_setup_auto_config_no_sensors(hass): """Test async_setup.""" # setup component, enable auto_config - await async_setup_component(hass, 'upnp', { + config = { + 'discovery': {}, 'upnp': {'sensors': False}, - 'discovery': {}}) + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() assert hass.data[upnp.DOMAIN]['auto_config'] == { 'active': True, @@ -115,18 +144,23 @@ async def test_async_setup_entry_default(hass): 'port_mapping': False, }) - # ensure hass.http is available - await async_setup_component(hass, 'upnp') + config = { + 'http': {}, + 'discovery': {}, + # no upnp + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'http', config) + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() # mock homeassistant.components.upnp.device.Device - mock_device = MagicMock() - mock_device.udn = udn - mock_device.async_add_port_mappings.return_value = mock_coro() - mock_device.async_delete_port_mappings.return_value = mock_coro() - with patch.object(Device, 'async_create_device') as mock_create_device: - mock_create_device.return_value = mock_coro( - return_value=mock_device) - with patch('homeassistant.components.upnp.device.get_local_ip', + mock_device = MockDevice(udn) + with patch.object(Device, 'async_create_device') as create_device: + create_device.return_value = mock_coro(return_value=mock_device) + with patch('homeassistant.components.upnp.get_local_ip', return_value='192.168.1.10'): assert await upnp.async_setup_entry(hass, entry) is True @@ -136,12 +170,9 @@ async def test_async_setup_entry_default(hass): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() - # ensure cleaned up - assert udn not in hass.data[upnp.DOMAIN]['devices'] - - # ensure no port-mapping-methods called - assert len(mock_device.async_add_port_mappings.mock_calls) == 0 - assert len(mock_device.async_delete_port_mappings.mock_calls) == 0 + # ensure no port-mappings created or removed + assert not mock_device.added_port_mappings + assert not mock_device.removed_port_mappings async def test_async_setup_entry_port_mapping(hass): @@ -154,35 +185,37 @@ async def test_async_setup_entry_port_mapping(hass): 'port_mapping': True, }) - # ensure hass.http is available - await async_setup_component(hass, 'upnp', { + config = { + 'http': {}, + 'discovery': {}, 'upnp': { 'port_mapping': True, 'ports': {'hass': 'hass'}, }, - 'discovery': {}, - }) + } + with MockDependency('netdisco.discovery'), \ + patch('homeassistant.components.upnp.get_local_ip', + return_value='192.168.1.10'): + await async_setup_component(hass, 'http', config) + await async_setup_component(hass, 'upnp', config) + await hass.async_block_till_done() mock_device = MockDevice(udn) - with patch.object(Device, 'async_create_device') as mock_create_device: - mock_create_device.return_value = mock_coro(return_value=mock_device) - with patch('homeassistant.components.upnp.device.get_local_ip', - return_value='192.168.1.10'): - assert await upnp.async_setup_entry(hass, entry) is True + with patch.object(Device, 'async_create_device') as create_device: + create_device.return_value = mock_coro(return_value=mock_device) - # ensure device is stored/used - assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device + assert await upnp.async_setup_entry(hass, entry) is True - # ensure add-port-mapping-methods called - assert mock_device.added_port_mappings == [ - [8123, ip_address('192.168.1.10'), 8123] - ] + # ensure device is stored/used + assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() + # ensure add-port-mapping-methods called + assert mock_device.added_port_mappings == [ + [8123, ip_address('192.168.1.10'), 8123] + ] - # ensure cleaned up - assert udn not in hass.data[upnp.DOMAIN]['devices'] + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() # ensure delete-port-mapping-methods called assert mock_device.removed_port_mappings == [8123] From 6ac9677168425932ae8a9f0f2089f6c0f165300d Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Wed, 24 Oct 2018 05:15:59 -0400 Subject: [PATCH 031/230] Elk-M1 climate (#17679) * Initial climate for Elk-M1. * Tidy * fix hound error * fix hound error --- homeassistant/components/climate/elkm1.py | 193 +++++++++++++++++++++ homeassistant/components/elkm1/__init__.py | 4 +- 2 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/climate/elkm1.py diff --git a/homeassistant/components/climate/elkm1.py b/homeassistant/components/climate/elkm1.py new file mode 100644 index 00000000000..6bd33b382dc --- /dev/null +++ b/homeassistant/components/climate/elkm1.py @@ -0,0 +1,193 @@ +""" +Support for control of Elk-M1 connected thermostats. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.elkm1/ +""" +from homeassistant.components.climate import ( + ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, PRECISION_WHOLE, STATE_AUTO, + STATE_COOL, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT, + SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice) +from homeassistant.components.elkm1 import ( + DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities) +from homeassistant.const import STATE_ON + +DEPENDENCIES = [ELK_DOMAIN] + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Create the Elk-M1 thermostat platform.""" + if discovery_info is None: + return + + elk = hass.data[ELK_DOMAIN]['elk'] + async_add_entities(create_elk_entities( + hass, elk.thermostats, 'thermostat', ElkThermostat, []), True) + + +class ElkThermostat(ElkEntity, ClimateDevice): + """Representation of an Elk-M1 Thermostat.""" + + def __init__(self, element, elk, elk_data): + """Initialize climate entity.""" + super().__init__(element, elk, elk_data) + self._state = None + + @property + def supported_features(self): + """Return the list of supported features.""" + return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT + | SUPPORT_TARGET_TEMPERATURE_HIGH + | SUPPORT_TARGET_TEMPERATURE_LOW) + + @property + def temperature_unit(self): + """Return the temperature unit.""" + return self._temperature_unit + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._element.current_temp + + @property + def target_temperature(self): + """Return the temperature we are trying to reach.""" + from elkm1_lib.const import ThermostatMode + if (self._element.mode == ThermostatMode.HEAT.value) or ( + self._element.mode == ThermostatMode.EMERGENCY_HEAT.value): + return self._element.heat_setpoint + if self._element.mode == ThermostatMode.COOL.value: + return self._element.cool_setpoint + return None + + @property + def target_temperature_high(self): + """Return the high target temperature.""" + return self._element.cool_setpoint + + @property + def target_temperature_low(self): + """Return the low target temperature.""" + return self._element.heat_setpoint + + @property + def target_temperature_step(self): + """Return the supported step of target temperature.""" + return 1 + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._element.humidity + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return self._state + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY] + + @property + def precision(self): + """Return the precision of the system.""" + return PRECISION_WHOLE + + @property + def is_aux_heat_on(self): + """Return if aux heater is on.""" + from elkm1_lib.const import ThermostatMode + return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value + + @property + def min_temp(self): + """Return the minimum temperature supported.""" + return 1 + + @property + def max_temp(self): + """Return the maximum temperature supported.""" + return 99 + + @property + def current_fan_mode(self): + """Return the fan setting.""" + from elkm1_lib.const import ThermostatFan + if self._element.fan == ThermostatFan.AUTO.value: + return STATE_AUTO + if self._element.fan == ThermostatFan.ON.value: + return STATE_ON + return None + + def _elk_set(self, mode, fan): + from elkm1_lib.const import ThermostatSetting + if mode is not None: + self._element.set(ThermostatSetting.MODE.value, mode) + if fan is not None: + self._element.set(ThermostatSetting.FAN.value, fan) + + async def async_set_operation_mode(self, operation_mode): + """Set thermostat operation mode.""" + from elkm1_lib.const import ThermostatFan, ThermostatMode + settings = { + STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value), + STATE_HEAT: (ThermostatMode.HEAT.value, None), + STATE_COOL: (ThermostatMode.COOL.value, None), + STATE_AUTO: (ThermostatMode.AUTO.value, None), + STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value) + } + self._elk_set(settings[operation_mode][0], settings[operation_mode][1]) + + async def async_turn_aux_heat_on(self): + """Turn auxiliary heater on.""" + from elkm1_lib.const import ThermostatMode + self._elk_set(ThermostatMode.EMERGENCY_HEAT.value, None) + + async def async_turn_aux_heat_off(self): + """Turn auxiliary heater off.""" + from elkm1_lib.const import ThermostatMode + self._elk_set(ThermostatMode.HEAT.value, None) + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return [STATE_AUTO, STATE_ON] + + async def async_set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + from elkm1_lib.const import ThermostatFan + if fan_mode == STATE_AUTO: + self._elk_set(None, ThermostatFan.AUTO.value) + elif fan_mode == STATE_ON: + self._elk_set(None, ThermostatFan.ON.value) + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + from elkm1_lib.const import ThermostatSetting + low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) + high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) + if low_temp is not None: + self._element.set( + ThermostatSetting.HEAT_SETPOINT.value, round(low_temp)) + if high_temp is not None: + self._element.set( + ThermostatSetting.COOL_SETPOINT.value, round(high_temp)) + + def _element_changed(self, element, changeset): + from elkm1_lib.const import ThermostatFan, ThermostatMode + mode_to_state = { + ThermostatMode.OFF.value: STATE_IDLE, + ThermostatMode.COOL.value: STATE_COOL, + ThermostatMode.HEAT.value: STATE_HEAT, + ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT, + ThermostatMode.AUTO.value: STATE_AUTO, + } + self._state = mode_to_state.get(self._element.mode) + if self._state == STATE_IDLE and \ + self._element.fan == ThermostatFan.ON.value: + self._state = STATE_FAN_ONLY diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 5c379c7438b..76594e16736 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -35,8 +35,8 @@ CONF_ENABLED = 'enabled' _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ['alarm_control_panel', 'light', 'scene', 'sensor', - 'switch'] +SUPPORTED_DOMAINS = ['alarm_control_panel', 'climate', 'light', 'scene', + 'sensor', 'switch'] SPEAK_SERVICE_SCHEMA = vol.Schema({ vol.Required('number'): From 4222f7562b981f6050e8afcd050db7163c4295c3 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Wed, 24 Oct 2018 05:53:45 -0400 Subject: [PATCH 032/230] Add cover to supported platforms (#17725) --- homeassistant/components/zwave/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 74678cda0fc..35703d64974 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -66,7 +66,7 @@ DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS = False DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_DELAY = 5 -SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'fan', +SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover', 'fan', 'light', 'sensor', 'switch'] RENAME_NODE_SCHEMA = vol.Schema({ From 08fe7c3ecee971a49aa52b8254c32c23796628bb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Oct 2018 12:10:05 +0200 Subject: [PATCH 033/230] Pytest tests (#17750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Convert core tests * Convert component tests to use pytest assert * Lint 🤷‍♂️ * Fix test * Fix 3 typos in docs --- homeassistant/auth/mfa_modules/__init__.py | 2 +- homeassistant/auth/mfa_modules/totp.py | 2 +- homeassistant/auth/providers/__init__.py | 2 +- .../alarm_control_panel/test_manual.py | 560 +++++++++--------- .../alarm_control_panel/test_manual_mqtt.py | 558 ++++++++--------- .../alarm_control_panel/test_mqtt.py | 26 +- tests/components/automation/test_event.py | 16 +- .../automation/test_geo_location.py | 29 +- tests/components/automation/test_init.py | 34 +- tests/components/automation/test_mqtt.py | 12 +- .../automation/test_numeric_state.py | 94 ++- tests/components/automation/test_state.py | 60 +- tests/components/automation/test_sun.py | 34 +- tests/components/automation/test_template.py | 48 +- tests/components/automation/test_time.py | 42 +- tests/components/automation/test_zone.py | 17 +- tests/components/binary_sensor/test_aurora.py | 20 +- .../components/binary_sensor/test_bayesian.py | 42 +- .../binary_sensor/test_binary_sensor.py | 10 +- .../binary_sensor/test_command_line.py | 10 +- tests/components/binary_sensor/test_mqtt.py | 56 +- tests/components/binary_sensor/test_nx584.py | 60 +- tests/components/binary_sensor/test_random.py | 4 +- tests/components/binary_sensor/test_rest.py | 43 +- tests/components/binary_sensor/test_ring.py | 16 +- .../components/binary_sensor/test_sleepiq.py | 10 +- .../components/binary_sensor/test_template.py | 10 +- .../binary_sensor/test_threshold.py | 132 ++--- tests/components/binary_sensor/test_vultr.py | 72 +-- tests/components/calendar/test_caldav.py | 64 +- tests/components/calendar/test_google.py | 62 +- tests/components/camera/test_uvc.py | 100 ++-- tests/components/climate/test_demo.py | 134 ++--- tests/components/climate/test_dyson.py | 48 +- tests/components/climate/test_ecobee.py | 218 +++---- tests/components/climate/test_fritzbox.py | 60 +- .../climate/test_generic_thermostat.py | 310 +++++----- tests/components/climate/test_honeywell.py | 155 +++-- tests/components/climate/test_melissa.py | 119 ++-- tests/components/climate/test_mqtt.py | 224 ++++--- tests/components/climate/test_nuheat.py | 76 ++- tests/components/counter/test_init.py | 41 +- tests/components/cover/test_mqtt.py | 208 +++---- tests/components/cover/test_rfxtrx.py | 72 +-- .../components/device_tracker/test_asuswrt.py | 4 +- tests/components/device_tracker/test_ddwrt.py | 28 +- tests/components/device_tracker/test_init.py | 120 ++-- tests/components/device_tracker/test_mqtt.py | 12 +- .../device_tracker/test_mqtt_json.py | 34 +- .../device_tracker/test_owntracks.py | 62 +- .../components/device_tracker/test_tplink.py | 5 +- .../device_tracker/test_unifi_direct.py | 20 +- .../components/device_tracker/test_xiaomi.py | 50 +- tests/components/emulated_hue/test_upnp.py | 14 +- tests/components/fan/test_demo.py | 48 +- tests/components/fan/test_dyson.py | 46 +- tests/components/fan/test_init.py | 15 +- tests/components/fan/test_mqtt.py | 24 +- tests/components/geo_location/test_demo.py | 17 +- .../geo_location/test_geo_json_events.py | 18 +- .../test_nsw_rural_fire_service_feed.py | 15 +- tests/components/group/test_init.py | 137 +++-- tests/components/light/test_init.py | 150 +++-- tests/components/light/test_mochad.py | 4 +- tests/components/light/test_mqtt.py | 266 ++++----- tests/components/light/test_mqtt_json.py | 312 +++++----- tests/components/light/test_mqtt_template.py | 154 ++--- tests/components/light/test_rfxtrx.py | 191 +++--- tests/components/lock/test_demo.py | 14 +- tests/components/lock/test_mqtt.py | 42 +- tests/components/lovelace/test_init.py | 9 +- .../media_player/test_async_helpers.py | 48 +- .../components/media_player/test_blackbird.py | 88 +-- .../components/media_player/test_monoprice.py | 211 ++++--- .../components/media_player/test_samsungtv.py | 53 +- tests/components/media_player/test_sonos.py | 42 +- .../media_player/test_soundtouch.py | 270 ++++----- .../components/media_player/test_universal.py | 148 +++-- tests/components/media_player/test_yamaha.py | 2 +- tests/components/mqtt/test_discovery.py | 2 +- tests/components/mqtt/test_init.py | 228 +++---- tests/components/notify/test_apns.py | 97 ++- tests/components/notify/test_command_line.py | 24 +- tests/components/notify/test_demo.py | 21 +- tests/components/notify/test_facebook.py | 30 +- tests/components/notify/test_file.py | 18 +- tests/components/notify/test_pushbullet.py | 40 +- tests/components/notify/test_smtp.py | 7 +- tests/components/recorder/test_purge.py | 34 +- tests/components/remote/test_demo.py | 15 +- tests/components/remote/test_init.py | 34 +- tests/components/scene/test_init.py | 42 +- tests/components/sensor/test_bom.py | 16 +- tests/components/sensor/test_canary.py | 70 +-- tests/components/sensor/test_command_line.py | 62 +- tests/components/sensor/test_darksky.py | 14 +- .../sensor/test_dte_energy_bridge.py | 23 +- tests/components/sensor/test_dyson.py | 130 ++-- tests/components/sensor/test_efergy.py | 24 +- tests/components/sensor/test_fail2ban.py | 89 ++- tests/components/sensor/test_file.py | 6 +- tests/components/sensor/test_filesize.py | 6 +- tests/components/sensor/test_filter.py | 24 +- tests/components/sensor/test_folder.py | 6 +- .../components/sensor/test_geo_rss_events.py | 11 +- tests/components/sensor/test_google_wifi.py | 42 +- tests/components/sensor/test_hddtemp.py | 62 +- tests/components/sensor/test_history_stats.py | 69 +-- .../sensor/test_imap_email_content.py | 50 +- .../components/sensor/test_jewish_calendar.py | 18 +- tests/components/sensor/test_london_air.py | 3 +- .../sensor/test_london_underground.py | 3 +- tests/components/sensor/test_melissa.py | 24 +- tests/components/sensor/test_mfi.py | 56 +- tests/components/sensor/test_mhz19.py | 39 +- tests/components/sensor/test_min_max.py | 58 +- tests/components/sensor/test_moldindicator.py | 24 +- tests/components/sensor/test_moon.py | 4 +- tests/components/sensor/test_mqtt.py | 70 ++- tests/components/sensor/test_mqtt_room.py | 8 +- .../sensor/test_nsw_fuel_station.py | 18 +- .../sensor/test_openhardwaremonitor.py | 8 +- tests/components/sensor/test_radarr.py | 97 ++- tests/components/sensor/test_random.py | 4 +- tests/components/sensor/test_rest.py | 73 ++- tests/components/sensor/test_rfxtrx.py | 238 ++++---- tests/components/sensor/test_ring.py | 54 +- tests/components/sensor/test_season.py | 86 +-- tests/components/sensor/test_sigfox.py | 6 +- tests/components/sensor/test_simulated.py | 2 +- tests/components/sensor/test_sleepiq.py | 10 +- tests/components/sensor/test_sonarr.py | 123 ++-- tests/components/sensor/test_sql.py | 2 +- tests/components/sensor/test_statistics.py | 72 +-- tests/components/sensor/test_transport_nsw.py | 10 +- tests/components/sensor/test_uk_transport.py | 8 +- tests/components/sensor/test_uptime.py | 18 +- tests/components/sensor/test_version.py | 2 +- tests/components/sensor/test_vultr.py | 47 +- tests/components/sensor/test_worldclock.py | 2 +- tests/components/sensor/test_wsdot.py | 18 +- tests/components/sensor/test_yahoo_finance.py | 6 +- tests/components/sensor/test_yweather.py | 62 +- tests/components/switch/test_command_line.py | 48 +- tests/components/switch/test_flux.py | 250 ++++---- tests/components/switch/test_init.py | 53 +- tests/components/switch/test_mfi.py | 32 +- tests/components/switch/test_mochad.py | 4 +- tests/components/switch/test_mqtt.py | 66 +-- tests/components/switch/test_rfxtrx.py | 150 ++--- tests/components/switch/test_vultr.py | 76 +-- tests/components/switch/test_wake_on_lan.py | 46 +- tests/components/test_alert.py | 48 +- tests/components/test_canary.py | 9 +- tests/components/test_configurator.py | 26 +- tests/components/test_datadog.py | 42 +- .../test_device_sun_light_trigger.py | 26 +- tests/components/test_dialogflow.py | 50 +- tests/components/test_dyson.py | 50 +- tests/components/test_feedreader.py | 11 +- tests/components/test_google.py | 9 +- tests/components/test_graphite.py | 100 ++-- tests/components/test_history.py | 11 +- tests/components/test_history_graph.py | 18 +- tests/components/test_influxdb.py | 140 ++--- tests/components/test_init.py | 20 +- tests/components/test_input_boolean.py | 42 +- tests/components/test_input_datetime.py | 3 +- tests/components/test_input_number.py | 46 +- tests/components/test_input_select.py | 79 ++- tests/components/test_input_text.py | 24 +- tests/components/test_introduction.py | 2 +- tests/components/test_logbook.py | 72 +-- tests/components/test_logentries.py | 22 +- tests/components/test_logger.py | 12 +- tests/components/test_melissa.py | 8 +- tests/components/test_nuheat.py | 11 +- tests/components/test_pilight.py | 98 ++- tests/components/test_plant.py | 22 +- tests/components/test_proximity.py | 20 +- tests/components/test_remember_the_milk.py | 18 +- tests/components/test_rfxtrx.py | 66 +-- tests/components/test_ring.py | 2 +- tests/components/test_script.py | 20 +- tests/components/test_shell_command.py | 63 +- tests/components/test_sleepiq.py | 2 +- tests/components/test_splunk.py | 22 +- tests/components/test_statsd.py | 53 +- tests/components/test_sun.py | 34 +- tests/components/test_vultr.py | 2 +- tests/components/test_weblink.py | 32 +- tests/components/timer/test_init.py | 25 +- tests/components/vacuum/test_demo.py | 224 +++---- tests/components/vacuum/test_dyson.py | 46 +- tests/components/vacuum/test_mqtt.py | 78 ++- tests/components/water_heater/test_demo.py | 50 +- tests/components/weather/test_darksky.py | 10 +- tests/components/weather/test_ipma.py | 18 +- tests/components/weather/test_weather.py | 4 +- tests/components/weather/test_yweather.py | 32 +- tests/components/zwave/test_init.py | 37 +- tests/components/zwave/test_node_entity.py | 95 ++- tests/helpers/test_event.py | 232 ++++---- tests/helpers/test_icon.py | 32 +- tests/helpers/test_init.py | 4 +- tests/helpers/test_intent.py | 13 +- tests/helpers/test_location.py | 13 +- tests/helpers/test_service.py | 34 +- tests/helpers/test_state.py | 113 ++-- tests/helpers/test_sun.py | 100 ++-- tests/helpers/test_temperature.py | 19 +- tests/helpers/test_template.py | 447 ++++++-------- tests/test_config.py | 56 +- tests/test_core.py | 152 +++-- tests/test_loader.py | 15 +- tests/util/test_color.py | 326 +++++----- tests/util/test_distance.py | 82 ++- tests/util/test_dt.py | 152 ++--- tests/util/test_init.py | 217 ++++--- tests/util/test_json.py | 13 +- tests/util/test_unit_system.py | 72 +-- tests/util/test_volume.py | 30 +- tests/util/test_yaml.py | 29 +- 223 files changed, 6747 insertions(+), 7237 deletions(-) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 1746ef38f95..3313063679d 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -104,7 +104,7 @@ class SetupFlow(data_entry_flow.FlowHandler): -> Dict[str, Any]: """Handle the first step of setup flow. - Return self.async_show_form(step_id='init') if user_input == None. + Return self.async_show_form(step_id='init') if user_input is None. Return self.async_create_entry(data={'result': result}) if finish. """ errors = {} # type: Dict[str, str] diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 9b5896ef666..68f4e1d0596 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -176,7 +176,7 @@ class TotpSetupFlow(SetupFlow): -> Dict[str, Any]: """Handle the first step of setup flow. - Return self.async_show_form(step_id='init') if user_input == None. + Return self.async_show_form(step_id='init') if user_input is None. Return self.async_create_entry(data={'result': result}) if finish. """ import pyotp diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index e96f6d7ebba..9ca4232b610 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -179,7 +179,7 @@ class LoginFlow(data_entry_flow.FlowHandler): -> Dict[str, Any]: """Handle the first step of login flow. - Return self.async_show_form(step_id='init') if user_input == None. + Return self.async_show_form(step_id='init') if user_input is None. Return await self.async_finish(flow_result) if login init step pass. """ raise NotImplementedError diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 02085a44b47..7d87d05b58f 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -35,11 +35,11 @@ class TestAlarmControlPanelManual(unittest.TestCase): mock = MagicMock() add_entities = mock.MagicMock() demo.setup_platform(self.hass, {}, add_entities) - self.assertEqual(add_entities.call_count, 1) + assert add_entities.call_count == 1 def test_arm_home_no_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -47,22 +47,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_arm_home_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -70,18 +70,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_HOME @@ -97,7 +97,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_home_with_invalid_code(self): """Attempt to arm home without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -105,22 +105,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_arm_away_no_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -128,22 +128,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_arm_home_with_template_code(self): """Attempt to arm with a template-based code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -151,25 +151,25 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code_template': '{{ "abc" }}', 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' self.hass.start() self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, 'abc') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state def test_arm_away_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -177,18 +177,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_AWAY @@ -204,7 +204,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_away_with_invalid_code(self): """Attempt to arm away without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -212,22 +212,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_arm_night_no_pending(self): """Test arm night method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -235,22 +235,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_arm_night_with_pending(self): """Test arm night method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -258,18 +258,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == \ @@ -288,12 +288,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): common.alarm_arm_night(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_arm_night_with_invalid_code(self): """Attempt to night home without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -301,40 +301,40 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_no_pending(self): """Test triggering when no pending submitted method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'trigger_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=60) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -342,12 +342,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state def test_trigger_with_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -356,26 +356,26 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'delay_time': 1, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -384,11 +384,11 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_TRIGGERED, state.state) + assert STATE_ALARM_TRIGGERED == state.state def test_trigger_zero_trigger_time(self): """Test disabled trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -396,22 +396,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 0, 'trigger_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_zero_trigger_time_with_pending(self): """Test disabled trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -419,22 +419,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 2, 'trigger_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -442,18 +442,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 2, 'trigger_time': 3, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED @@ -478,7 +478,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_trigger_with_unused_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -490,26 +490,26 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'delay_time': 10 }, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -522,7 +522,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_trigger_with_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -534,26 +534,26 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'delay_time': 1 }, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -566,7 +566,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_trigger_with_pending_and_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -578,18 +578,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 1 }, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() @@ -619,7 +619,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_trigger_with_pending_and_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -634,18 +634,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 1 }, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() @@ -675,7 +675,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_armed_home_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -684,15 +684,15 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'armed_home': { 'pending_time': 2 } - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_home(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -700,12 +700,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_armed_away_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -714,15 +714,15 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'armed_away': { 'pending_time': 2 } - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_away(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -730,12 +730,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_armed_night_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -744,15 +744,15 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'armed_night': { 'pending_time': 2 } - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_night(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -760,12 +760,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_trigger_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -776,15 +776,15 @@ class TestAlarmControlPanelManual(unittest.TestCase): }, 'trigger_time': 3, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -792,8 +792,8 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -801,12 +801,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_disarm_after_trigger(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -814,18 +814,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'trigger_time': 5, 'pending_time': 0, 'disarm_after_trigger': True - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -833,12 +833,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_zero_specific_trigger_time(self): """Test trigger method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -849,22 +849,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): }, 'pending_time': 0, 'disarm_after_trigger': True - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_unused_zero_specific_trigger_time(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -875,18 +875,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): }, 'pending_time': 0, 'disarm_after_trigger': True - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -894,12 +894,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_specific_trigger_time(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -909,18 +909,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): }, 'pending_time': 0, 'disarm_after_trigger': True - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -928,12 +928,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_no_disarm_after_trigger(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -941,24 +941,24 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'trigger_time': 5, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -966,12 +966,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_back_to_back_trigger_with_no_disarm_after_trigger(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -979,24 +979,24 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'trigger_time': 5, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1004,14 +1004,14 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1019,36 +1019,36 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_disarm_while_pending_trigger(self): """Test disarming while pending state.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'trigger_time': 5, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state common.alarm_disarm(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1056,12 +1056,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_disarm_during_trigger_with_invalid_code(self): """Test disarming while code is invalid.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1069,24 +1069,24 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'pending_time': 5, 'code': CODE + '2', 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state common.alarm_disarm(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1094,12 +1094,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state def test_disarm_with_template_code(self): """Attempt to disarm with a valid or invalid template-based code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1108,37 +1108,37 @@ class TestAlarmControlPanelManual(unittest.TestCase): '{{ "" if from_state == "disarmed" else "abc" }}', 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' self.hass.start() self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, 'def') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state common.alarm_disarm(self.hass, 'def') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state common.alarm_disarm(self.hass, 'abc') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_DISARMED, state.state) + assert STATE_ALARM_DISARMED == state.state def test_arm_custom_bypass_no_pending(self): """Test arm custom bypass method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1146,22 +1146,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 0, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_custom_bypass(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_CUSTOM_BYPASS, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_CUSTOM_BYPASS == \ + self.hass.states.get(entity_id).state def test_arm_custom_bypass_with_pending(self): """Test arm custom bypass method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1169,18 +1169,18 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_custom_bypass(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == \ @@ -1197,7 +1197,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_custom_bypass_with_invalid_code(self): """Attempt to custom bypass without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1205,22 +1205,22 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'code': CODE, 'pending_time': 1, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_custom_bypass(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_armed_custom_bypass_with_specific_pending(self): """Test arm custom bypass method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1229,15 +1229,15 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'armed_custom_bypass': { 'pending_time': 2 } - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_custom_bypass(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1245,12 +1245,12 @@ class TestAlarmControlPanelManual(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_CUSTOM_BYPASS, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_CUSTOM_BYPASS == \ + self.hass.states.get(entity_id).state def test_arm_away_after_disabled_disarmed(self): """Test pending state with and without zero trigger time.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual', @@ -1265,32 +1265,32 @@ class TestAlarmControlPanelManual(unittest.TestCase): 'trigger_time': 0 }, 'disarm_after_trigger': False - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_DISARMED, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_DISARMED == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['post_pending_state'] common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_DISARMED, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_DISARMED == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1299,17 +1299,17 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_AWAY, state.state) + assert STATE_ALARM_ARMED_AWAY == state.state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future += timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual.' @@ -1318,4 +1318,4 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_TRIGGERED, state.state) + assert STATE_ALARM_TRIGGERED == state.state diff --git a/tests/components/alarm_control_panel/test_manual_mqtt.py b/tests/components/alarm_control_panel/test_manual_mqtt.py index 4e2ec6a9489..3d063f8a34a 100644 --- a/tests/components/alarm_control_panel/test_manual_mqtt.py +++ b/tests/components/alarm_control_panel/test_manual_mqtt.py @@ -54,7 +54,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): def test_arm_home_no_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -64,22 +64,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_arm_home_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -89,18 +89,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_HOME @@ -111,12 +111,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_arm_home_with_invalid_code(self): """Attempt to arm home without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -126,22 +126,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_arm_away_no_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -151,22 +151,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_arm_home_with_template_code(self): """Attempt to arm with a template-based code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -176,25 +176,25 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' self.hass.start() self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, 'abc') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state def test_arm_away_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -204,18 +204,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_ARMED_AWAY @@ -226,12 +226,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_arm_away_with_invalid_code(self): """Attempt to arm away without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -241,22 +241,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_arm_night_no_pending(self): """Test arm night method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -266,22 +266,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_arm_night_with_pending(self): """Test arm night method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -291,18 +291,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == \ @@ -314,19 +314,19 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state # Do not go to the pending state when updating to the same state common.alarm_arm_night(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_arm_night_with_invalid_code(self): """Attempt to arm night without a valid code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -336,22 +336,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_night(self.hass, CODE + '2') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_no_pending(self): """Test triggering when no pending submitted method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -360,18 +360,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=60) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -379,12 +379,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state def test_trigger_with_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -395,26 +395,26 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -423,11 +423,11 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_TRIGGERED, state.state) + assert STATE_ALARM_TRIGGERED == state.state def test_trigger_zero_trigger_time(self): """Test disabled trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -437,22 +437,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_zero_trigger_time_with_pending(self): """Test disabled trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -462,22 +462,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -487,18 +487,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state state = self.hass.states.get(entity_id) assert state.attributes['post_pending_state'] == STATE_ALARM_TRIGGERED @@ -509,8 +509,8 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -518,12 +518,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_disarm_after_trigger(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -533,18 +533,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': True, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -552,12 +552,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_zero_specific_trigger_time(self): """Test trigger method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -570,22 +570,22 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': True, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_unused_zero_specific_trigger_time(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -598,18 +598,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': True, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -617,12 +617,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_trigger_with_specific_trigger_time(self): """Test disarm after trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -634,18 +634,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': True, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -653,12 +653,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_back_to_back_trigger_with_no_disarm_after_trigger(self): """Test no disarm after back to back trigger.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -668,24 +668,24 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE, entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -693,14 +693,14 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -708,12 +708,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_disarm_while_pending_trigger(self): """Test disarming while pending state.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -722,24 +722,24 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state common.alarm_disarm(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -747,12 +747,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_disarm_during_trigger_with_invalid_code(self): """Test disarming while code is invalid.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -762,24 +762,24 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state common.alarm_disarm(self.hass, entity_id=entity_id) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -787,12 +787,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state def test_trigger_with_unused_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -806,26 +806,26 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -838,7 +838,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): def test_trigger_with_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -852,26 +852,26 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -884,7 +884,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): def test_trigger_with_pending_and_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -898,18 +898,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() @@ -939,7 +939,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): def test_trigger_with_pending_and_specific_delay(self): """Test trigger method and switch from pending to triggered.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -956,18 +956,18 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state' - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() @@ -997,7 +997,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): def test_armed_home_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1008,15 +1008,15 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): }, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_home(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1024,12 +1024,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_armed_away_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1040,15 +1040,15 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): }, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_away(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1056,12 +1056,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_armed_night_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1072,15 +1072,15 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): }, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_arm_night(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1088,12 +1088,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_trigger_with_specific_pending(self): """Test arm home method.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1106,15 +1106,15 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1122,8 +1122,8 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_TRIGGERED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_TRIGGERED == \ + self.hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=5) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1131,12 +1131,12 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_arm_away_after_disabled_disarmed(self): """Test pending state with and without zero trigger time.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1153,32 +1153,32 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_away(self.hass, CODE) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_DISARMED, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_DISARMED == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['post_pending_state'] common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_DISARMED, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_DISARMED == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['post_pending_state'] future = dt_util.utcnow() + timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1187,17 +1187,17 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_AWAY, state.state) + assert STATE_ALARM_ARMED_AWAY == state.state common.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_PENDING, state.state) - self.assertEqual(STATE_ALARM_ARMED_AWAY, - state.attributes['pre_pending_state']) - self.assertEqual(STATE_ALARM_TRIGGERED, - state.attributes['post_pending_state']) + assert STATE_ALARM_PENDING == state.state + assert STATE_ALARM_ARMED_AWAY == \ + state.attributes['pre_pending_state'] + assert STATE_ALARM_TRIGGERED == \ + state.attributes['post_pending_state'] future += timedelta(seconds=1) with patch(('homeassistant.components.alarm_control_panel.manual_mqtt.' @@ -1206,11 +1206,11 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_TRIGGERED, state.state) + assert STATE_ALARM_TRIGGERED == state.state def test_disarm_with_template_code(self): """Attempt to disarm with a valid or invalid template-based code.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, alarm_control_panel.DOMAIN, {'alarm_control_panel': { 'platform': 'manual_mqtt', @@ -1221,33 +1221,33 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): 'disarm_after_trigger': False, 'command_topic': 'alarm/command', 'state_topic': 'alarm/state', - }})) + }}) entity_id = 'alarm_control_panel.test' self.hass.start() self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_arm_home(self.hass, 'def') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state common.alarm_disarm(self.hass, 'def') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_ARMED_HOME, state.state) + assert STATE_ALARM_ARMED_HOME == state.state common.alarm_disarm(self.hass, 'abc') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(STATE_ALARM_DISARMED, state.state) + assert STATE_ALARM_DISARMED == state.state def test_arm_home_via_command_topic(self): """Test arming home via command topic.""" @@ -1264,14 +1264,14 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state # Fire the arm command via MQTT; ensure state changes to pending fire_mqtt_message(self.hass, 'alarm/command', 'ARM_HOME') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1280,8 +1280,8 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_HOME, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_HOME == \ + self.hass.states.get(entity_id).state def test_arm_away_via_command_topic(self): """Test arming away via command topic.""" @@ -1298,14 +1298,14 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state # Fire the arm command via MQTT; ensure state changes to pending fire_mqtt_message(self.hass, 'alarm/command', 'ARM_AWAY') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1314,8 +1314,8 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_AWAY, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_AWAY == \ + self.hass.states.get(entity_id).state def test_arm_night_via_command_topic(self): """Test arming night via command topic.""" @@ -1332,14 +1332,14 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state # Fire the arm command via MQTT; ensure state changes to pending fire_mqtt_message(self.hass, 'alarm/command', 'ARM_NIGHT') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state # Fast-forward a little bit future = dt_util.utcnow() + timedelta(seconds=1) @@ -1348,8 +1348,8 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_ARMED_NIGHT, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_ARMED_NIGHT == \ + self.hass.states.get(entity_id).state def test_disarm_pending_via_command_topic(self): """Test disarming pending alarm via command topic.""" @@ -1366,21 +1366,21 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state common.alarm_trigger(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_ALARM_PENDING, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_PENDING == \ + self.hass.states.get(entity_id).state # Now that we're pending, receive a command to disarm fire_mqtt_message(self.hass, 'alarm/command', 'DISARM') self.hass.block_till_done() - self.assertEqual(STATE_ALARM_DISARMED, - self.hass.states.get(entity_id).state) + assert STATE_ALARM_DISARMED == \ + self.hass.states.get(entity_id).state def test_state_changes_are_published_to_mqtt(self): """Test publishing of MQTT messages when state changes.""" diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index dd606bb53ec..64616718125 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -65,15 +65,15 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_UNKNOWN, - self.hass.states.get(entity_id).state) + assert STATE_UNKNOWN == \ + self.hass.states.get(entity_id).state for state in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED): fire_mqtt_message(self.hass, 'alarm/state', state) self.hass.block_till_done() - self.assertEqual(state, self.hass.states.get(entity_id).state) + assert state == self.hass.states.get(entity_id).state def test_ignore_update_state_if_unknown_via_state_topic(self): """Test ignoring updates via state topic.""" @@ -88,12 +88,12 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): entity_id = 'alarm_control_panel.test' - self.assertEqual(STATE_UNKNOWN, - self.hass.states.get(entity_id).state) + assert STATE_UNKNOWN == \ + self.hass.states.get(entity_id).state fire_mqtt_message(self.hass, 'alarm/state', 'unsupported state') self.hass.block_till_done() - self.assertEqual(STATE_UNKNOWN, self.hass.states.get(entity_id).state) + assert STATE_UNKNOWN == self.hass.states.get(entity_id).state def test_arm_home_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" @@ -126,7 +126,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count common.alarm_arm_home(self.hass, 'abcd') self.hass.block_till_done() - self.assertEqual(call_count, self.mock_publish.call_count) + assert call_count == self.mock_publish.call_count def test_arm_away_publishes_mqtt(self): """Test publishing of MQTT messages while armed.""" @@ -159,7 +159,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count common.alarm_arm_away(self.hass, 'abcd') self.hass.block_till_done() - self.assertEqual(call_count, self.mock_publish.call_count) + assert call_count == self.mock_publish.call_count def test_disarm_publishes_mqtt(self): """Test publishing of MQTT messages while disarmed.""" @@ -192,7 +192,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count common.alarm_disarm(self.hass, 'abcd') self.hass.block_till_done() - self.assertEqual(call_count, self.mock_publish.call_count) + assert call_count == self.mock_publish.call_count def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" @@ -208,19 +208,19 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): }) state = self.hass.states.get('alarm_control_panel.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('alarm_control_panel.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('alarm_control_panel.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" @@ -238,7 +238,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): }) state = self.hass.states.get('alarm_control_panel.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 09d237013b0..b925e5a809d 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -48,7 +48,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context common.turn_off(self.hass) @@ -56,7 +56,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_event_extra_data(self): """Test the firing of events still matches with event data.""" @@ -74,14 +74,14 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'extra_key': 'extra_data'}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) common.turn_off(self.hass) self.hass.block_till_done() self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_event_with_data(self): """Test the firing of events with data.""" @@ -101,7 +101,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'some_attr': 'some_value', 'another': 'value'}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_event_with_empty_data_config(self): """Test the firing of events with empty data config. @@ -125,7 +125,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'some_attr': 'some_value', 'another': 'value'}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_event_with_nested_data(self): """Test the firing of events with nested data.""" @@ -153,7 +153,7 @@ class TestAutomationEvent(unittest.TestCase): } }) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_if_event_data_not_matches(self): """Test firing of event if no match.""" @@ -172,4 +172,4 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) diff --git a/tests/components/automation/test_geo_location.py b/tests/components/automation/test_geo_location.py index 130cdeef99c..f14ea34d297 100644 --- a/tests/components/automation/test_geo_location.py +++ b/tests/components/automation/test_geo_location.py @@ -75,11 +75,10 @@ class TestAutomationGeoLocation(unittest.TestCase): }, context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context - self.assertEqual( - 'geo_location - geo_location.entity - hello - hello - test', - self.calls[0].data['some']) + assert 'geo_location - geo_location.entity - hello - hello - test' == \ + self.calls[0].data['some'] # Set out of zone again so we can trigger call self.hass.states.set('geo_location.entity', 'hello', { @@ -97,7 +96,7 @@ class TestAutomationGeoLocation(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_for_enter_on_zone_leave(self): """Test for not firing on zone leave.""" @@ -128,7 +127,7 @@ class TestAutomationGeoLocation(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_zone_leave(self): """Test for firing on zone leave.""" @@ -160,7 +159,7 @@ class TestAutomationGeoLocation(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_for_leave_on_zone_enter(self): """Test for not firing on zone enter.""" @@ -191,7 +190,7 @@ class TestAutomationGeoLocation(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_zone_appear(self): """Test for firing if entity appears in zone.""" @@ -225,11 +224,10 @@ class TestAutomationGeoLocation(unittest.TestCase): }, context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context - self.assertEqual( - 'geo_location - geo_location.entity - - hello - test', - self.calls[0].data['some']) + assert 'geo_location - geo_location.entity - - hello - test' == \ + self.calls[0].data['some'] def test_if_fires_on_zone_disappear(self): """Test for firing if entity disappears from zone.""" @@ -265,7 +263,6 @@ class TestAutomationGeoLocation(unittest.TestCase): self.hass.states.async_remove('geo_location.entity') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'geo_location - geo_location.entity - hello - - test', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'geo_location - geo_location.entity - hello - - test' == \ + self.calls[0].data['some'] diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3bcbc7da04f..015c318874a 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -152,9 +152,9 @@ class TestAutomation(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual(['hello.world'], - self.calls[0].data.get(ATTR_ENTITY_ID)) + assert 1 == len(self.calls) + assert ['hello.world'] == \ + self.calls[0].data.get(ATTR_ENTITY_ID) def test_service_specify_entity_id_list(self): """Test service data.""" @@ -173,9 +173,9 @@ class TestAutomation(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual(['hello.world', 'hello.world2'], - self.calls[0].data.get(ATTR_ENTITY_ID)) + assert 1 == len(self.calls) + assert ['hello.world', 'hello.world2'] == \ + self.calls[0].data.get(ATTR_ENTITY_ID) def test_two_triggers(self): """Test triggers.""" @@ -199,10 +199,10 @@ class TestAutomation(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set('test.entity', 'hello') self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_trigger_service_ignoring_condition(self): """Test triggers.""" @@ -268,21 +268,21 @@ class TestAutomation(unittest.TestCase): self.hass.states.set(entity_id, 100) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set(entity_id, 101) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set(entity_id, 151) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_automation_list_setting(self): """Event is not a valid condition.""" - self.assertTrue(setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: [{ 'trigger': { 'platform': 'event', @@ -301,19 +301,19 @@ class TestAutomation(unittest.TestCase): 'service': 'test.automation', } }] - })) + }) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.bus.fire('test_event_2') self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_automation_calling_two_actions(self): """Test if we can call two actions from automation definition.""" - self.assertTrue(setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -328,7 +328,7 @@ class TestAutomation(unittest.TestCase): 'data': {'position': 1}, }], } - })) + }) self.hass.bus.fire('test_event') self.hass.block_till_done() diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 29a53467c4f..2d43c4a6d65 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -53,15 +53,15 @@ class TestAutomationMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', '{ "hello": "world" }') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('mqtt - test-topic - { "hello": "world" } - world', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'mqtt - test-topic - { "hello": "world" } - world' == \ + self.calls[0].data['some'] common.turn_off(self.hass) self.hass.block_till_done() fire_mqtt_message(self.hass, 'test-topic', 'test_payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_topic_and_payload_match(self): """Test if message is fired on topic and payload match.""" @@ -80,7 +80,7 @@ class TestAutomationMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'hello') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_topic_but_no_payload_match(self): """Test if message is not fired on topic but no payload.""" @@ -99,4 +99,4 @@ class TestAutomationMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'no-hello') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 183d1f4a5f9..f7124be9dab 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -53,7 +53,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9, context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context # Set above 12 so the automation will fire again @@ -62,7 +62,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.block_till_done() self.hass.states.set('test.entity', 9) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_over_to_below(self): """Test the firing with changed entity.""" @@ -85,7 +85,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entities_change_over_to_below(self): """Test the firing with changed entities.""" @@ -112,10 +112,10 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity_1', 9) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set('test.entity_2', 9) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_if_not_fires_on_entity_change_below_to_below(self): """Test the firing with changed entity.""" @@ -139,18 +139,18 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 so this should fire self.hass.states.set('test.entity', 9, context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context # already below so should not fire again self.hass.states.set('test.entity', 5) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) # still below so should not fire again self.hass.states.set('test.entity', 3) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_below_fires_on_entity_change_to_equal(self): """Test the firing with changed entity.""" @@ -173,7 +173,7 @@ class TestAutomationNumericState(unittest.TestCase): # 10 is not below 10 so this should not fire again self.hass.states.set('test.entity', 10) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_initial_entity_below(self): """Test the firing when starting with a match.""" @@ -196,7 +196,7 @@ class TestAutomationNumericState(unittest.TestCase): # Fire on first update even if initial state was already below self.hass.states.set('test.entity', 8) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_initial_entity_above(self): """Test the firing when starting with a match.""" @@ -219,7 +219,7 @@ class TestAutomationNumericState(unittest.TestCase): # Fire on first update even if initial state was already above self.hass.states.set('test.entity', 12) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_above(self): """Test the firing with changed entity.""" @@ -238,7 +238,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is above 10 self.hass.states.set('test.entity', 11) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_below_to_above(self): """Test the firing with changed entity.""" @@ -262,7 +262,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is above 10 and 9 is below self.hass.states.set('test.entity', 11) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_entity_change_above_to_above(self): """Test the firing with changed entity.""" @@ -286,12 +286,12 @@ class TestAutomationNumericState(unittest.TestCase): # 12 is above 10 so this should fire self.hass.states.set('test.entity', 12) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) # already above, should not fire again self.hass.states.set('test.entity', 15) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_above_fires_on_entity_change_to_equal(self): """Test the firing with changed entity.""" @@ -315,7 +315,7 @@ class TestAutomationNumericState(unittest.TestCase): # 10 is not above 10 so this should not fire again self.hass.states.set('test.entity', 10) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_entity_change_below_range(self): """Test the firing with changed entity.""" @@ -335,7 +335,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_below_above_range(self): """Test the firing with changed entity.""" @@ -355,7 +355,7 @@ class TestAutomationNumericState(unittest.TestCase): # 4 is below 5 self.hass.states.set('test.entity', 4) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_entity_change_over_to_below_range(self): """Test the firing with changed entity.""" @@ -379,7 +379,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_over_to_below_above_range(self): """Test the firing with changed entity.""" @@ -403,7 +403,7 @@ class TestAutomationNumericState(unittest.TestCase): # 4 is below 5 so it should not fire self.hass.states.set('test.entity', 4) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_if_entity_not_match(self): """Test if not fired with non matching entity.""" @@ -422,7 +422,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 11) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_entity_change_below_with_attribute(self): """Test attributes change.""" @@ -441,7 +441,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9, {'test_attribute': 11}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_entity_change_not_below_with_attribute(self): """Test attributes.""" @@ -460,7 +460,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is not below 10 self.hass.states.set('test.entity', 11, {'test_attribute': 9}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_attribute_change_with_attribute_below(self): """Test attributes change.""" @@ -480,7 +480,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 9}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_attribute_change_with_attribute_not_below(self): """Test attributes change.""" @@ -500,7 +500,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 11}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_entity_change_with_attribute_below(self): """Test attributes change.""" @@ -520,7 +520,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is not below 10, entity state value should not be tested self.hass.states.set('test.entity', '9', {'test_attribute': 11}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_entity_change_with_not_attribute_below(self): """Test attributes change.""" @@ -540,7 +540,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is not below 10, entity state value should not be tested self.hass.states.set('test.entity', 'entity') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(self): """Test attributes change.""" @@ -561,7 +561,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 'entity', {'test_attribute': 9, 'not_test_attribute': 11}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_template_list(self): """Test template list.""" @@ -583,7 +583,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 'entity', {'test_attribute': [11, 15, 3]}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_template_string(self): """Test template string.""" @@ -612,11 +612,10 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 'test state 2', {'test_attribute': '0.9'}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'numeric_state - test.entity - 10.0 - None - test state 1 - ' - 'test state 2', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'numeric_state - test.entity - 10.0 - None - test state 1 - ' \ + 'test state 2' == \ + self.calls[0].data['some'] def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self): """Test if not fired changed attributes.""" @@ -637,7 +636,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 'entity', {'test_attribute': 11, 'not_test_attribute': 9}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_action(self): """Test if action.""" @@ -664,19 +663,19 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set(entity_id, 8) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set(entity_id, 9) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_if_fails_setup_bad_for(self): """Test for setup failure for bad for.""" @@ -739,7 +738,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_entities_change_with_for_after_stop(self): """Test for not firing on entities change with for after stop.""" @@ -768,7 +767,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) self.hass.states.set('test.entity_1', 15) self.hass.states.set('test.entity_2', 15) @@ -781,7 +780,7 @@ class TestAutomationNumericState(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_if_fires_on_entity_change_with_for_attribute_change(self): """Test for firing on entity change with for and attribute change.""" @@ -812,11 +811,11 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 9, attributes={"mock_attr": "attr_change"}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) mock_utcnow.return_value += timedelta(seconds=4) fire_time_changed(self.hass, mock_utcnow.return_value) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_for(self): """Test for firing on entity change with for.""" @@ -841,7 +840,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_wait_template_with_trigger(self): """Test using wait template with 'trigger.entity_id'.""" @@ -872,7 +871,6 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.block_till_done() self.hass.states.set('test.entity', '8') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'numeric_state - test.entity - 12', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'numeric_state - test.entity - 12' == \ + self.calls[0].data['some'] diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 15c6353b234..599816ac7dc 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -63,17 +63,16 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world', context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context - self.assertEqual( - 'state - test.entity - hello - world - None', - self.calls[0].data['some']) + assert 'state - test.entity - hello - world - None' == \ + self.calls[0].data['some'] common.turn_off(self.hass) self.hass.block_till_done() self.hass.states.set('test.entity', 'planet') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_from_filter(self): """Test for firing on entity change with filter.""" @@ -92,7 +91,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_to_filter(self): """Test for firing on entity change with no filter.""" @@ -111,7 +110,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_attribute_change_with_to_filter(self): """Test for not firing on attribute change.""" @@ -131,7 +130,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world', {'test_attribute': 11}) self.hass.states.set('test.entity', 'world', {'test_attribute': 12}) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_both_filters(self): """Test for firing if both filters are a non match.""" @@ -151,7 +150,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_if_to_filter_not_match(self): """Test for not firing if to filter is not a match.""" @@ -171,7 +170,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'moon') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_if_from_filter_not_match(self): """Test for not firing if from filter is not a match.""" @@ -193,7 +192,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_if_entity_not_match(self): """Test for not firing if entity is not matching.""" @@ -211,7 +210,7 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_action(self): """Test for to action.""" @@ -238,13 +237,13 @@ class TestAutomationState(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) self.hass.states.set(entity_id, test_state + 'something') self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fails_setup_if_to_boolean_value(self): """Test for setup failure for boolean to.""" @@ -335,7 +334,7 @@ class TestAutomationState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_entities_change_with_for_after_stop(self): """Test for not firing on entity change with for after stop trigger.""" @@ -363,7 +362,7 @@ class TestAutomationState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) self.hass.states.set('test.entity_1', 'world_no') self.hass.states.set('test.entity_2', 'world_no') @@ -376,7 +375,7 @@ class TestAutomationState(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) def test_if_fires_on_entity_change_with_for_attribute_change(self): """Test for firing on entity change with for and attribute change.""" @@ -406,11 +405,11 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set('test.entity', 'world', attributes={"mock_attr": "attr_change"}) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) mock_utcnow.return_value += timedelta(seconds=4) fire_time_changed(self.hass, mock_utcnow.return_value) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_for_multiple_force_update(self): """Test for firing on entity change with for and force update.""" @@ -440,11 +439,11 @@ class TestAutomationState(unittest.TestCase): fire_time_changed(self.hass, mock_utcnow.return_value) self.hass.states.set('test.force_entity', 'world', None, True) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) mock_utcnow.return_value += timedelta(seconds=4) fire_time_changed(self.hass, mock_utcnow.return_value) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_entity_change_with_for(self): """Test for firing on entity change with for.""" @@ -468,7 +467,7 @@ class TestAutomationState(unittest.TestCase): self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_for_condition(self): """Test for firing if condition is on.""" @@ -498,13 +497,13 @@ class TestAutomationState(unittest.TestCase): # not enough time has passed self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Time travel 10 secs into the future mock_utcnow.return_value = point2 self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_for_condition_attribute_change(self): """Test for firing if condition is on with attribute change.""" @@ -535,7 +534,7 @@ class TestAutomationState(unittest.TestCase): # not enough time has passed self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Still not enough time has passed, but an attribute is changed mock_utcnow.return_value = point2 @@ -543,13 +542,13 @@ class TestAutomationState(unittest.TestCase): attributes={"mock_attr": "attr_change"}) self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Enough time has now passed mock_utcnow.return_value = point3 self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fails_setup_for_without_time(self): """Test for setup failure if no time is provided.""" @@ -615,7 +614,6 @@ class TestAutomationState(unittest.TestCase): self.hass.block_till_done() self.hass.states.set('test.entity', 'hello') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'state - test.entity - hello - world', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'state - test.entity - hello - world' == \ + self.calls[0].data['some'] diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index ad8709fdf36..3eb30b5594e 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -63,7 +63,7 @@ class TestAutomationSun(unittest.TestCase): fire_time_changed(self.hass, trigger_time) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) with patch('homeassistant.util.dt.utcnow', return_value=now): @@ -72,7 +72,7 @@ class TestAutomationSun(unittest.TestCase): fire_time_changed(self.hass, trigger_time) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_sunrise_trigger(self): """Test the sunrise trigger.""" @@ -95,7 +95,7 @@ class TestAutomationSun(unittest.TestCase): fire_time_changed(self.hass, trigger_time) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_sunset_trigger_with_offset(self): """Test the sunset trigger with offset.""" @@ -124,8 +124,8 @@ class TestAutomationSun(unittest.TestCase): fire_time_changed(self.hass, trigger_time) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('sun - sunset - 0:30:00', self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'sun - sunset - 0:30:00' == self.calls[0].data['some'] def test_sunrise_trigger_with_offset(self): """Test the sunrise trigger with offset.""" @@ -149,7 +149,7 @@ class TestAutomationSun(unittest.TestCase): fire_time_changed(self.hass, trigger_time) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_before(self): """Test if action was before.""" @@ -174,14 +174,14 @@ class TestAutomationSun(unittest.TestCase): return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_after(self): """Test if action was after.""" @@ -206,14 +206,14 @@ class TestAutomationSun(unittest.TestCase): return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_before_with_offset(self): """Test if action was before offset.""" @@ -239,14 +239,14 @@ class TestAutomationSun(unittest.TestCase): return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_after_with_offset(self): """Test if action was after offset.""" @@ -272,14 +272,14 @@ class TestAutomationSun(unittest.TestCase): return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_before_and_after_during(self): """Test if action was before and after during.""" @@ -305,18 +305,18 @@ class TestAutomationSun(unittest.TestCase): return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 17, 2, 25, 18, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) now = datetime(2015, 9, 16, 16, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=now): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 4fec0e707a9..9945677c123 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -48,14 +48,14 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) common.turn_off(self.hass) self.hass.block_till_done() self.hass.states.set('test.entity', 'planet') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_change_str(self): """Test for firing on change.""" @@ -73,7 +73,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_change_str_crazy(self): """Test for firing on change.""" @@ -91,7 +91,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_change_bool(self): """Test for not firing on boolean change.""" @@ -109,7 +109,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_change_str(self): """Test for not firing on string change.""" @@ -127,7 +127,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_on_change_str_crazy(self): """Test for not firing on string change.""" @@ -145,7 +145,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_no_change(self): """Test for firing on no change.""" @@ -166,7 +166,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'hello') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_two_change(self): """Test for firing on two changes.""" @@ -185,12 +185,12 @@ class TestAutomationTemplate(unittest.TestCase): # Trigger once self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) # Trigger again self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_change_with_template(self): """Test for firing on change with template.""" @@ -208,7 +208,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_on_change_with_template(self): """Test for not firing on change with template.""" @@ -257,11 +257,10 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world', context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context - self.assertEqual( - 'template - test.entity - hello - world', - self.calls[0].data['some']) + assert 'template - test.entity - hello - world' == \ + self.calls[0].data['some'] def test_if_fires_on_no_change_with_template_advanced(self): """Test for firing on no change with template advanced.""" @@ -284,12 +283,12 @@ class TestAutomationTemplate(unittest.TestCase): # Different state self.hass.states.set('test.entity', 'worldz') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Different state self.hass.states.set('test.entity', 'hello') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_change_with_template_2(self): """Test for firing on change with template.""" @@ -354,17 +353,17 @@ class TestAutomationTemplate(unittest.TestCase): # Condition is not true yet self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Change condition to true, but it shouldn't be triggered yet self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) # Condition is true and event is triggered self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_on_change_with_bad_template(self): """Test for firing on change with bad template.""" @@ -397,7 +396,7 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.states.set('test.entity', 'world') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_wait_template_with_trigger(self): """Test using wait template with 'trigger.entity_id'.""" @@ -429,7 +428,6 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.block_till_done() self.hass.states.set('test.entity', 'hello') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'template - test.entity - hello - world', - self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'template - test.entity - hello - world' == \ + self.calls[0].data['some'] diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index dcb723d725e..d1d9bcaecdf 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -51,14 +51,14 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) common.turn_off(self.hass) self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_when_minute_matches(self): """Test for firing if minutes are matching.""" @@ -77,7 +77,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_when_second_matches(self): """Test for firing if seconds are matching.""" @@ -96,7 +96,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_when_all_matches(self): """Test for firing if everything matches.""" @@ -118,7 +118,7 @@ class TestAutomationTime(unittest.TestCase): hour=1, minute=2, second=3)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_periodic_seconds(self): """Test for firing periodically every second.""" @@ -138,7 +138,7 @@ class TestAutomationTime(unittest.TestCase): hour=0, minute=0, second=2)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_periodic_minutes(self): """Test for firing periodically every minute.""" @@ -158,7 +158,7 @@ class TestAutomationTime(unittest.TestCase): hour=0, minute=2, second=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_periodic_hours(self): """Test for firing periodically every hour.""" @@ -178,7 +178,7 @@ class TestAutomationTime(unittest.TestCase): hour=2, minute=0, second=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_fires_using_at(self): """Test for firing at.""" @@ -202,8 +202,8 @@ class TestAutomationTime(unittest.TestCase): hour=5, minute=0, second=0)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('time - 5', self.calls[0].data['some']) + assert 1 == len(self.calls) + assert 'time - 5' == self.calls[0].data['some'] def test_if_not_working_if_no_values_in_conf_provided(self): """Test for failure if no configuration.""" @@ -223,7 +223,7 @@ class TestAutomationTime(unittest.TestCase): hour=5, minute=0, second=0)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_not_fires_using_wrong_at(self): """YAML translates time values to total seconds. @@ -248,7 +248,7 @@ class TestAutomationTime(unittest.TestCase): hour=1, minute=0, second=5)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_action_before(self): """Test for if action before.""" @@ -276,14 +276,14 @@ class TestAutomationTime(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) with patch('homeassistant.helpers.condition.dt_util.now', return_value=after_10): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_after(self): """Test for if action after.""" @@ -311,14 +311,14 @@ class TestAutomationTime(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) with patch('homeassistant.helpers.condition.dt_util.now', return_value=after_10): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_one_weekday(self): """Test for if action with one weekday.""" @@ -347,14 +347,14 @@ class TestAutomationTime(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) with patch('homeassistant.helpers.condition.dt_util.now', return_value=tuesday): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_action_list_weekday(self): """Test for action with a list of weekdays.""" @@ -384,18 +384,18 @@ class TestAutomationTime(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) with patch('homeassistant.helpers.condition.dt_util.now', return_value=tuesday): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) with patch('homeassistant.helpers.condition.dt_util.now', return_value=wednesday): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 795f55a3e0b..d0ab4d726ab 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -75,11 +75,10 @@ class TestAutomationZone(unittest.TestCase): }, context=context) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) assert self.calls[0].context is context - self.assertEqual( - 'zone - test.entity - hello - hello - test', - self.calls[0].data['some']) + assert 'zone - test.entity - hello - hello - test' == \ + self.calls[0].data['some'] # Set out of zone again so we can trigger call self.hass.states.set('test.entity', 'hello', { @@ -97,7 +96,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_for_enter_on_zone_leave(self): """Test for not firing on zone leave.""" @@ -127,7 +126,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_if_fires_on_zone_leave(self): """Test for firing on zone leave.""" @@ -157,7 +156,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_if_not_fires_for_leave_on_zone_enter(self): """Test for not firing on zone enter.""" @@ -187,7 +186,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_zone_condition(self): """Test for zone condition.""" @@ -216,4 +215,4 @@ class TestAutomationZone(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) diff --git a/tests/components/binary_sensor/test_aurora.py b/tests/components/binary_sensor/test_aurora.py index 1198aeb1357..109da8e8a43 100644 --- a/tests/components/binary_sensor/test_aurora.py +++ b/tests/components/binary_sensor/test_aurora.py @@ -50,17 +50,13 @@ class TestAuroraSensorSetUp(unittest.TestCase): aurora.setup_platform(self.hass, config, mock_add_entities) aurora_component = entities[0] - self.assertEqual(len(entities), 1) - self.assertEqual(aurora_component.name, "Test") - self.assertEqual( - aurora_component.device_state_attributes["visibility_level"], - '0' - ) - self.assertEqual( - aurora_component.device_state_attributes["message"], + assert len(entities) == 1 + assert aurora_component.name == "Test" + assert \ + aurora_component.device_state_attributes["visibility_level"] == '0' + assert aurora_component.device_state_attributes["message"] == \ "nothing's out" - ) - self.assertFalse(aurora_component.is_on) + assert not aurora_component.is_on @requests_mock.Mocker() def test_custom_threshold_works(self, mock_req): @@ -91,5 +87,5 @@ class TestAuroraSensorSetUp(unittest.TestCase): aurora.setup_platform(self.hass, config, mock_add_entities) aurora_component = entities[0] - self.assertEqual(aurora_component.aurora_data.visibility_level, '5') - self.assertTrue(aurora_component.is_on) + assert aurora_component.aurora_data.visibility_level == '5' + assert aurora_component.is_on diff --git a/tests/components/binary_sensor/test_bayesian.py b/tests/components/binary_sensor/test_bayesian.py index c3242e09e78..b52459ec47d 100644 --- a/tests/components/binary_sensor/test_bayesian.py +++ b/tests/components/binary_sensor/test_bayesian.py @@ -52,8 +52,8 @@ class TestBayesianBinarySensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([], state.attributes.get('observations')) - self.assertEqual(0.2, state.attributes.get('probability')) + assert [] == state.attributes.get('observations') + assert 0.2 == state.attributes.get('probability') assert state.state == 'off' @@ -66,14 +66,14 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([{ + assert [{ 'prob_false': 0.4, 'prob_true': 0.6 }, { 'prob_false': 0.1, 'prob_true': 0.9 - }], state.attributes.get('observations')) - self.assertAlmostEqual(0.77, state.attributes.get('probability')) + }] == state.attributes.get('observations') + assert round(abs(0.77-state.attributes.get('probability')), 7) == 0 assert state.state == 'on' @@ -84,7 +84,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual(0.2, state.attributes.get('probability')) + assert 0.2 == state.attributes.get('probability') assert state.state == 'off' @@ -123,8 +123,8 @@ class TestBayesianBinarySensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([], state.attributes.get('observations')) - self.assertEqual(0.2, state.attributes.get('probability')) + assert [] == state.attributes.get('observations') + assert 0.2 == state.attributes.get('probability') assert state.state == 'off' @@ -136,11 +136,11 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([{ + assert [{ 'prob_true': 0.8, 'prob_false': 0.4 - }], state.attributes.get('observations')) - self.assertAlmostEqual(0.33, state.attributes.get('probability')) + }] == state.attributes.get('observations') + assert round(abs(0.33-state.attributes.get('probability')), 7) == 0 assert state.state == 'on' @@ -150,7 +150,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertAlmostEqual(0.2, state.attributes.get('probability')) + assert round(abs(0.2-state.attributes.get('probability')), 7) == 0 assert state.state == 'off' @@ -181,7 +181,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertAlmostEqual(1.0, state.attributes.get('probability')) + assert round(abs(1.0-state.attributes.get('probability')), 7) == 0 assert state.state == 'on' @@ -219,8 +219,8 @@ class TestBayesianBinarySensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([], state.attributes.get('observations')) - self.assertEqual(0.2, state.attributes.get('probability')) + assert [] == state.attributes.get('observations') + assert 0.2 == state.attributes.get('probability') assert state.state == 'off' @@ -232,11 +232,11 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertEqual([{ + assert [{ 'prob_true': 0.8, 'prob_false': 0.4 - }], state.attributes.get('observations')) - self.assertAlmostEqual(0.33, state.attributes.get('probability')) + }] == state.attributes.get('observations') + assert round(abs(0.33-state.attributes.get('probability')), 7) == 0 assert state.state == 'on' @@ -246,7 +246,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_binary') - self.assertAlmostEqual(0.11, state.attributes.get('probability')) + assert round(abs(0.11-state.attributes.get('probability')), 7) == 0 assert state.state == 'off' @@ -259,7 +259,7 @@ class TestBayesianBinarySensor(unittest.TestCase): for pt, pf in zip(prob_true, prob_false): prior = bayesian.update_probability(prior, pt, pf) - self.assertAlmostEqual(0.720000, prior) + assert round(abs(0.720000-prior), 7) == 0 prob_true = [0.8, 0.3, 0.9] prob_false = [0.6, 0.4, 0.2] @@ -268,4 +268,4 @@ class TestBayesianBinarySensor(unittest.TestCase): for pt, pf in zip(prob_true, prob_false): prior = bayesian.update_probability(prior, pt, pf) - self.assertAlmostEqual(0.9130434782608695, prior) + assert round(abs(0.9130434782608695-prior), 7) == 0 diff --git a/tests/components/binary_sensor/test_binary_sensor.py b/tests/components/binary_sensor/test_binary_sensor.py index 89b6226c016..050af3e2c82 100644 --- a/tests/components/binary_sensor/test_binary_sensor.py +++ b/tests/components/binary_sensor/test_binary_sensor.py @@ -12,14 +12,14 @@ class TestBinarySensor(unittest.TestCase): def test_state(self): """Test binary sensor state.""" sensor = binary_sensor.BinarySensorDevice() - self.assertEqual(STATE_OFF, sensor.state) + assert STATE_OFF == sensor.state with mock.patch('homeassistant.components.binary_sensor.' 'BinarySensorDevice.is_on', new=False): - self.assertEqual(STATE_OFF, - binary_sensor.BinarySensorDevice().state) + assert STATE_OFF == \ + binary_sensor.BinarySensorDevice().state with mock.patch('homeassistant.components.binary_sensor.' 'BinarySensorDevice.is_on', new=True): - self.assertEqual(STATE_ON, - binary_sensor.BinarySensorDevice().state) + assert STATE_ON == \ + binary_sensor.BinarySensorDevice().state diff --git a/tests/components/binary_sensor/test_command_line.py b/tests/components/binary_sensor/test_command_line.py index d469fc65e8e..2a6b35ea5e0 100644 --- a/tests/components/binary_sensor/test_command_line.py +++ b/tests/components/binary_sensor/test_command_line.py @@ -37,11 +37,11 @@ class TestCommandSensorBinarySensor(unittest.TestCase): command_line.setup_platform(self.hass, config, add_dev_callback) - self.assertEqual(1, len(devices)) + assert 1 == len(devices) entity = devices[0] entity.update() - self.assertEqual('Test', entity.name) - self.assertEqual(STATE_ON, entity.state) + assert 'Test' == entity.name + assert STATE_ON == entity.state def test_template(self): """Test setting the state with a template.""" @@ -51,7 +51,7 @@ class TestCommandSensorBinarySensor(unittest.TestCase): self.hass, data, 'test', None, '1.0', '0', template.Template('{{ value | multiply(0.1) }}', self.hass)) entity.update() - self.assertEqual(STATE_ON, entity.state) + assert STATE_ON == entity.state def test_sensor_off(self): """Test setting the state with a template.""" @@ -60,4 +60,4 @@ class TestCommandSensorBinarySensor(unittest.TestCase): entity = command_line.CommandBinarySensor( self.hass, data, 'test', None, '1', '0', None) entity.update() - self.assertEqual(STATE_OFF, entity.state) + assert STATE_OFF == entity.state diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py index 8e9e7dcf301..d49bbb329e4 100644 --- a/tests/components/binary_sensor/test_mqtt.py +++ b/tests/components/binary_sensor/test_mqtt.py @@ -46,17 +46,17 @@ class TestSensorMQTT(unittest.TestCase): }) state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state fire_mqtt_message(self.hass, 'test-topic', 'OFF') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_valid_device_class(self): """Test the setting of a valid sensor class.""" @@ -70,7 +70,7 @@ class TestSensorMQTT(unittest.TestCase): }) state = self.hass.states.get('binary_sensor.test') - self.assertEqual('motion', state.attributes.get('device_class')) + assert 'motion' == state.attributes.get('device_class') def test_invalid_device_class(self): """Test the setting of an invalid sensor class.""" @@ -84,50 +84,50 @@ class TestSensorMQTT(unittest.TestCase): }) state = self.hass.states.get('binary_sensor.test') - self.assertIsNone(state) + assert state is None def test_availability_without_topic(self): """Test availability without defined availability topic.""" - self.assertTrue(setup_component(self.hass, binary_sensor.DOMAIN, { + assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'state-topic', } - })) + }) state = self.hass.states.get('binary_sensor.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state def test_availability_by_defaults(self): """Test availability by defaults with defined topic.""" - self.assertTrue(setup_component(self.hass, binary_sensor.DOMAIN, { + assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'state-topic', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_availability_by_custom_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, binary_sensor.DOMAIN, { + assert setup_component(self.hass, binary_sensor.DOMAIN, { binary_sensor.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -136,22 +136,22 @@ class TestSensorMQTT(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_force_update_disabled(self): """Test force update option.""" @@ -177,11 +177,11 @@ class TestSensorMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) def test_force_update_enabled(self): """Test force update option.""" @@ -208,11 +208,11 @@ class TestSensorMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() - self.assertEqual(2, len(events)) + assert 2 == len(events) def test_off_delay(self): """Test off_delay option.""" @@ -241,20 +241,20 @@ class TestSensorMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(1, len(events)) + assert STATE_ON == state.state + assert 1 == len(events) fire_mqtt_message(self.hass, 'test-topic', 'ON') self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(2, len(events)) + assert STATE_ON == state.state + assert 2 == len(events) fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=30)) self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(3, len(events)) + assert STATE_OFF == state.state + assert 3 == len(events) async def test_unique_id(hass): diff --git a/tests/components/binary_sensor/test_nx584.py b/tests/components/binary_sensor/test_nx584.py index 252996201bb..3239ddbc436 100644 --- a/tests/components/binary_sensor/test_nx584.py +++ b/tests/components/binary_sensor/test_nx584.py @@ -9,6 +9,7 @@ from homeassistant.components.binary_sensor import nx584 from homeassistant.setup import setup_component from tests.common import get_test_home_assistant +import pytest class StopMe(Exception): @@ -52,14 +53,13 @@ class TestNX584SensorSetup(unittest.TestCase): 'exclude_zones': [], 'zone_types': {}, } - self.assertTrue(nx584.setup_platform(self.hass, config, add_entities)) + assert nx584.setup_platform(self.hass, config, add_entities) mock_nx.assert_has_calls( [mock.call(zone, 'opening') for zone in self.fake_zones]) - self.assertTrue(add_entities.called) - self.assertEqual(nx584_client.Client.call_count, 1) - self.assertEqual( - nx584_client.Client.call_args, mock.call('http://localhost:5007') - ) + assert add_entities.called + assert nx584_client.Client.call_count == 1 + assert nx584_client.Client.call_args == \ + mock.call('http://localhost:5007') @mock.patch('homeassistant.components.binary_sensor.nx584.NX584Watcher') @mock.patch('homeassistant.components.binary_sensor.nx584.NX584ZoneSensor') @@ -72,22 +72,20 @@ class TestNX584SensorSetup(unittest.TestCase): 'zone_types': {3: 'motion'}, } add_entities = mock.MagicMock() - self.assertTrue(nx584.setup_platform(self.hass, config, add_entities)) + assert nx584.setup_platform(self.hass, config, add_entities) mock_nx.assert_has_calls([ mock.call(self.fake_zones[0], 'opening'), mock.call(self.fake_zones[2], 'motion'), ]) - self.assertTrue(add_entities.called) - self.assertEqual(nx584_client.Client.call_count, 1) - self.assertEqual( - nx584_client.Client.call_args, mock.call('http://foo:123') - ) - self.assertTrue(mock_watcher.called) + assert add_entities.called + assert nx584_client.Client.call_count == 1 + assert nx584_client.Client.call_args == mock.call('http://foo:123') + assert mock_watcher.called def _test_assert_graceful_fail(self, config): """Test the failing.""" - self.assertFalse(setup_component( - self.hass, 'binary_sensor.nx584', config)) + assert not setup_component( + self.hass, 'binary_sensor.nx584', config) def test_setup_bad_config(self): """Test the setup with bad configuration.""" @@ -121,8 +119,8 @@ class TestNX584SensorSetup(unittest.TestCase): """Test the setup with no zones.""" nx584_client.Client.return_value.list_zones.return_value = [] add_entities = mock.MagicMock() - self.assertTrue(nx584.setup_platform(self.hass, {}, add_entities)) - self.assertFalse(add_entities.called) + assert nx584.setup_platform(self.hass, {}, add_entities) + assert not add_entities.called class TestNX584ZoneSensor(unittest.TestCase): @@ -132,12 +130,12 @@ class TestNX584ZoneSensor(unittest.TestCase): """Test the sensor.""" zone = {'number': 1, 'name': 'foo', 'state': True} sensor = nx584.NX584ZoneSensor(zone, 'motion') - self.assertEqual('foo', sensor.name) - self.assertFalse(sensor.should_poll) - self.assertTrue(sensor.is_on) + assert 'foo' == sensor.name + assert not sensor.should_poll + assert sensor.is_on zone['state'] = False - self.assertFalse(sensor.is_on) + assert not sensor.is_on class TestNX584Watcher(unittest.TestCase): @@ -154,15 +152,15 @@ class TestNX584Watcher(unittest.TestCase): } watcher = nx584.NX584Watcher(None, zones) watcher._process_zone_event({'zone': 1, 'zone_state': False}) - self.assertFalse(zone1['state']) - self.assertEqual(1, mock_update.call_count) + assert not zone1['state'] + assert 1 == mock_update.call_count @mock.patch.object(nx584.NX584ZoneSensor, 'schedule_update_ha_state') def test_process_zone_event_missing_zone(self, mock_update): """Test the processing of zone events with missing zones.""" watcher = nx584.NX584Watcher(None, {}) watcher._process_zone_event({'zone': 1, 'zone_state': False}) - self.assertFalse(mock_update.called) + assert not mock_update.called def test_run_with_zone_events(self): """Test the zone events.""" @@ -187,12 +185,13 @@ class TestNX584Watcher(unittest.TestCase): def run(fake_process): """Run a fake process.""" fake_process.side_effect = StopMe - self.assertRaises(StopMe, watcher._run) - self.assertEqual(fake_process.call_count, 1) - self.assertEqual(fake_process.call_args, mock.call(fake_events[0])) + with pytest.raises(StopMe): + watcher._run() + assert fake_process.call_count == 1 + assert fake_process.call_args == mock.call(fake_events[0]) run() - self.assertEqual(3, client.get_events.call_count) + assert 3 == client.get_events.call_count @mock.patch('time.sleep') def test_run_retries_failures(self, mock_sleep): @@ -210,6 +209,7 @@ class TestNX584Watcher(unittest.TestCase): watcher = nx584.NX584Watcher(None, {}) with mock.patch.object(watcher, '_run') as mock_inner: mock_inner.side_effect = fake_run - self.assertRaises(StopMe, watcher.run) - self.assertEqual(3, mock_inner.call_count) + with pytest.raises(StopMe): + watcher.run() + assert 3 == mock_inner.call_count mock_sleep.assert_has_calls([mock.call(10), mock.call(10)]) diff --git a/tests/components/binary_sensor/test_random.py b/tests/components/binary_sensor/test_random.py index 9ec1990158d..45f2e384ba4 100644 --- a/tests/components/binary_sensor/test_random.py +++ b/tests/components/binary_sensor/test_random.py @@ -32,7 +32,7 @@ class TestRandomSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.test') - self.assertEqual(state.state, 'on') + assert state.state == 'on' @patch('random.getrandbits', return_value=False) def test_random_binary_sensor_off(self, mocked): @@ -48,4 +48,4 @@ class TestRandomSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.test') - self.assertEqual(state.state, 'off') + assert state.state == 'off' diff --git a/tests/components/binary_sensor/test_rest.py b/tests/components/binary_sensor/test_rest.py index db70303e217..d3df1a32ac7 100644 --- a/tests/components/binary_sensor/test_rest.py +++ b/tests/components/binary_sensor/test_rest.py @@ -15,6 +15,7 @@ from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers import template from tests.common import get_test_home_assistant, assert_setup_component +import pytest class TestRestBinarySensorSetup(unittest.TestCase): @@ -45,7 +46,7 @@ class TestRestBinarySensorSetup(unittest.TestCase): def test_setup_missing_schema(self): """Test setup with resource missing schema.""" - with self.assertRaises(MissingSchema): + with pytest.raises(MissingSchema): rest.setup_platform(self.hass, { 'platform': 'rest', 'resource': 'localhost', @@ -61,7 +62,7 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'platform': 'rest', 'resource': 'http://localhost', }, self.add_devices, None) - self.assertEqual(len(self.DEVICES), 0) + assert len(self.DEVICES) == 0 @patch('requests.Session.send', side_effect=Timeout()) def test_setup_timeout(self, mock_req): @@ -71,27 +72,27 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'platform': 'rest', 'resource': 'http://localhost', }, self.add_devices, None) - self.assertEqual(len(self.DEVICES), 0) + assert len(self.DEVICES) == 0 @requests_mock.Mocker() def test_setup_minimum(self, mock_req): """Test setup with minimum configuration.""" mock_req.get('http://localhost', status_code=200) with assert_setup_component(1, 'binary_sensor'): - self.assertTrue(setup_component(self.hass, 'binary_sensor', { + assert setup_component(self.hass, 'binary_sensor', { 'binary_sensor': { 'platform': 'rest', 'resource': 'http://localhost' } - })) - self.assertEqual(1, mock_req.call_count) + }) + assert 1 == mock_req.call_count @requests_mock.Mocker() def test_setup_get(self, mock_req): """Test setup with valid configuration.""" mock_req.get('http://localhost', status_code=200) with assert_setup_component(1, 'binary_sensor'): - self.assertTrue(setup_component(self.hass, 'binary_sensor', { + assert setup_component(self.hass, 'binary_sensor', { 'binary_sensor': { 'platform': 'rest', 'resource': 'http://localhost', @@ -105,15 +106,15 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'password': 'my password', 'headers': {'Accept': 'application/json'} } - })) - self.assertEqual(1, mock_req.call_count) + }) + assert 1 == mock_req.call_count @requests_mock.Mocker() def test_setup_post(self, mock_req): """Test setup with valid configuration.""" mock_req.post('http://localhost', status_code=200) with assert_setup_component(1, 'binary_sensor'): - self.assertTrue(setup_component(self.hass, 'binary_sensor', { + assert setup_component(self.hass, 'binary_sensor', { 'binary_sensor': { 'platform': 'rest', 'resource': 'http://localhost', @@ -128,8 +129,8 @@ class TestRestBinarySensorSetup(unittest.TestCase): 'password': 'my password', 'headers': {'Accept': 'application/json'} } - })) - self.assertEqual(1, mock_req.call_count) + }) + assert 1 == mock_req.call_count class TestRestBinarySensor(unittest.TestCase): @@ -161,16 +162,16 @@ class TestRestBinarySensor(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual(self.name, self.binary_sensor.name) + assert self.name == self.binary_sensor.name def test_device_class(self): """Test the device class.""" - self.assertEqual(self.device_class, self.binary_sensor.device_class) + assert self.device_class == self.binary_sensor.device_class def test_initial_state(self): """Test the initial state.""" self.binary_sensor.update() - self.assertEqual(STATE_OFF, self.binary_sensor.state) + assert STATE_OFF == self.binary_sensor.state def test_update_when_value_is_none(self): """Test state gets updated to unknown when sensor returns no data.""" @@ -178,7 +179,7 @@ class TestRestBinarySensor(unittest.TestCase): 'RestData.update', side_effect=self.update_side_effect(None)) self.binary_sensor.update() - self.assertFalse(self.binary_sensor.available) + assert not self.binary_sensor.available def test_update_when_value_changed(self): """Test state gets updated when sensor returns a new status.""" @@ -186,15 +187,15 @@ class TestRestBinarySensor(unittest.TestCase): side_effect=self.update_side_effect( '{ "key": true }')) self.binary_sensor.update() - self.assertEqual(STATE_ON, self.binary_sensor.state) - self.assertTrue(self.binary_sensor.available) + assert STATE_ON == self.binary_sensor.state + assert self.binary_sensor.available def test_update_when_failed_request(self): """Test state gets updated when sensor returns a new status.""" self.rest.update = Mock('rest.RestData.update', side_effect=self.update_side_effect(None)) self.binary_sensor.update() - self.assertFalse(self.binary_sensor.available) + assert not self.binary_sensor.available def test_update_with_no_template(self): """Test update when there is no value template.""" @@ -203,5 +204,5 @@ class TestRestBinarySensor(unittest.TestCase): self.binary_sensor = rest.RestBinarySensor( self.hass, self.rest, self.name, self.device_class, None) self.binary_sensor.update() - self.assertEqual(STATE_ON, self.binary_sensor.state) - self.assertTrue(self.binary_sensor.available) + assert STATE_ON == self.binary_sensor.state + assert self.binary_sensor.available diff --git a/tests/components/binary_sensor/test_ring.py b/tests/components/binary_sensor/test_ring.py index b7564dff464..a4b243e7420 100644 --- a/tests/components/binary_sensor/test_ring.py +++ b/tests/components/binary_sensor/test_ring.py @@ -64,13 +64,13 @@ class TestRingBinarySensorSetup(unittest.TestCase): for device in self.DEVICES: device.update() if device.name == 'Front Door Ding': - self.assertEqual('on', device.state) - self.assertEqual('America/New_York', - device.device_state_attributes['timezone']) + assert 'on' == device.state + assert 'America/New_York' == \ + device.device_state_attributes['timezone'] elif device.name == 'Front Door Motion': - self.assertEqual('off', device.state) - self.assertEqual('motion', device.device_class) + assert 'off' == device.state + assert 'motion' == device.device_class - self.assertIsNone(device.entity_picture) - self.assertEqual(ATTRIBUTION, - device.device_state_attributes['attribution']) + assert device.entity_picture is None + assert ATTRIBUTION == \ + device.device_state_attributes['attribution'] diff --git a/tests/components/binary_sensor/test_sleepiq.py b/tests/components/binary_sensor/test_sleepiq.py index 9cd5dfa7f2e..3f3d69ad9d3 100644 --- a/tests/components/binary_sensor/test_sleepiq.py +++ b/tests/components/binary_sensor/test_sleepiq.py @@ -47,12 +47,12 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase): self.config, self.add_entities, MagicMock()) - self.assertEqual(2, len(self.DEVICES)) + assert 2 == len(self.DEVICES) left_side = self.DEVICES[1] - self.assertEqual('SleepNumber ILE Test1 Is In Bed', left_side.name) - self.assertEqual('on', left_side.state) + assert 'SleepNumber ILE Test1 Is In Bed' == left_side.name + assert 'on' == left_side.state right_side = self.DEVICES[0] - self.assertEqual('SleepNumber ILE Test2 Is In Bed', right_side.name) - self.assertEqual('off', right_side.state) + assert 'SleepNumber ILE Test2 Is In Bed' == right_side.name + assert 'off' == right_side.state diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index eba3a368f56..7307b222436 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -168,18 +168,18 @@ class TestBinarySensorTemplate(unittest.TestCase): template_hlpr.Template('{{ 1 > 1 }}', self.hass), None, None, MATCH_ALL, None, None ).result() - self.assertFalse(vs.should_poll) - self.assertEqual('motion', vs.device_class) - self.assertEqual('Parent', vs.name) + assert not vs.should_poll + assert 'motion' == vs.device_class + assert 'Parent' == vs.name run_callback_threadsafe(self.hass.loop, vs.async_check_state).result() - self.assertFalse(vs.is_on) + assert not vs.is_on # pylint: disable=protected-access vs._template = template_hlpr.Template("{{ 2 > 1 }}", self.hass) run_callback_threadsafe(self.hass.loop, vs.async_check_state).result() - self.assertTrue(vs.is_on) + assert vs.is_on def test_event(self): """Test the event.""" diff --git a/tests/components/binary_sensor/test_threshold.py b/tests/components/binary_sensor/test_threshold.py index 926b3c67983..2537282ed6c 100644 --- a/tests/components/binary_sensor/test_threshold.py +++ b/tests/components/binary_sensor/test_threshold.py @@ -37,14 +37,14 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('sensor.test_monitored', - state.attributes.get('entity_id')) - self.assertEqual(16, state.attributes.get('sensor_value')) - self.assertEqual('above', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) - self.assertEqual(0.0, state.attributes.get('hysteresis')) - self.assertEqual('upper', state.attributes.get('type')) + assert 'sensor.test_monitored' == \ + state.attributes.get('entity_id') + assert 16 == state.attributes.get('sensor_value') + assert 'above' == state.attributes.get('position') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') + assert 0.0 == state.attributes.get('hysteresis') + assert 'upper' == state.attributes.get('type') assert state.state == 'on' @@ -79,11 +79,11 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('above', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['lower']), - state.attributes.get('lower')) - self.assertEqual(0.0, state.attributes.get('hysteresis')) - self.assertEqual('lower', state.attributes.get('type')) + assert 'above' == state.attributes.get('position') + assert float(config['binary_sensor']['lower']) == \ + state.attributes.get('lower') + assert 0.0 == state.attributes.get('hysteresis') + assert 'lower' == state.attributes.get('type') assert state.state == 'off' @@ -112,11 +112,11 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('above', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) - self.assertEqual(2.5, state.attributes.get('hysteresis')) - self.assertEqual('upper', state.attributes.get('type')) + assert 'above' == state.attributes.get('position') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') + assert 2.5 == state.attributes.get('hysteresis') + assert 'upper' == state.attributes.get('type') assert state.state == 'on' @@ -167,16 +167,16 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('sensor.test_monitored', - state.attributes.get('entity_id')) - self.assertEqual(16, state.attributes.get('sensor_value')) - self.assertEqual('in_range', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['lower']), - state.attributes.get('lower')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) - self.assertEqual(0.0, state.attributes.get('hysteresis')) - self.assertEqual('range', state.attributes.get('type')) + assert 'sensor.test_monitored' == \ + state.attributes.get('entity_id') + assert 16 == state.attributes.get('sensor_value') + assert 'in_range' == state.attributes.get('position') + assert float(config['binary_sensor']['lower']) == \ + state.attributes.get('lower') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') + assert 0.0 == state.attributes.get('hysteresis') + assert 'range' == state.attributes.get('type') assert state.state == 'on' @@ -185,7 +185,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('below', state.attributes.get('position')) + assert 'below' == state.attributes.get('position') assert state.state == 'off' self.hass.states.set('sensor.test_monitored', 21) @@ -193,7 +193,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('above', state.attributes.get('position')) + assert 'above' == state.attributes.get('position') assert state.state == 'off' def test_sensor_in_range_with_hysteresis(self): @@ -216,17 +216,17 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('sensor.test_monitored', - state.attributes.get('entity_id')) - self.assertEqual(16, state.attributes.get('sensor_value')) - self.assertEqual('in_range', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['lower']), - state.attributes.get('lower')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) - self.assertEqual(float(config['binary_sensor']['hysteresis']), - state.attributes.get('hysteresis')) - self.assertEqual('range', state.attributes.get('type')) + assert 'sensor.test_monitored' == \ + state.attributes.get('entity_id') + assert 16 == state.attributes.get('sensor_value') + assert 'in_range' == state.attributes.get('position') + assert float(config['binary_sensor']['lower']) == \ + state.attributes.get('lower') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') + assert float(config['binary_sensor']['hysteresis']) == \ + state.attributes.get('hysteresis') + assert 'range' == state.attributes.get('type') assert state.state == 'on' @@ -235,7 +235,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('in_range', state.attributes.get('position')) + assert 'in_range' == state.attributes.get('position') assert state.state == 'on' self.hass.states.set('sensor.test_monitored', 7) @@ -243,7 +243,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('below', state.attributes.get('position')) + assert 'below' == state.attributes.get('position') assert state.state == 'off' self.hass.states.set('sensor.test_monitored', 12) @@ -251,7 +251,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('below', state.attributes.get('position')) + assert 'below' == state.attributes.get('position') assert state.state == 'off' self.hass.states.set('sensor.test_monitored', 13) @@ -259,7 +259,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('in_range', state.attributes.get('position')) + assert 'in_range' == state.attributes.get('position') assert state.state == 'on' self.hass.states.set('sensor.test_monitored', 22) @@ -267,7 +267,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('in_range', state.attributes.get('position')) + assert 'in_range' == state.attributes.get('position') assert state.state == 'on' self.hass.states.set('sensor.test_monitored', 23) @@ -275,7 +275,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('above', state.attributes.get('position')) + assert 'above' == state.attributes.get('position') assert state.state == 'off' self.hass.states.set('sensor.test_monitored', 18) @@ -283,7 +283,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('above', state.attributes.get('position')) + assert 'above' == state.attributes.get('position') assert state.state == 'off' self.hass.states.set('sensor.test_monitored', 17) @@ -291,7 +291,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('in_range', state.attributes.get('position')) + assert 'in_range' == state.attributes.get('position') assert state.state == 'on' def test_sensor_in_range_unknown_state(self): @@ -313,16 +313,16 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('sensor.test_monitored', - state.attributes.get('entity_id')) - self.assertEqual(16, state.attributes.get('sensor_value')) - self.assertEqual('in_range', state.attributes.get('position')) - self.assertEqual(float(config['binary_sensor']['lower']), - state.attributes.get('lower')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) - self.assertEqual(0.0, state.attributes.get('hysteresis')) - self.assertEqual('range', state.attributes.get('type')) + assert 'sensor.test_monitored' == \ + state.attributes.get('entity_id') + assert 16 == state.attributes.get('sensor_value') + assert 'in_range' == state.attributes.get('position') + assert float(config['binary_sensor']['lower']) == \ + state.attributes.get('lower') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') + assert 0.0 == state.attributes.get('hysteresis') + assert 'range' == state.attributes.get('type') assert state.state == 'on' @@ -331,7 +331,7 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('unknown', state.attributes.get('position')) + assert 'unknown' == state.attributes.get('position') assert state.state == 'off' def test_sensor_lower_zero_threshold(self): @@ -351,9 +351,9 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('lower', state.attributes.get('type')) - self.assertEqual(float(config['binary_sensor']['lower']), - state.attributes.get('lower')) + assert 'lower' == state.attributes.get('type') + assert float(config['binary_sensor']['lower']) == \ + state.attributes.get('lower') assert state.state == 'off' @@ -381,9 +381,9 @@ class TestThresholdSensor(unittest.TestCase): state = self.hass.states.get('binary_sensor.threshold') - self.assertEqual('upper', state.attributes.get('type')) - self.assertEqual(float(config['binary_sensor']['upper']), - state.attributes.get('upper')) + assert 'upper' == state.attributes.get('type') + assert float(config['binary_sensor']['upper']) == \ + state.attributes.get('upper') assert state.state == 'off' diff --git a/tests/components/binary_sensor/test_vultr.py b/tests/components/binary_sensor/test_vultr.py index f356149ddde..ee19cf941a3 100644 --- a/tests/components/binary_sensor/test_vultr.py +++ b/tests/components/binary_sensor/test_vultr.py @@ -74,53 +74,53 @@ class TestVultrBinarySensorSetup(unittest.TestCase): self.add_entities, None) - self.assertEqual(len(self.DEVICES), 3) + assert len(self.DEVICES) == 3 for device in self.DEVICES: # Test pre data retrieval if device.subscription == '555555': - self.assertEqual('Vultr {}', device.name) + assert 'Vultr {}' == device.name device.update() device_attrs = device.device_state_attributes if device.subscription == '555555': - self.assertEqual('Vultr Another Server', device.name) + assert 'Vultr Another Server' == device.name if device.name == 'A Server': - self.assertEqual(True, device.is_on) - self.assertEqual('power', device.device_class) - self.assertEqual('on', device.state) - self.assertEqual('mdi:server', device.icon) - self.assertEqual('1000', - device_attrs[ATTR_ALLOWED_BANDWIDTH]) - self.assertEqual('yes', - device_attrs[ATTR_AUTO_BACKUPS]) - self.assertEqual('123.123.123.123', - device_attrs[ATTR_IPV4_ADDRESS]) - self.assertEqual('10.05', - device_attrs[ATTR_COST_PER_MONTH]) - self.assertEqual('2013-12-19 14:45:41', - device_attrs[ATTR_CREATED_AT]) - self.assertEqual('576965', - device_attrs[ATTR_SUBSCRIPTION_ID]) + assert device.is_on is True + assert 'power' == device.device_class + assert 'on' == device.state + assert 'mdi:server' == device.icon + assert '1000' == \ + device_attrs[ATTR_ALLOWED_BANDWIDTH] + assert 'yes' == \ + device_attrs[ATTR_AUTO_BACKUPS] + assert '123.123.123.123' == \ + device_attrs[ATTR_IPV4_ADDRESS] + assert '10.05' == \ + device_attrs[ATTR_COST_PER_MONTH] + assert '2013-12-19 14:45:41' == \ + device_attrs[ATTR_CREATED_AT] + assert '576965' == \ + device_attrs[ATTR_SUBSCRIPTION_ID] elif device.name == 'Failed Server': - self.assertEqual(False, device.is_on) - self.assertEqual('off', device.state) - self.assertEqual('mdi:server-off', device.icon) - self.assertEqual('1000', - device_attrs[ATTR_ALLOWED_BANDWIDTH]) - self.assertEqual('no', - device_attrs[ATTR_AUTO_BACKUPS]) - self.assertEqual('192.168.100.50', - device_attrs[ATTR_IPV4_ADDRESS]) - self.assertEqual('73.25', - device_attrs[ATTR_COST_PER_MONTH]) - self.assertEqual('2014-10-13 14:45:41', - device_attrs[ATTR_CREATED_AT]) - self.assertEqual('123456', - device_attrs[ATTR_SUBSCRIPTION_ID]) + assert device.is_on is False + assert 'off' == device.state + assert 'mdi:server-off' == device.icon + assert '1000' == \ + device_attrs[ATTR_ALLOWED_BANDWIDTH] + assert 'no' == \ + device_attrs[ATTR_AUTO_BACKUPS] + assert '192.168.100.50' == \ + device_attrs[ATTR_IPV4_ADDRESS] + assert '73.25' == \ + device_attrs[ATTR_COST_PER_MONTH] + assert '2014-10-13 14:45:41' == \ + device_attrs[ATTR_CREATED_AT] + assert '123456' == \ + device_attrs[ATTR_SUBSCRIPTION_ID] def test_invalid_sensor_config(self): """Test config type failures.""" @@ -150,7 +150,7 @@ class TestVultrBinarySensorSetup(unittest.TestCase): self.add_entities, None) - self.assertFalse(no_subs_setup) + assert not no_subs_setup bad_conf = { CONF_NAME: "Missing Server", @@ -162,4 +162,4 @@ class TestVultrBinarySensorSetup(unittest.TestCase): self.add_entities, None) - self.assertFalse(wrong_subs_setup) + assert not wrong_subs_setup diff --git a/tests/components/calendar/test_caldav.py b/tests/components/calendar/test_caldav.py index c5dadbc56ea..45de0052ce6 100644 --- a/tests/components/calendar/test_caldav.py +++ b/tests/components/calendar/test_caldav.py @@ -178,10 +178,10 @@ class TestComponentsWebDavCalendar(unittest.TestCase): assert len(devices) == 2 assert devices[0].name == "First" assert devices[0].dev_id == "First" - self.assertFalse(devices[0].data.include_all_day) + assert not devices[0].data.include_all_day assert devices[1].name == "Second" assert devices[1].dev_id == "Second" - self.assertFalse(devices[1].data.include_all_day) + assert not devices[1].data.include_all_day caldav.setup_platform(self.hass, { @@ -226,7 +226,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): assert len(devices) == 1 assert devices[0].name == "HomeOffice" assert devices[0].dev_id == "Second HomeOffice" - self.assertTrue(devices[0].data.include_all_day) + assert devices[0].data.include_all_day caldav.setup_platform(self.hass, { @@ -247,9 +247,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): DEVICE_DATA, self.calendar) - self.assertEqual(cal.name, DEVICE_DATA["name"]) - self.assertEqual(cal.state, STATE_ON) - self.assertEqual(cal.device_state_attributes, { + assert cal.name == DEVICE_DATA["name"] + assert cal.state == STATE_ON + assert cal.device_state_attributes == { "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -257,7 +257,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-27 18:00:00", "location": "Hamburg", "description": "Surprisingly rainy", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(17, 30)) def test_just_ended_event(self, mock_now): @@ -266,9 +266,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): DEVICE_DATA, self.calendar) - self.assertEqual(cal.name, DEVICE_DATA["name"]) - self.assertEqual(cal.state, STATE_ON) - self.assertEqual(cal.device_state_attributes, { + assert cal.name == DEVICE_DATA["name"] + assert cal.state == STATE_ON + assert cal.device_state_attributes == { "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -276,7 +276,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-27 18:00:00", "location": "Hamburg", "description": "Surprisingly rainy", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(17, 00)) def test_ongoing_event_different_tz(self, mock_now): @@ -285,9 +285,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): DEVICE_DATA, self.calendar) - self.assertEqual(cal.name, DEVICE_DATA["name"]) - self.assertEqual(cal.state, STATE_ON) - self.assertEqual(cal.device_state_attributes, { + assert cal.name == DEVICE_DATA["name"] + assert cal.state == STATE_ON + assert cal.device_state_attributes == { "message": "Enjoy the sun", "all_day": False, "offset_reached": False, @@ -295,7 +295,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "description": "Sunny day", "end_time": "2017-11-27 17:30:00", "location": "San Francisco", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(8, 30)) def test_ongoing_event_with_offset(self, mock_now): @@ -304,8 +304,8 @@ class TestComponentsWebDavCalendar(unittest.TestCase): DEVICE_DATA, self.calendar) - self.assertEqual(cal.state, STATE_OFF) - self.assertEqual(cal.device_state_attributes, { + assert cal.state == STATE_OFF + assert cal.device_state_attributes == { "message": "This is an offset event", "all_day": False, "offset_reached": True, @@ -313,7 +313,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-27 11:00:00", "location": "Hamburg", "description": "Surprisingly shiny", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(12, 00)) def test_matching_filter(self, mock_now): @@ -324,9 +324,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): False, "This is a normal event") - self.assertEqual(cal.state, STATE_OFF) - self.assertFalse(cal.offset_reached()) - self.assertEqual(cal.device_state_attributes, { + assert cal.state == STATE_OFF + assert not cal.offset_reached() + assert cal.device_state_attributes == { "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -334,7 +334,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-27 18:00:00", "location": "Hamburg", "description": "Surprisingly rainy", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(12, 00)) def test_matching_filter_real_regexp(self, mock_now): @@ -345,9 +345,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): False, "^This.*event") - self.assertEqual(cal.state, STATE_OFF) - self.assertFalse(cal.offset_reached()) - self.assertEqual(cal.device_state_attributes, { + assert cal.state == STATE_OFF + assert not cal.offset_reached() + assert cal.device_state_attributes == { "message": "This is a normal event", "all_day": False, "offset_reached": False, @@ -355,7 +355,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-27 18:00:00", "location": "Hamburg", "description": "Surprisingly rainy", - }) + } @patch('homeassistant.util.dt.now', return_value=_local_datetime(20, 00)) def test_filter_matching_past_event(self, mock_now): @@ -366,7 +366,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): False, "This is a normal event") - self.assertEqual(cal.data.event, None) + assert cal.data.event is None @patch('homeassistant.util.dt.now', return_value=_local_datetime(12, 00)) def test_no_result_with_filtering(self, mock_now): @@ -377,7 +377,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase): False, "This is a non-existing event") - self.assertEqual(cal.data.event, None) + assert cal.data.event is None @patch('homeassistant.util.dt.now', return_value=_local_datetime(17, 30)) def test_all_day_event_returned(self, mock_now): @@ -387,9 +387,9 @@ class TestComponentsWebDavCalendar(unittest.TestCase): self.calendar, True) - self.assertEqual(cal.name, DEVICE_DATA["name"]) - self.assertEqual(cal.state, STATE_ON) - self.assertEqual(cal.device_state_attributes, { + assert cal.name == DEVICE_DATA["name"] + assert cal.state == STATE_ON + assert cal.device_state_attributes == { "message": "This is an all day event", "all_day": True, "offset_reached": False, @@ -397,4 +397,4 @@ class TestComponentsWebDavCalendar(unittest.TestCase): "end_time": "2017-11-28 00:00:00", "location": "Hamburg", "description": "What a beautiful day", - }) + } diff --git a/tests/components/calendar/test_google.py b/tests/components/calendar/test_google.py index e07f7a6306a..ec4089677d8 100644 --- a/tests/components/calendar/test_google.py +++ b/tests/components/calendar/test_google.py @@ -87,13 +87,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, '', {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_OFF) + assert cal.state == STATE_OFF - self.assertFalse(cal.offset_reached()) + assert not cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event['summary'], 'all_day': True, 'offset_reached': False, @@ -101,7 +101,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'end_time': '{} 00:00:00'.format(event['end']['date']), 'location': event['location'], 'description': event['description'], - }) + } @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_future_event(self, mock_next_event): @@ -146,13 +146,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, device_id, {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_OFF) + assert cal.state == STATE_OFF - self.assertFalse(cal.offset_reached()) + assert not cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event['summary'], 'all_day': False, 'offset_reached': False, @@ -162,7 +162,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): .strftime(DATE_STR_FORMAT), 'location': '', 'description': '', - }) + } @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_in_progress_event(self, mock_next_event): @@ -208,13 +208,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, device_id, {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_ON) + assert cal.state == STATE_ON - self.assertFalse(cal.offset_reached()) + assert not cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event['summary'], 'all_day': False, 'offset_reached': False, @@ -224,7 +224,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): .strftime(DATE_STR_FORMAT), 'location': '', 'description': '', - }) + } @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_offset_in_progress_event(self, mock_next_event): @@ -271,13 +271,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, device_id, {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_OFF) + assert cal.state == STATE_OFF - self.assertTrue(cal.offset_reached()) + assert cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event_summary, 'all_day': False, 'offset_reached': True, @@ -287,7 +287,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): .strftime(DATE_STR_FORMAT), 'location': '', 'description': '', - }) + } @pytest.mark.skip @patch('homeassistant.components.calendar.google.GoogleCalendarData') @@ -340,13 +340,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, device_id, {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_OFF) + assert cal.state == STATE_OFF - self.assertTrue(cal.offset_reached()) + assert cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event_summary, 'all_day': True, 'offset_reached': True, @@ -354,7 +354,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'end_time': '{} 06:00:00'.format(event['end']['date']), 'location': event['location'], 'description': event['description'], - }) + } @patch('homeassistant.components.calendar.google.GoogleCalendarData') def test_all_day_offset_event(self, mock_next_event): @@ -407,13 +407,13 @@ class TestComponentsGoogleCalendar(unittest.TestCase): cal = calendar.GoogleCalendarEventDevice(self.hass, None, device_id, {'name': device_name}) - self.assertEqual(cal.name, device_name) + assert cal.name == device_name - self.assertEqual(cal.state, STATE_OFF) + assert cal.state == STATE_OFF - self.assertFalse(cal.offset_reached()) + assert not cal.offset_reached() - self.assertEqual(cal.device_state_attributes, { + assert cal.device_state_attributes == { 'message': event_summary, 'all_day': True, 'offset_reached': False, @@ -421,7 +421,7 @@ class TestComponentsGoogleCalendar(unittest.TestCase): 'end_time': '{} 00:00:00'.format(event['end']['date']), 'location': event['location'], 'description': event['description'], - }) + } @MockDependency("httplib2") def test_update_false(self, mock_httplib2): @@ -434,4 +434,4 @@ class TestComponentsGoogleCalendar(unittest.TestCase): {'name': "test"}) result = cal.data.update() - self.assertFalse(result) + assert not result diff --git a/tests/components/camera/test_uvc.py b/tests/components/camera/test_uvc.py index 328ba5096ea..b41cb9f865b 100644 --- a/tests/components/camera/test_uvc.py +++ b/tests/components/camera/test_uvc.py @@ -11,6 +11,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import setup_component from homeassistant.components.camera import uvc from tests.common import get_test_home_assistant +import pytest class TestUVCSetup(unittest.TestCase): @@ -53,10 +54,8 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - self.assertEqual(mock_remote.call_count, 1) - self.assertEqual( - mock_remote.call_args, mock.call('foo', 123, 'secret') - ) + assert mock_remote.call_count == 1 + assert mock_remote.call_args == mock.call('foo', 123, 'secret') mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'id1', 'Front', 'bar'), mock.call(mock_remote.return_value, 'id2', 'Back', 'bar'), @@ -81,10 +80,8 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - self.assertEqual(mock_remote.call_count, 1) - self.assertEqual( - mock_remote.call_args, mock.call('foo', 7080, 'secret') - ) + assert mock_remote.call_count == 1 + assert mock_remote.call_args == mock.call('foo', 7080, 'secret') mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'id1', 'Front', 'ubnt'), mock.call(mock_remote.return_value, 'id2', 'Back', 'ubnt'), @@ -109,10 +106,8 @@ class TestUVCSetup(unittest.TestCase): assert setup_component(self.hass, 'camera', {'camera': config}) - self.assertEqual(mock_remote.call_count, 1) - self.assertEqual( - mock_remote.call_args, mock.call('foo', 7080, 'secret') - ) + assert mock_remote.call_count == 1 + assert mock_remote.call_args == mock.call('foo', 7080, 'secret') mock_uvc.assert_has_calls([ mock.call(mock_remote.return_value, 'one', 'Front', 'ubnt'), mock.call(mock_remote.return_value, 'two', 'Back', 'ubnt'), @@ -151,13 +146,13 @@ class TestUVCSetup(unittest.TestCase): def test_setup_nvr_error_during_indexing_nvrerror(self): """Test for error: nvr.NvrError.""" self.setup_nvr_errors_during_indexing(nvr.NvrError) - self.assertRaises(PlatformNotReady) + pytest.raises(PlatformNotReady) def test_setup_nvr_error_during_indexing_connectionerror(self): """Test for error: requests.exceptions.ConnectionError.""" self.setup_nvr_errors_during_indexing( requests.exceptions.ConnectionError) - self.assertRaises(PlatformNotReady) + pytest.raises(PlatformNotReady) @mock.patch.object(uvc, 'UnifiVideoCamera') @mock.patch('uvcclient.nvr.UVCRemote.__init__') @@ -182,13 +177,13 @@ class TestUVCSetup(unittest.TestCase): def test_setup_nvr_error_during_initialization_nvrerror(self): """Test for error: nvr.NvrError.""" self.setup_nvr_errors_during_initialization(nvr.NvrError) - self.assertRaises(PlatformNotReady) + pytest.raises(PlatformNotReady) def test_setup_nvr_error_during_initialization_connectionerror(self): """Test for error: requests.exceptions.ConnectionError.""" self.setup_nvr_errors_during_initialization( requests.exceptions.ConnectionError) - self.assertRaises(PlatformNotReady) + pytest.raises(PlatformNotReady) class TestUVC(unittest.TestCase): @@ -215,22 +210,20 @@ class TestUVC(unittest.TestCase): def test_properties(self): """Test the properties.""" - self.assertEqual(self.name, self.uvc.name) - self.assertTrue(self.uvc.is_recording) - self.assertEqual('Ubiquiti', self.uvc.brand) - self.assertEqual('UVC Fake', self.uvc.model) + assert self.name == self.uvc.name + assert self.uvc.is_recording + assert 'Ubiquiti' == self.uvc.brand + assert 'UVC Fake' == self.uvc.model @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') def test_login(self, mock_camera, mock_store): """Test the login.""" self.uvc._login() - self.assertEqual(mock_camera.call_count, 1) - self.assertEqual( - mock_camera.call_args, mock.call('host-a', 'admin', 'seekret') - ) - self.assertEqual(mock_camera.return_value.login.call_count, 1) - self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) + assert mock_camera.call_count == 1 + assert mock_camera.call_args == mock.call('host-a', 'admin', 'seekret') + assert mock_camera.return_value.login.call_count == 1 + assert mock_camera.return_value.login.call_args == mock.call() @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClient') @@ -238,12 +231,10 @@ class TestUVC(unittest.TestCase): """Test login with v3.1.x server.""" self.nvr.server_version = (3, 1, 3) self.uvc._login() - self.assertEqual(mock_camera.call_count, 1) - self.assertEqual( - mock_camera.call_args, mock.call('host-a', 'admin', 'seekret') - ) - self.assertEqual(mock_camera.return_value.login.call_count, 1) - self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) + assert mock_camera.call_count == 1 + assert mock_camera.call_args == mock.call('host-a', 'admin', 'seekret') + assert mock_camera.return_value.login.call_count == 1 + assert mock_camera.return_value.login.call_args == mock.call() @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') @@ -262,45 +253,43 @@ class TestUVC(unittest.TestCase): mock_store.return_value.get_camera_password.return_value = None mock_camera.return_value.login.side_effect = mock_login self.uvc._login() - self.assertEqual(2, mock_camera.call_count) - self.assertEqual('host-b', self.uvc._connect_addr) + assert 2 == mock_camera.call_count + assert 'host-b' == self.uvc._connect_addr mock_camera.reset_mock() self.uvc._login() - self.assertEqual(mock_camera.call_count, 1) - self.assertEqual( - mock_camera.call_args, mock.call('host-b', 'admin', 'seekret') - ) - self.assertEqual(mock_camera.return_value.login.call_count, 1) - self.assertEqual(mock_camera.return_value.login.call_args, mock.call()) + assert mock_camera.call_count == 1 + assert mock_camera.call_args == mock.call('host-b', 'admin', 'seekret') + assert mock_camera.return_value.login.call_count == 1 + assert mock_camera.return_value.login.call_args == mock.call() @mock.patch('uvcclient.store.get_info_store') @mock.patch('uvcclient.camera.UVCCameraClientV320') def test_login_fails_both_properly(self, mock_camera, mock_store): """Test if login fails properly.""" mock_camera.return_value.login.side_effect = socket.error - self.assertEqual(None, self.uvc._login()) - self.assertEqual(None, self.uvc._connect_addr) + assert self.uvc._login() is None + assert self.uvc._connect_addr is None def test_camera_image_tries_login_bails_on_failure(self): """Test retrieving failure.""" with mock.patch.object(self.uvc, '_login') as mock_login: mock_login.return_value = False - self.assertEqual(None, self.uvc.camera_image()) - self.assertEqual(mock_login.call_count, 1) - self.assertEqual(mock_login.call_args, mock.call()) + assert self.uvc.camera_image() is None + assert mock_login.call_count == 1 + assert mock_login.call_args == mock.call() def test_camera_image_logged_in(self): """Test the login state.""" self.uvc._camera = mock.MagicMock() - self.assertEqual(self.uvc._camera.get_snapshot.return_value, - self.uvc.camera_image()) + assert self.uvc._camera.get_snapshot.return_value == \ + self.uvc.camera_image() def test_camera_image_error(self): """Test the camera image error.""" self.uvc._camera = mock.MagicMock() self.uvc._camera.get_snapshot.side_effect = camera.CameraConnectError - self.assertEqual(None, self.uvc.camera_image()) + assert self.uvc.camera_image() is None def test_camera_image_reauths(self): """Test the re-authentication.""" @@ -318,16 +307,17 @@ class TestUVC(unittest.TestCase): self.uvc._camera = mock.MagicMock() self.uvc._camera.get_snapshot.side_effect = mock_snapshot with mock.patch.object(self.uvc, '_login') as mock_login: - self.assertEqual('image', self.uvc.camera_image()) - self.assertEqual(mock_login.call_count, 1) - self.assertEqual(mock_login.call_args, mock.call()) - self.assertEqual([], responses) + assert 'image' == self.uvc.camera_image() + assert mock_login.call_count == 1 + assert mock_login.call_args == mock.call() + assert [] == responses def test_camera_image_reauths_only_once(self): """Test if the re-authentication only happens once.""" self.uvc._camera = mock.MagicMock() self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError with mock.patch.object(self.uvc, '_login') as mock_login: - self.assertRaises(camera.CameraAuthError, self.uvc.camera_image) - self.assertEqual(mock_login.call_count, 1) - self.assertEqual(mock_login.call_args, mock.call()) + with pytest.raises(camera.CameraAuthError): + self.uvc.camera_image() + assert mock_login.call_count == 1 + assert mock_login.call_args == mock.call() diff --git a/tests/components/climate/test_demo.py b/tests/components/climate/test_demo.py index 4990a8a6998..462939af23a 100644 --- a/tests/components/climate/test_demo.py +++ b/tests/components/climate/test_demo.py @@ -23,10 +23,10 @@ class TestDemoClimate(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - self.assertTrue(setup_component(self.hass, climate.DOMAIN, { + assert setup_component(self.hass, climate.DOMAIN, { 'climate': { 'platform': 'demo', - }})) + }}) def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" @@ -35,132 +35,132 @@ class TestDemoClimate(unittest.TestCase): def test_setup_params(self): """Test the initial parameters.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(21, state.attributes.get('temperature')) - self.assertEqual('on', state.attributes.get('away_mode')) - self.assertEqual(22, state.attributes.get('current_temperature')) - self.assertEqual("On High", state.attributes.get('fan_mode')) - self.assertEqual(67, state.attributes.get('humidity')) - self.assertEqual(54, state.attributes.get('current_humidity')) - self.assertEqual("Off", state.attributes.get('swing_mode')) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 21 == state.attributes.get('temperature') + assert 'on' == state.attributes.get('away_mode') + assert 22 == state.attributes.get('current_temperature') + assert "On High" == state.attributes.get('fan_mode') + assert 67 == state.attributes.get('humidity') + assert 54 == state.attributes.get('current_humidity') + assert "Off" == state.attributes.get('swing_mode') + assert "cool" == state.attributes.get('operation_mode') + assert 'off' == state.attributes.get('aux_heat') def test_default_setup_params(self): """Test the setup with default parameters.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(7, state.attributes.get('min_temp')) - self.assertEqual(35, state.attributes.get('max_temp')) - self.assertEqual(30, state.attributes.get('min_humidity')) - self.assertEqual(99, state.attributes.get('max_humidity')) + assert 7 == state.attributes.get('min_temp') + assert 35 == state.attributes.get('max_temp') + assert 30 == state.attributes.get('min_humidity') + assert 99 == state.attributes.get('max_humidity') def test_set_only_target_temp_bad_attr(self): """Test setting the target temperature without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(21, state.attributes.get('temperature')) + assert 21 == state.attributes.get('temperature') common.set_temperature(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() - self.assertEqual(21, state.attributes.get('temperature')) + assert 21 == state.attributes.get('temperature') def test_set_only_target_temp(self): """Test the setting of the target temperature.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(21, state.attributes.get('temperature')) + assert 21 == state.attributes.get('temperature') common.set_temperature(self.hass, 30, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(30.0, state.attributes.get('temperature')) + assert 30.0 == state.attributes.get('temperature') def test_set_only_target_temp_with_convert(self): """Test the setting of the target temperature.""" state = self.hass.states.get(ENTITY_HEATPUMP) - self.assertEqual(20, state.attributes.get('temperature')) + assert 20 == state.attributes.get('temperature') common.set_temperature(self.hass, 21, ENTITY_HEATPUMP) self.hass.block_till_done() state = self.hass.states.get(ENTITY_HEATPUMP) - self.assertEqual(21.0, state.attributes.get('temperature')) + assert 21.0 == state.attributes.get('temperature') def test_set_target_temp_range(self): """Test the setting of the target temperature with range.""" state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual(None, state.attributes.get('temperature')) - self.assertEqual(21.0, state.attributes.get('target_temp_low')) - self.assertEqual(24.0, state.attributes.get('target_temp_high')) + assert state.attributes.get('temperature') is None + assert 21.0 == state.attributes.get('target_temp_low') + assert 24.0 == state.attributes.get('target_temp_high') common.set_temperature(self.hass, target_temp_high=25, target_temp_low=20, entity_id=ENTITY_ECOBEE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual(None, state.attributes.get('temperature')) - self.assertEqual(20.0, state.attributes.get('target_temp_low')) - self.assertEqual(25.0, state.attributes.get('target_temp_high')) + assert state.attributes.get('temperature') is None + assert 20.0 == state.attributes.get('target_temp_low') + assert 25.0 == state.attributes.get('target_temp_high') def test_set_target_temp_range_bad_attr(self): """Test setting the target temperature range without attribute.""" state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual(None, state.attributes.get('temperature')) - self.assertEqual(21.0, state.attributes.get('target_temp_low')) - self.assertEqual(24.0, state.attributes.get('target_temp_high')) + assert state.attributes.get('temperature') is None + assert 21.0 == state.attributes.get('target_temp_low') + assert 24.0 == state.attributes.get('target_temp_high') common.set_temperature(self.hass, temperature=None, entity_id=ENTITY_ECOBEE, target_temp_low=None, target_temp_high=None) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual(None, state.attributes.get('temperature')) - self.assertEqual(21.0, state.attributes.get('target_temp_low')) - self.assertEqual(24.0, state.attributes.get('target_temp_high')) + assert state.attributes.get('temperature') is None + assert 21.0 == state.attributes.get('target_temp_low') + assert 24.0 == state.attributes.get('target_temp_high') def test_set_target_humidity_bad_attr(self): """Test setting the target humidity without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(67, state.attributes.get('humidity')) + assert 67 == state.attributes.get('humidity') common.set_humidity(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(67, state.attributes.get('humidity')) + assert 67 == state.attributes.get('humidity') def test_set_target_humidity(self): """Test the setting of the target humidity.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(67, state.attributes.get('humidity')) + assert 67 == state.attributes.get('humidity') common.set_humidity(self.hass, 64, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(64.0, state.attributes.get('humidity')) + assert 64.0 == state.attributes.get('humidity') def test_set_fan_mode_bad_attr(self): """Test setting fan mode without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("On High", state.attributes.get('fan_mode')) + assert "On High" == state.attributes.get('fan_mode') common.set_fan_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("On High", state.attributes.get('fan_mode')) + assert "On High" == state.attributes.get('fan_mode') def test_set_fan_mode(self): """Test setting of new fan mode.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("On High", state.attributes.get('fan_mode')) + assert "On High" == state.attributes.get('fan_mode') common.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("On Low", state.attributes.get('fan_mode')) + assert "On Low" == state.attributes.get('fan_mode') def test_set_swing_mode_bad_attr(self): """Test setting swing mode without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("Off", state.attributes.get('swing_mode')) + assert "Off" == state.attributes.get('swing_mode') common.set_swing_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("Off", state.attributes.get('swing_mode')) + assert "Off" == state.attributes.get('swing_mode') def test_set_swing(self): """Test setting of new swing mode.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("Off", state.attributes.get('swing_mode')) + assert "Off" == state.attributes.get('swing_mode') common.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("Auto", state.attributes.get('swing_mode')) + assert "Auto" == state.attributes.get('swing_mode') def test_set_operation_bad_attr_and_state(self): """Test setting operation mode without required attribute. @@ -168,103 +168,103 @@ class TestDemoClimate(unittest.TestCase): Also check the state. """ state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state common.set_operation_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state def test_set_operation(self): """Test setting of new operation mode.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state common.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("heat", state.attributes.get('operation_mode')) - self.assertEqual("heat", state.state) + assert "heat" == state.attributes.get('operation_mode') + assert "heat" == state.state def test_set_away_mode_bad_attr(self): """Test setting the away mode without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') common.set_away_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') def test_set_away_mode_on(self): """Test setting the away mode on/true.""" common.set_away_mode(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') def test_set_away_mode_off(self): """Test setting the away mode off/false.""" common.set_away_mode(self.hass, False, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') def test_set_hold_mode_home(self): """Test setting the hold mode home.""" common.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('home', state.attributes.get('hold_mode')) + assert 'home' == state.attributes.get('hold_mode') def test_set_hold_mode_away(self): """Test setting the hold mode away.""" common.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('away', state.attributes.get('hold_mode')) + assert 'away' == state.attributes.get('hold_mode') def test_set_hold_mode_none(self): """Test setting the hold mode off/false.""" common.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('off', state.attributes.get('hold_mode')) + assert 'off' == state.attributes.get('hold_mode') def test_set_aux_heat_bad_attr(self): """Test setting the auxiliary heater without required attribute.""" state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') common.set_aux_heat(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') def test_set_aux_heat_on(self): """Test setting the axillary heater on/true.""" common.set_aux_heat(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('aux_heat')) + assert 'on' == state.attributes.get('aux_heat') def test_set_aux_heat_off(self): """Test setting the auxiliary heater off/false.""" common.set_aux_heat(self.hass, False, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') def test_set_on_off(self): """Test on/off service.""" state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('auto', state.state) + assert 'auto' == state.state self.hass.services.call(climate.DOMAIN, climate.SERVICE_TURN_OFF, {climate.ATTR_ENTITY_ID: ENTITY_ECOBEE}) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('off', state.state) + assert 'off' == state.state self.hass.services.call(climate.DOMAIN, climate.SERVICE_TURN_ON, {climate.ATTR_ENTITY_ID: ENTITY_ECOBEE}) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ECOBEE) - self.assertEqual('auto', state.state) + assert 'auto' == state.state diff --git a/tests/components/climate/test_dyson.py b/tests/components/climate/test_dyson.py index 6e8b63d64c4..e2cfffe6458 100644 --- a/tests/components/climate/test_dyson.py +++ b/tests/components/climate/test_dyson.py @@ -123,7 +123,7 @@ class DysonTest(unittest.TestCase): dyson_parent.CONF_LANGUAGE: "US", } }) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 2) + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 2 self.hass.block_till_done() for m in mocked_devices.return_value: assert m.add_message_listener.called @@ -145,7 +145,7 @@ class DysonTest(unittest.TestCase): self.hass.data[dyson.DYSON_DEVICES] = devices add_devices = mock.MagicMock() dyson.setup_platform(self.hass, None, add_devices, discovery_info={}) - self.assertTrue(add_devices.called) + assert add_devices.called def test_setup_component_with_invalid_devices(self): """Test setup component with invalid devices.""" @@ -175,7 +175,7 @@ class DysonTest(unittest.TestCase): device = _get_device_heat_on() device.temp_unit = TEMP_CELSIUS entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertFalse(entity.should_poll) + assert not entity.should_poll # Without target temp. kwargs = {} @@ -223,7 +223,7 @@ class DysonTest(unittest.TestCase): """Test set fan mode.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertFalse(entity.should_poll) + assert not entity.should_poll entity.set_fan_mode(dyson.STATE_FOCUS) set_config = device.set_configuration @@ -237,27 +237,27 @@ class DysonTest(unittest.TestCase): """Test get fan list.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(len(entity.fan_list), 2) - self.assertTrue(dyson.STATE_FOCUS in entity.fan_list) - self.assertTrue(dyson.STATE_DIFFUSE in entity.fan_list) + assert len(entity.fan_list) == 2 + assert dyson.STATE_FOCUS in entity.fan_list + assert dyson.STATE_DIFFUSE in entity.fan_list def test_dyson_fan_mode_focus(self): """Test fan focus mode.""" device = _get_device_focus() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_fan_mode, dyson.STATE_FOCUS) + assert entity.current_fan_mode == dyson.STATE_FOCUS def test_dyson_fan_mode_diffuse(self): """Test fan diffuse mode.""" device = _get_device_diffuse() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_fan_mode, dyson.STATE_DIFFUSE) + assert entity.current_fan_mode == dyson.STATE_DIFFUSE def test_dyson_set_operation_mode(self): """Test set operation mode.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertFalse(entity.should_poll) + assert not entity.should_poll entity.set_operation_mode(dyson.STATE_HEAT) set_config = device.set_configuration @@ -271,9 +271,9 @@ class DysonTest(unittest.TestCase): """Test get operation list.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(len(entity.operation_list), 2) - self.assertTrue(dyson.STATE_HEAT in entity.operation_list) - self.assertTrue(dyson.STATE_COOL in entity.operation_list) + assert len(entity.operation_list) == 2 + assert dyson.STATE_HEAT in entity.operation_list + assert dyson.STATE_COOL in entity.operation_list def test_dyson_heat_off(self): """Test turn off heat.""" @@ -295,19 +295,19 @@ class DysonTest(unittest.TestCase): """Test get heat value on.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_operation, dyson.STATE_HEAT) + assert entity.current_operation == dyson.STATE_HEAT def test_dyson_heat_value_off(self): """Test get heat value off.""" device = _get_device_cool() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_operation, dyson.STATE_COOL) + assert entity.current_operation == dyson.STATE_COOL def test_dyson_heat_value_idle(self): """Test get heat value idle.""" device = _get_device_heat_off() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_operation, dyson.STATE_IDLE) + assert entity.current_operation == dyson.STATE_IDLE def test_on_message(self): """Test when message is received.""" @@ -321,38 +321,38 @@ class DysonTest(unittest.TestCase): """Test properties of entity.""" device = _get_device_with_no_state() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.should_poll, False) - self.assertEqual(entity.supported_features, dyson.SUPPORT_FLAGS) - self.assertEqual(entity.temperature_unit, TEMP_CELSIUS) + assert entity.should_poll is False + assert entity.supported_features == dyson.SUPPORT_FLAGS + assert entity.temperature_unit == TEMP_CELSIUS def test_property_current_humidity(self): """Test properties of current humidity.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_humidity, 53) + assert entity.current_humidity == 53 def test_property_current_humidity_with_invalid_env_state(self): """Test properties of current humidity with invalid env state.""" device = _get_device_off() device.environmental_state.humidity = 0 entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_humidity, None) + assert entity.current_humidity is None def test_property_current_humidity_without_env_state(self): """Test properties of current humidity without env state.""" device = _get_device_with_no_state() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.current_humidity, None) + assert entity.current_humidity is None def test_property_current_temperature(self): """Test properties of current temperature.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) # Result should be in celsius, hence then subtraction of 273. - self.assertEqual(entity.current_temperature, 289 - 273) + assert entity.current_temperature == 289 - 273 def test_property_target_temperature(self): """Test properties of target temperature.""" device = _get_device_heat_on() entity = dyson.DysonPureHotCoolLinkDevice(device) - self.assertEqual(entity.target_temperature, 23) + assert entity.target_temperature == 23 diff --git a/tests/components/climate/test_ecobee.py b/tests/components/climate/test_ecobee.py index eb843d8eb34..8a03cbcd191 100644 --- a/tests/components/climate/test_ecobee.py +++ b/tests/components/climate/test_ecobee.py @@ -44,214 +44,214 @@ class TestEcobee(unittest.TestCase): def test_name(self): """Test name property.""" - self.assertEqual('Ecobee', self.thermostat.name) + assert 'Ecobee' == self.thermostat.name def test_temperature_unit(self): """Test temperature unit property.""" - self.assertEqual(const.TEMP_FAHRENHEIT, - self.thermostat.temperature_unit) + assert const.TEMP_FAHRENHEIT == \ + self.thermostat.temperature_unit def test_current_temperature(self): """Test current temperature.""" - self.assertEqual(30, self.thermostat.current_temperature) + assert 30 == self.thermostat.current_temperature self.ecobee['runtime']['actualTemperature'] = 404 - self.assertEqual(40.4, self.thermostat.current_temperature) + assert 40.4 == self.thermostat.current_temperature def test_target_temperature_low(self): """Test target low temperature.""" - self.assertEqual(40, self.thermostat.target_temperature_low) + assert 40 == self.thermostat.target_temperature_low self.ecobee['runtime']['desiredHeat'] = 502 - self.assertEqual(50.2, self.thermostat.target_temperature_low) + assert 50.2 == self.thermostat.target_temperature_low def test_target_temperature_high(self): """Test target high temperature.""" - self.assertEqual(20, self.thermostat.target_temperature_high) + assert 20 == self.thermostat.target_temperature_high self.ecobee['runtime']['desiredCool'] = 103 - self.assertEqual(10.3, self.thermostat.target_temperature_high) + assert 10.3 == self.thermostat.target_temperature_high def test_target_temperature(self): """Test target temperature.""" - self.assertIsNone(self.thermostat.target_temperature) + assert self.thermostat.target_temperature is None self.ecobee['settings']['hvacMode'] = 'heat' - self.assertEqual(40, self.thermostat.target_temperature) + assert 40 == self.thermostat.target_temperature self.ecobee['settings']['hvacMode'] = 'cool' - self.assertEqual(20, self.thermostat.target_temperature) + assert 20 == self.thermostat.target_temperature self.ecobee['settings']['hvacMode'] = 'auxHeatOnly' - self.assertEqual(40, self.thermostat.target_temperature) + assert 40 == self.thermostat.target_temperature self.ecobee['settings']['hvacMode'] = 'off' - self.assertIsNone(self.thermostat.target_temperature) + assert self.thermostat.target_temperature is None def test_desired_fan_mode(self): """Test desired fan mode property.""" - self.assertEqual('on', self.thermostat.current_fan_mode) + assert 'on' == self.thermostat.current_fan_mode self.ecobee['runtime']['desiredFanMode'] = 'auto' - self.assertEqual('auto', self.thermostat.current_fan_mode) + assert 'auto' == self.thermostat.current_fan_mode def test_fan(self): """Test fan property.""" - self.assertEqual(const.STATE_ON, self.thermostat.fan) + assert const.STATE_ON == self.thermostat.fan self.ecobee['equipmentStatus'] = '' - self.assertEqual(STATE_OFF, self.thermostat.fan) + assert STATE_OFF == self.thermostat.fan self.ecobee['equipmentStatus'] = 'heatPump, heatPump2' - self.assertEqual(STATE_OFF, self.thermostat.fan) + assert STATE_OFF == self.thermostat.fan def test_current_hold_mode_away_temporary(self): """Test current hold mode when away.""" # Temporary away hold - self.assertEqual('away', self.thermostat.current_hold_mode) + assert 'away' == self.thermostat.current_hold_mode self.ecobee['events'][0]['endDate'] = '2018-01-01 09:49:00' - self.assertEqual('away', self.thermostat.current_hold_mode) + assert 'away' == self.thermostat.current_hold_mode def test_current_hold_mode_away_permanent(self): """Test current hold mode when away permanently.""" # Permanent away hold self.ecobee['events'][0]['endDate'] = '2019-01-01 10:17:00' - self.assertIsNone(self.thermostat.current_hold_mode) + assert self.thermostat.current_hold_mode is None def test_current_hold_mode_no_running_events(self): """Test current hold mode when no running events.""" # No running events self.ecobee['events'][0]['running'] = False - self.assertIsNone(self.thermostat.current_hold_mode) + assert self.thermostat.current_hold_mode is None def test_current_hold_mode_vacation(self): """Test current hold mode when on vacation.""" # Vacation Hold self.ecobee['events'][0]['type'] = 'vacation' - self.assertEqual('vacation', self.thermostat.current_hold_mode) + assert 'vacation' == self.thermostat.current_hold_mode def test_current_hold_mode_climate(self): """Test current hold mode when heat climate is set.""" # Preset climate hold self.ecobee['events'][0]['type'] = 'hold' self.ecobee['events'][0]['holdClimateRef'] = 'heatClimate' - self.assertEqual('heatClimate', self.thermostat.current_hold_mode) + assert 'heatClimate' == self.thermostat.current_hold_mode def test_current_hold_mode_temperature_hold(self): """Test current hold mode when temperature hold is set.""" # Temperature hold self.ecobee['events'][0]['type'] = 'hold' self.ecobee['events'][0]['holdClimateRef'] = '' - self.assertEqual('temp', self.thermostat.current_hold_mode) + assert 'temp' == self.thermostat.current_hold_mode def test_current_hold_mode_auto_hold(self): """Test current hold mode when auto heat is set.""" # auto Hold self.ecobee['events'][0]['type'] = 'autoHeat' - self.assertEqual('heat', self.thermostat.current_hold_mode) + assert 'heat' == self.thermostat.current_hold_mode def test_current_operation(self): """Test current operation property.""" - self.assertEqual('auto', self.thermostat.current_operation) + assert 'auto' == self.thermostat.current_operation self.ecobee['settings']['hvacMode'] = 'heat' - self.assertEqual('heat', self.thermostat.current_operation) + assert 'heat' == self.thermostat.current_operation self.ecobee['settings']['hvacMode'] = 'cool' - self.assertEqual('cool', self.thermostat.current_operation) + assert 'cool' == self.thermostat.current_operation self.ecobee['settings']['hvacMode'] = 'auxHeatOnly' - self.assertEqual('heat', self.thermostat.current_operation) + assert 'heat' == self.thermostat.current_operation self.ecobee['settings']['hvacMode'] = 'off' - self.assertEqual('off', self.thermostat.current_operation) + assert 'off' == self.thermostat.current_operation def test_operation_list(self): """Test operation list property.""" - self.assertEqual(['auto', 'auxHeatOnly', 'cool', - 'heat', 'off'], self.thermostat.operation_list) + assert ['auto', 'auxHeatOnly', 'cool', + 'heat', 'off'] == self.thermostat.operation_list def test_operation_mode(self): """Test operation mode property.""" - self.assertEqual('auto', self.thermostat.operation_mode) + assert 'auto' == self.thermostat.operation_mode self.ecobee['settings']['hvacMode'] = 'heat' - self.assertEqual('heat', self.thermostat.operation_mode) + assert 'heat' == self.thermostat.operation_mode def test_mode(self): """Test mode property.""" - self.assertEqual('Climate1', self.thermostat.mode) + assert 'Climate1' == self.thermostat.mode self.ecobee['program']['currentClimateRef'] = 'c2' - self.assertEqual('Climate2', self.thermostat.mode) + assert 'Climate2' == self.thermostat.mode def test_fan_min_on_time(self): """Test fan min on time property.""" - self.assertEqual(10, self.thermostat.fan_min_on_time) + assert 10 == self.thermostat.fan_min_on_time self.ecobee['settings']['fanMinOnTime'] = 100 - self.assertEqual(100, self.thermostat.fan_min_on_time) + assert 100 == self.thermostat.fan_min_on_time def test_device_state_attributes(self): """Test device state attributes property.""" self.ecobee['equipmentStatus'] = 'heatPump2' - self.assertEqual({'actual_humidity': 15, - 'climate_list': ['Climate1', 'Climate2'], - 'fan': 'off', - 'fan_min_on_time': 10, - 'climate_mode': 'Climate1', - 'operation': 'heat'}, - self.thermostat.device_state_attributes) + assert {'actual_humidity': 15, + 'climate_list': ['Climate1', 'Climate2'], + 'fan': 'off', + 'fan_min_on_time': 10, + 'climate_mode': 'Climate1', + 'operation': 'heat'} == \ + self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'auxHeat2' - self.assertEqual({'actual_humidity': 15, - 'climate_list': ['Climate1', 'Climate2'], - 'fan': 'off', - 'fan_min_on_time': 10, - 'climate_mode': 'Climate1', - 'operation': 'heat'}, - self.thermostat.device_state_attributes) + assert {'actual_humidity': 15, + 'climate_list': ['Climate1', 'Climate2'], + 'fan': 'off', + 'fan_min_on_time': 10, + 'climate_mode': 'Climate1', + 'operation': 'heat'} == \ + self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'compCool1' - self.assertEqual({'actual_humidity': 15, - 'climate_list': ['Climate1', 'Climate2'], - 'fan': 'off', - 'fan_min_on_time': 10, - 'climate_mode': 'Climate1', - 'operation': 'cool'}, - self.thermostat.device_state_attributes) + assert {'actual_humidity': 15, + 'climate_list': ['Climate1', 'Climate2'], + 'fan': 'off', + 'fan_min_on_time': 10, + 'climate_mode': 'Climate1', + 'operation': 'cool'} == \ + self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = '' - self.assertEqual({'actual_humidity': 15, - 'climate_list': ['Climate1', 'Climate2'], - 'fan': 'off', - 'fan_min_on_time': 10, - 'climate_mode': 'Climate1', - 'operation': 'idle'}, - self.thermostat.device_state_attributes) + assert {'actual_humidity': 15, + 'climate_list': ['Climate1', 'Climate2'], + 'fan': 'off', + 'fan_min_on_time': 10, + 'climate_mode': 'Climate1', + 'operation': 'idle'} == \ + self.thermostat.device_state_attributes self.ecobee['equipmentStatus'] = 'Unknown' - self.assertEqual({'actual_humidity': 15, - 'climate_list': ['Climate1', 'Climate2'], - 'fan': 'off', - 'fan_min_on_time': 10, - 'climate_mode': 'Climate1', - 'operation': 'Unknown'}, - self.thermostat.device_state_attributes) + assert {'actual_humidity': 15, + 'climate_list': ['Climate1', 'Climate2'], + 'fan': 'off', + 'fan_min_on_time': 10, + 'climate_mode': 'Climate1', + 'operation': 'Unknown'} == \ + self.thermostat.device_state_attributes def test_is_away_mode_on(self): """Test away mode property.""" - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # Temporary away hold self.ecobee['events'][0]['endDate'] = '2018-01-01 11:12:12' - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # Permanent away hold self.ecobee['events'][0]['endDate'] = '2019-01-01 13:12:12' - self.assertTrue(self.thermostat.is_away_mode_on) + assert self.thermostat.is_away_mode_on # No running events self.ecobee['events'][0]['running'] = False - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # Vacation Hold self.ecobee['events'][0]['type'] = 'vacation' - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # Preset climate hold self.ecobee['events'][0]['type'] = 'hold' self.ecobee['events'][0]['holdClimateRef'] = 'heatClimate' - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # Temperature hold self.ecobee['events'][0]['type'] = 'hold' self.ecobee['events'][0]['holdClimateRef'] = '' - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on # auto Hold self.ecobee['events'][0]['type'] = 'autoHeat' - self.assertFalse(self.thermostat.is_away_mode_on) + assert not self.thermostat.is_away_mode_on def test_is_aux_heat_on(self): """Test aux heat property.""" - self.assertFalse(self.thermostat.is_aux_heat_on) + assert not self.thermostat.is_aux_heat_on self.ecobee['equipmentStatus'] = 'fan, auxHeat' - self.assertTrue(self.thermostat.is_aux_heat_on) + assert self.thermostat.is_aux_heat_on def test_turn_away_mode_on_off(self): """Test turn away mode setter.""" @@ -265,7 +265,7 @@ class TestEcobee(unittest.TestCase): self.data.reset_mock() self.ecobee['events'][0]['endDate'] = '2019-01-01 11:12:12' # Should not call set_climate_hold() - self.assertFalse(self.data.ecobee.set_climate_hold.called) + assert not self.data.ecobee.set_climate_hold.called # Try turning off while hold mode is away hold self.data.reset_mock() @@ -276,7 +276,7 @@ class TestEcobee(unittest.TestCase): self.data.reset_mock() self.ecobee['events'][0]['endDate'] = '2017-01-01 14:00:00' self.thermostat.turn_away_mode_off() - self.assertFalse(self.data.ecobee.resume_program.called) + assert not self.data.ecobee.resume_program.called def test_set_hold_mode(self): """Test hold mode setter.""" @@ -284,18 +284,18 @@ class TestEcobee(unittest.TestCase): # Away->Away self.data.reset_mock() self.thermostat.set_hold_mode('away') - self.assertFalse(self.data.ecobee.delete_vacation.called) - self.assertFalse(self.data.ecobee.resume_program.called) - self.assertFalse(self.data.ecobee.set_hold_temp.called) - self.assertFalse(self.data.ecobee.set_climate_hold.called) + assert not self.data.ecobee.delete_vacation.called + assert not self.data.ecobee.resume_program.called + assert not self.data.ecobee.set_hold_temp.called + assert not self.data.ecobee.set_climate_hold.called # Away->'None' self.data.reset_mock() self.thermostat.set_hold_mode('None') - self.assertFalse(self.data.ecobee.delete_vacation.called) + assert not self.data.ecobee.delete_vacation.called self.data.ecobee.resume_program.assert_has_calls([mock.call(1)]) - self.assertFalse(self.data.ecobee.set_hold_temp.called) - self.assertFalse(self.data.ecobee.set_climate_hold.called) + assert not self.data.ecobee.set_hold_temp.called + assert not self.data.ecobee.set_climate_hold.called # Vacation Hold -> None self.ecobee['events'][0]['type'] = 'vacation' @@ -303,28 +303,28 @@ class TestEcobee(unittest.TestCase): self.thermostat.set_hold_mode(None) self.data.ecobee.delete_vacation.assert_has_calls( [mock.call(1, 'Event1')]) - self.assertFalse(self.data.ecobee.resume_program.called) - self.assertFalse(self.data.ecobee.set_hold_temp.called) - self.assertFalse(self.data.ecobee.set_climate_hold.called) + assert not self.data.ecobee.resume_program.called + assert not self.data.ecobee.set_hold_temp.called + assert not self.data.ecobee.set_climate_hold.called # Away -> home, sleep for hold in ['home', 'sleep']: self.data.reset_mock() self.thermostat.set_hold_mode(hold) - self.assertFalse(self.data.ecobee.delete_vacation.called) - self.assertFalse(self.data.ecobee.resume_program.called) - self.assertFalse(self.data.ecobee.set_hold_temp.called) + assert not self.data.ecobee.delete_vacation.called + assert not self.data.ecobee.resume_program.called + assert not self.data.ecobee.set_hold_temp.called self.data.ecobee.set_climate_hold.assert_has_calls( [mock.call(1, hold, 'nextTransition')]) # Away -> temp self.data.reset_mock() self.thermostat.set_hold_mode('temp') - self.assertFalse(self.data.ecobee.delete_vacation.called) - self.assertFalse(self.data.ecobee.resume_program.called) + assert not self.data.ecobee.delete_vacation.called + assert not self.data.ecobee.resume_program.called self.data.ecobee.set_hold_temp.assert_has_calls( [mock.call(1, 35.0, 25.0, 'nextTransition')]) - self.assertFalse(self.data.ecobee.set_climate_hold.called) + assert not self.data.ecobee.set_climate_hold.called def test_set_auto_temp_hold(self): """Test auto temp hold setter.""" @@ -389,7 +389,7 @@ class TestEcobee(unittest.TestCase): self.ecobee['settings']['hvacMode'] = 'heat' self.thermostat.set_temperature(target_temp_low=20, target_temp_high=30) - self.assertFalse(self.data.ecobee.set_hold_temp.called) + assert not self.data.ecobee.set_hold_temp.called def test_set_operation_mode(self): """Test operation mode setter.""" @@ -441,17 +441,17 @@ class TestEcobee(unittest.TestCase): def test_hold_preference(self): """Test hold preference.""" - self.assertEqual('nextTransition', self.thermostat.hold_preference()) + assert 'nextTransition' == self.thermostat.hold_preference() for action in ['useEndTime4hour', 'useEndTime2hour', 'nextPeriod', 'indefinite', 'askMe']: self.ecobee['settings']['holdAction'] = action - self.assertEqual('nextTransition', - self.thermostat.hold_preference()) + assert 'nextTransition' == \ + self.thermostat.hold_preference() def test_climate_list(self): """Test climate list property.""" - self.assertEqual(['Climate1', 'Climate2'], - self.thermostat.climate_list) + assert ['Climate1', 'Climate2'] == \ + self.thermostat.climate_list def test_set_fan_mode_on(self): """Test set fan mode to on.""" diff --git a/tests/components/climate/test_fritzbox.py b/tests/components/climate/test_fritzbox.py index ccffef9e547..1cd15e3655f 100644 --- a/tests/components/climate/test_fritzbox.py +++ b/tests/components/climate/test_fritzbox.py @@ -30,46 +30,46 @@ class TestFritzboxClimate(unittest.TestCase): def test_init(self): """Test instance creation.""" - self.assertEqual(18.0, self.thermostat._current_temperature) - self.assertEqual(19.5, self.thermostat._target_temperature) - self.assertEqual(22.0, self.thermostat._comfort_temperature) - self.assertEqual(16.0, self.thermostat._eco_temperature) + assert 18.0 == self.thermostat._current_temperature + assert 19.5 == self.thermostat._target_temperature + assert 22.0 == self.thermostat._comfort_temperature + assert 16.0 == self.thermostat._eco_temperature def test_supported_features(self): """Test supported features property.""" - self.assertEqual(129, self.thermostat.supported_features) + assert 129 == self.thermostat.supported_features def test_available(self): """Test available property.""" - self.assertTrue(self.thermostat.available) + assert self.thermostat.available self.thermostat._device.present = False - self.assertFalse(self.thermostat.available) + assert not self.thermostat.available def test_name(self): """Test name property.""" - self.assertEqual('Test Thermostat', self.thermostat.name) + assert 'Test Thermostat' == self.thermostat.name def test_temperature_unit(self): """Test temperature_unit property.""" - self.assertEqual('°C', self.thermostat.temperature_unit) + assert '°C' == self.thermostat.temperature_unit def test_precision(self): """Test precision property.""" - self.assertEqual(0.5, self.thermostat.precision) + assert 0.5 == self.thermostat.precision def test_current_temperature(self): """Test current_temperature property incl. special temperatures.""" - self.assertEqual(18, self.thermostat.current_temperature) + assert 18 == self.thermostat.current_temperature def test_target_temperature(self): """Test target_temperature property.""" - self.assertEqual(19.5, self.thermostat.target_temperature) + assert 19.5 == self.thermostat.target_temperature self.thermostat._target_temperature = 126.5 - self.assertEqual(None, self.thermostat.target_temperature) + assert self.thermostat.target_temperature is None self.thermostat._target_temperature = 127.0 - self.assertEqual(None, self.thermostat.target_temperature) + assert self.thermostat.target_temperature is None @patch.object(FritzboxThermostat, 'set_operation_mode') def test_set_temperature_operation_mode(self, mock_set_op): @@ -101,20 +101,20 @@ class TestFritzboxClimate(unittest.TestCase): def test_current_operation(self): """Test operation mode property for different temperatures.""" self.thermostat._target_temperature = 127.0 - self.assertEqual('on', self.thermostat.current_operation) + assert 'on' == self.thermostat.current_operation self.thermostat._target_temperature = 126.5 - self.assertEqual('off', self.thermostat.current_operation) + assert 'off' == self.thermostat.current_operation self.thermostat._target_temperature = 22.0 - self.assertEqual('heat', self.thermostat.current_operation) + assert 'heat' == self.thermostat.current_operation self.thermostat._target_temperature = 16.0 - self.assertEqual('eco', self.thermostat.current_operation) + assert 'eco' == self.thermostat.current_operation self.thermostat._target_temperature = 12.5 - self.assertEqual('manual', self.thermostat.current_operation) + assert 'manual' == self.thermostat.current_operation def test_operation_list(self): """Test operation_list property.""" - self.assertEqual(['heat', 'eco', 'off', 'on'], - self.thermostat.operation_list) + assert ['heat', 'eco', 'off', 'on'] == \ + self.thermostat.operation_list @patch.object(FritzboxThermostat, 'set_temperature') def test_set_operation_mode(self, mock_set_temp): @@ -137,15 +137,15 @@ class TestFritzboxClimate(unittest.TestCase): def test_min_max_temperature(self): """Test min_temp and max_temp properties.""" - self.assertEqual(8.0, self.thermostat.min_temp) - self.assertEqual(28.0, self.thermostat.max_temp) + assert 8.0 == self.thermostat.min_temp + assert 28.0 == self.thermostat.max_temp def test_device_state_attributes(self): """Test device_state property.""" attr = self.thermostat.device_state_attributes - self.assertEqual(attr['device_locked'], True) - self.assertEqual(attr['locked'], False) - self.assertEqual(attr['battery_low'], True) + assert attr['device_locked'] is True + assert attr['locked'] is False + assert attr['battery_low'] is True def test_update(self): """Test update function.""" @@ -160,10 +160,10 @@ class TestFritzboxClimate(unittest.TestCase): self.thermostat.update() device.update.assert_called_once_with() - self.assertEqual(10.0, self.thermostat._current_temperature) - self.assertEqual(11.0, self.thermostat._target_temperature) - self.assertEqual(12.0, self.thermostat._comfort_temperature) - self.assertEqual(13.0, self.thermostat._eco_temperature) + assert 10.0 == self.thermostat._current_temperature + assert 11.0 == self.thermostat._target_temperature + assert 12.0 == self.thermostat._comfort_temperature + assert 13.0 == self.thermostat._eco_temperature def test_update_http_error(self): """Test exception handling of update function.""" diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 8bbcbc8f840..71654afd5b8 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -62,15 +62,13 @@ class TestSetupClimateGenericThermostat(unittest.TestCase): def test_valid_conf(self): """Test set up generic_thermostat with valid config values.""" - self.assertTrue( - setup_component(self.hass, 'climate', - {'climate': { - 'platform': 'generic_thermostat', - 'name': 'test', - 'heater': ENT_SWITCH, - 'target_sensor': ENT_SENSOR - }}) - ) + assert setup_component(self.hass, 'climate', { + 'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR + }}) class TestGenericThermostatHeaterSwitching(unittest.TestCase): @@ -83,9 +81,9 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - self.assertTrue(run_coroutine_threadsafe( + assert run_coroutine_threadsafe( comps.async_setup(self.hass, {}), self.hass.loop - ).result()) + ).result() def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" @@ -104,16 +102,16 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase): 'target_sensor': ENT_SENSOR }}) - self.assertEqual(STATE_OFF, - self.hass.states.get(heater_switch).state) + assert STATE_OFF == \ + self.hass.states.get(heater_switch).state self._setup_sensor(18) self.hass.block_till_done() common.set_temperature(self.hass, 23) self.hass.block_till_done() - self.assertEqual(STATE_ON, - self.hass.states.get(heater_switch).state) + assert STATE_ON == \ + self.hass.states.get(heater_switch).state def test_heater_switch(self): """Test heater switching test switch.""" @@ -131,16 +129,16 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase): 'target_sensor': ENT_SENSOR }}) - self.assertEqual(STATE_OFF, - self.hass.states.get(heater_switch).state) + assert STATE_OFF == \ + self.hass.states.get(heater_switch).state self._setup_sensor(18) self.hass.block_till_done() common.set_temperature(self.hass, 23) self.hass.block_till_done() - self.assertEqual(STATE_ON, - self.hass.states.get(heater_switch).state) + assert STATE_ON == \ + self.hass.states.get(heater_switch).state def _setup_sensor(self, temp): """Set up the test sensor.""" @@ -170,31 +168,31 @@ class TestClimateGenericThermostat(unittest.TestCase): def test_setup_defaults_to_unknown(self): """Test the setting of defaults to unknown.""" - self.assertEqual(STATE_IDLE, self.hass.states.get(ENTITY).state) + assert STATE_IDLE == self.hass.states.get(ENTITY).state def test_default_setup_params(self): """Test the setup with default parameters.""" state = self.hass.states.get(ENTITY) - self.assertEqual(7, state.attributes.get('min_temp')) - self.assertEqual(35, state.attributes.get('max_temp')) - self.assertEqual(7, state.attributes.get('temperature')) + assert 7 == state.attributes.get('min_temp') + assert 35 == state.attributes.get('max_temp') + assert 7 == state.attributes.get('temperature') def test_get_operation_modes(self): """Test that the operation list returns the correct modes.""" state = self.hass.states.get(ENTITY) modes = state.attributes.get('operation_list') - self.assertEqual([climate.STATE_HEAT, STATE_OFF], modes) + assert [climate.STATE_HEAT, STATE_OFF] == modes def test_set_target_temp(self): """Test the setting of the target temperature.""" common.set_temperature(self.hass, 30) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(30.0, state.attributes.get('temperature')) + assert 30.0 == state.attributes.get('temperature') common.set_temperature(self.hass, None) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(30.0, state.attributes.get('temperature')) + assert 30.0 == state.attributes.get('temperature') def test_set_away_mode(self): """Test the setting away mode.""" @@ -203,7 +201,7 @@ class TestClimateGenericThermostat(unittest.TestCase): common.set_away_mode(self.hass, True) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(16, state.attributes.get('temperature')) + assert 16 == state.attributes.get('temperature') def test_set_away_mode_and_restore_prev_temp(self): """Test the setting and removing away mode. @@ -215,11 +213,11 @@ class TestClimateGenericThermostat(unittest.TestCase): common.set_away_mode(self.hass, True) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(16, state.attributes.get('temperature')) + assert 16 == state.attributes.get('temperature') common.set_away_mode(self.hass, False) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(23, state.attributes.get('temperature')) + assert 23 == state.attributes.get('temperature') def test_set_away_mode_twice_and_restore_prev_temp(self): """Test the setting away mode twice in a row. @@ -233,11 +231,11 @@ class TestClimateGenericThermostat(unittest.TestCase): common.set_away_mode(self.hass, True) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(16, state.attributes.get('temperature')) + assert 16 == state.attributes.get('temperature') common.set_away_mode(self.hass, False) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(23, state.attributes.get('temperature')) + assert 23 == state.attributes.get('temperature') def test_sensor_bad_value(self): """Test sensor that have None as state.""" @@ -248,7 +246,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(temp, state.attributes.get('current_temperature')) + assert temp == state.attributes.get('current_temperature') def test_set_target_temp_heater_on(self): """Test if target temperature turn heater on.""" @@ -257,11 +255,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() common.set_temperature(self.hass, 30) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_set_target_temp_heater_off(self): """Test if target temperature turn heater off.""" @@ -270,11 +268,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() common.set_temperature(self.hass, 25) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_heater_on_within_tolerance(self): """Test if temperature change doesn't turn on within tolerance.""" @@ -283,7 +281,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(29) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_heater_on_outside_tolerance(self): """Test if temperature change turn heater on outside cold tolerance.""" @@ -292,11 +290,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(27) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_heater_off_within_tolerance(self): """Test if temperature change doesn't turn off within tolerance.""" @@ -305,7 +303,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(33) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_heater_off_outside_tolerance(self): """Test if temperature change turn heater off outside hot tolerance.""" @@ -314,11 +312,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(35) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_running_when_operating_mode_is_off(self): """Test that the switch turns off when enabled is set False.""" @@ -327,11 +325,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() common.set_operation_mode(self.hass, STATE_OFF) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_no_state_change_when_operation_mode_off(self): """Test that the switch doesn't turn on when enabled is False.""" @@ -342,14 +340,14 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) @mock.patch('logging.Logger.error') def test_invalid_operating_mode(self, log_mock): """Test error handling for invalid operation mode.""" common.set_operation_mode(self.hass, 'invalid mode') self.hass.block_till_done() - self.assertEqual(log_mock.call_count, 1) + assert log_mock.call_count == 1 def test_operating_mode_heat(self): """Test change mode from OFF to HEAT. @@ -363,11 +361,11 @@ class TestClimateGenericThermostat(unittest.TestCase): self._setup_switch(False) common.set_operation_mode(self.hass, climate.STATE_HEAT) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def _setup_sensor(self, temp): """Set up the test sensor.""" @@ -416,11 +414,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() common.set_temperature(self.hass, 30) self.hass.block_till_done() - self.assertEqual(2, len(self.calls)) + assert 2 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_turn_away_mode_on_cooling(self): """Test the setting away mode when cooling.""" @@ -431,7 +429,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): common.set_away_mode(self.hass, True) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(30, state.attributes.get('temperature')) + assert 30 == state.attributes.get('temperature') def test_operating_mode_cool(self): """Test change mode from OFF to COOL. @@ -445,11 +443,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self._setup_switch(False) common.set_operation_mode(self.hass, climate.STATE_COOL) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_set_target_temp_ac_on(self): """Test if target temperature turn ac on.""" @@ -458,11 +456,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() common.set_temperature(self.hass, 25) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_ac_off_within_tolerance(self): """Test if temperature change doesn't turn ac off within tolerance.""" @@ -471,7 +469,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(29.8) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_set_temp_change_ac_off_outside_tolerance(self): """Test if temperature change turn ac off.""" @@ -480,11 +478,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(27) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_ac_on_within_tolerance(self): """Test if temperature change doesn't turn ac on within tolerance.""" @@ -493,7 +491,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25.2) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_ac_on_outside_tolerance(self): """Test if temperature change turn ac on.""" @@ -502,11 +500,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(30) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_running_when_operating_mode_is_off(self): """Test that the switch turns off when enabled is set False.""" @@ -515,11 +513,11 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() common.set_operation_mode(self.hass, STATE_OFF) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_no_state_change_when_operation_mode_off(self): """Test that the switch doesn't turn on when enabled is False.""" @@ -530,7 +528,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(35) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def _setup_sensor(self, temp): """Set up the test sensor.""" @@ -579,7 +577,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(30) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_ac_trigger_on_long_enough(self): """Test if temperature change turn ac on.""" @@ -592,11 +590,11 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(30) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_ac_trigger_off_not_long_enough(self): """Test if temperature change turn ac on.""" @@ -605,7 +603,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_ac_trigger_off_long_enough(self): """Test if temperature change turn ac on.""" @@ -618,11 +616,11 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def _setup_sensor(self, temp): """Set up the test sensor.""" @@ -670,7 +668,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(30) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_heater_trigger_on_not_long_enough(self): """Test if temp change doesn't turn heater on because of time.""" @@ -679,7 +677,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_temp_change_heater_trigger_on_long_enough(self): """Test if temperature change turn heater on after min cycle.""" @@ -692,11 +690,11 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(25) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_heater_trigger_off_long_enough(self): """Test if temperature change turn heater off after min cycle.""" @@ -709,11 +707,11 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() self._setup_sensor(30) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def _setup_sensor(self, temp): """Set up the test sensor.""" @@ -767,17 +765,17 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): test_time = datetime.datetime.now(pytz.UTC) self._send_time_changed(test_time) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=5)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_ac_trigger_off_long_enough(self): """Test if turn on signal is sent at keep-alive intervals.""" @@ -790,17 +788,17 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): test_time = datetime.datetime.now(pytz.UTC) self._send_time_changed(test_time) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=5)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def _send_time_changed(self, now): """Send a time changed event.""" @@ -857,17 +855,17 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): test_time = datetime.datetime.now(pytz.UTC) self._send_time_changed(test_time) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=5)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] def test_temp_change_heater_trigger_off_long_enough(self): """Test if turn on signal is sent at keep-alive intervals.""" @@ -880,17 +878,17 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): test_time = datetime.datetime.now(pytz.UTC) self._send_time_changed(test_time) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=5)) self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) self._send_time_changed(test_time + datetime.timedelta(minutes=10)) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] def _send_time_changed(self, now): """Send a time changed event.""" @@ -951,10 +949,10 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): self.hass.block_till_done() state_heat = self.hass.states.get(self.HEAT_ENTITY) state_cool = self.hass.states.get(self.COOL_ENTITY) - self.assertEqual(STATE_HEAT, - state_heat.attributes.get('operation_mode')) - self.assertEqual(STATE_COOL, - state_cool.attributes.get('operation_mode')) + assert STATE_HEAT == \ + state_heat.attributes.get('operation_mode') + assert STATE_COOL == \ + state_cool.attributes.get('operation_mode') def test_turn_on_when_on(self): """Test if climate.turn_on does nothing to a turned on device.""" @@ -965,10 +963,10 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): self.hass.block_till_done() state_heat = self.hass.states.get(self.HEAT_ENTITY) state_cool = self.hass.states.get(self.COOL_ENTITY) - self.assertEqual(STATE_HEAT, - state_heat.attributes.get('operation_mode')) - self.assertEqual(STATE_COOL, - state_cool.attributes.get('operation_mode')) + assert STATE_HEAT == \ + state_heat.attributes.get('operation_mode') + assert STATE_COOL == \ + state_cool.attributes.get('operation_mode') def test_turn_off_when_on(self): """Test if climate.turn_off turns off a turned on device.""" @@ -979,10 +977,10 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): self.hass.block_till_done() state_heat = self.hass.states.get(self.HEAT_ENTITY) state_cool = self.hass.states.get(self.COOL_ENTITY) - self.assertEqual(STATE_OFF, - state_heat.attributes.get('operation_mode')) - self.assertEqual(STATE_OFF, - state_cool.attributes.get('operation_mode')) + assert STATE_OFF == \ + state_heat.attributes.get('operation_mode') + assert STATE_OFF == \ + state_cool.attributes.get('operation_mode') def test_turn_off_when_off(self): """Test if climate.turn_off does nothing to a turned off device.""" @@ -992,10 +990,10 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): self.hass.block_till_done() state_heat = self.hass.states.get(self.HEAT_ENTITY) state_cool = self.hass.states.get(self.COOL_ENTITY) - self.assertEqual(STATE_OFF, - state_heat.attributes.get('operation_mode')) - self.assertEqual(STATE_OFF, - state_cool.attributes.get('operation_mode')) + assert STATE_OFF == \ + state_heat.attributes.get('operation_mode') + assert STATE_OFF == \ + state_cool.attributes.get('operation_mode') @asyncio.coroutine @@ -1096,18 +1094,18 @@ class TestClimateGenericThermostatRestoreState(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(20, state.attributes[ATTR_TEMPERATURE]) - self.assertEqual(STATE_OFF, - state.attributes[climate.ATTR_OPERATION_MODE]) - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(0, len(self.calls)) + assert 20 == state.attributes[ATTR_TEMPERATURE] + assert STATE_OFF == \ + state.attributes[climate.ATTR_OPERATION_MODE] + assert STATE_OFF == state.state + assert 0 == len(self.calls) self._setup_switch(False) self.hass.block_till_done() state = self.hass.states.get(ENTITY) - self.assertEqual(STATE_OFF, - state.attributes[climate.ATTR_OPERATION_MODE]) - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == \ + state.attributes[climate.ATTR_OPERATION_MODE] + assert STATE_OFF == state.state def _setup_climate(self): assert setup_component(self.hass, climate.DOMAIN, {'climate': { diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py index 7072090591b..7daed2ff4a9 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/climate/test_honeywell.py @@ -12,6 +12,7 @@ from homeassistant.components.climate import ( ATTR_FAN_MODE, ATTR_OPERATION_MODE, ATTR_FAN_LIST, ATTR_OPERATION_LIST) import homeassistant.components.climate.honeywell as honeywell +import pytest class TestHoneywell(unittest.TestCase): @@ -43,16 +44,16 @@ class TestHoneywell(unittest.TestCase): honeywell.CONF_REGION: 'un', } - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): honeywell.PLATFORM_SCHEMA(None) - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): honeywell.PLATFORM_SCHEMA({}) - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): honeywell.PLATFORM_SCHEMA(bad_pass_config) - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): honeywell.PLATFORM_SCHEMA(bad_region_config) hass = mock.MagicMock() @@ -70,9 +71,9 @@ class TestHoneywell(unittest.TestCase): locations[1].devices_by_id.values.return_value = devices_2 result = honeywell.setup_platform(hass, config, add_entities) - self.assertTrue(result) - self.assertEqual(mock_sc.call_count, 1) - self.assertEqual(mock_sc.call_args, mock.call('user', 'pass')) + assert result + assert mock_sc.call_count == 1 + assert mock_sc.call_args == mock.call('user', 'pass') mock_ht.assert_has_calls([ mock.call(mock_sc.return_value, devices_1[0], 18, 28, 'user', 'pass'), @@ -95,13 +96,13 @@ class TestHoneywell(unittest.TestCase): mock_sc.side_effect = somecomfort.AuthError result = honeywell.setup_platform(hass, config, add_entities) - self.assertFalse(result) - self.assertFalse(add_entities.called) + assert not result + assert not add_entities.called mock_sc.side_effect = somecomfort.SomeComfortError result = honeywell.setup_platform(hass, config, add_entities) - self.assertFalse(result) - self.assertFalse(add_entities.called) + assert not result + assert not add_entities.called @mock.patch('somecomfort.SomeComfort') @mock.patch('homeassistant.components.climate.' @@ -137,8 +138,7 @@ class TestHoneywell(unittest.TestCase): mock_sc.return_value = mock.MagicMock(locations_by_id=locations) hass = mock.MagicMock() add_entities = mock.MagicMock() - self.assertEqual(True, - honeywell.setup_platform(hass, config, add_entities)) + assert honeywell.setup_platform(hass, config, add_entities) is True return mock_ht.call_args_list, mock_sc @@ -147,29 +147,28 @@ class TestHoneywell(unittest.TestCase): result, client = self._test_us_filtered_devices( dev=mock.sentinel.loc1dev1) devices = [x[0][1].deviceid for x in result] - self.assertEqual([mock.sentinel.loc1dev1], devices) + assert [mock.sentinel.loc1dev1] == devices def test_us_filtered_thermostat_2(self): """Test for US filtered location.""" result, client = self._test_us_filtered_devices( dev=mock.sentinel.loc2dev1) devices = [x[0][1].deviceid for x in result] - self.assertEqual([mock.sentinel.loc2dev1], devices) + assert [mock.sentinel.loc2dev1] == devices def test_us_filtered_location_1(self): """Test for US filtered locations.""" result, client = self._test_us_filtered_devices( loc=mock.sentinel.loc1) devices = [x[0][1].deviceid for x in result] - self.assertEqual([mock.sentinel.loc1dev1, - mock.sentinel.loc1dev2], devices) + assert [mock.sentinel.loc1dev1, mock.sentinel.loc1dev2] == devices def test_us_filtered_location_2(self): """Test for US filtered locations.""" result, client = self._test_us_filtered_devices( loc=mock.sentinel.loc2) devices = [x[0][1].deviceid for x in result] - self.assertEqual([mock.sentinel.loc2dev1], devices) + assert [mock.sentinel.loc2dev1] == devices @mock.patch('evohomeclient.EvohomeClient') @mock.patch('homeassistant.components.climate.honeywell.' @@ -186,19 +185,17 @@ class TestHoneywell(unittest.TestCase): {'id': 'foo'}, {'id': 'bar'}] hass = mock.MagicMock() add_entities = mock.MagicMock() - self.assertTrue(honeywell.setup_platform(hass, config, add_entities)) - self.assertEqual(mock_evo.call_count, 1) - self.assertEqual(mock_evo.call_args, mock.call('user', 'pass')) - self.assertEqual(mock_evo.return_value.temperatures.call_count, 1) - self.assertEqual( - mock_evo.return_value.temperatures.call_args, + assert honeywell.setup_platform(hass, config, add_entities) + assert mock_evo.call_count == 1 + assert mock_evo.call_args == mock.call('user', 'pass') + assert mock_evo.return_value.temperatures.call_count == 1 + assert mock_evo.return_value.temperatures.call_args == \ mock.call(force_refresh=True) - ) mock_round.assert_has_calls([ mock.call(mock_evo.return_value, 'foo', True, 20.0), mock.call(mock_evo.return_value, 'bar', False, 20.0), ]) - self.assertEqual(2, add_entities.call_count) + assert 2 == add_entities.call_count @mock.patch('evohomeclient.EvohomeClient') @mock.patch('homeassistant.components.climate.honeywell.' @@ -218,7 +215,7 @@ class TestHoneywell(unittest.TestCase): hass = mock.MagicMock() add_entities = mock.MagicMock() - self.assertTrue(honeywell.setup_platform(hass, config, add_entities)) + assert honeywell.setup_platform(hass, config, add_entities) mock_round.assert_has_calls([ mock.call(mock_evo.return_value, 'foo', True, 16), mock.call(mock_evo.return_value, 'bar', False, 16), @@ -236,7 +233,7 @@ class TestHoneywell(unittest.TestCase): honeywell.CONF_REGION: 'eu', } - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): honeywell.PLATFORM_SCHEMA(config) @mock.patch('evohomeclient.EvohomeClient') @@ -253,7 +250,7 @@ class TestHoneywell(unittest.TestCase): mock_evo.return_value.temperatures.side_effect = socket.error add_entities = mock.MagicMock() hass = mock.MagicMock() - self.assertFalse(honeywell.setup_platform(hass, config, add_entities)) + assert not honeywell.setup_platform(hass, config, add_entities) class TestHoneywellRound(unittest.TestCase): @@ -282,53 +279,47 @@ class TestHoneywellRound(unittest.TestCase): def test_attributes(self): """Test the attributes.""" - self.assertEqual('House', self.round1.name) - self.assertEqual(TEMP_CELSIUS, self.round1.temperature_unit) - self.assertEqual(20, self.round1.current_temperature) - self.assertEqual(21, self.round1.target_temperature) - self.assertFalse(self.round1.is_away_mode_on) + assert 'House' == self.round1.name + assert TEMP_CELSIUS == self.round1.temperature_unit + assert 20 == self.round1.current_temperature + assert 21 == self.round1.target_temperature + assert not self.round1.is_away_mode_on - self.assertEqual('Hot Water', self.round2.name) - self.assertEqual(TEMP_CELSIUS, self.round2.temperature_unit) - self.assertEqual(21, self.round2.current_temperature) - self.assertEqual(None, self.round2.target_temperature) - self.assertFalse(self.round2.is_away_mode_on) + assert 'Hot Water' == self.round2.name + assert TEMP_CELSIUS == self.round2.temperature_unit + assert 21 == self.round2.current_temperature + assert self.round2.target_temperature is None + assert not self.round2.is_away_mode_on def test_away_mode(self): """Test setting the away mode.""" - self.assertFalse(self.round1.is_away_mode_on) + assert not self.round1.is_away_mode_on self.round1.turn_away_mode_on() - self.assertTrue(self.round1.is_away_mode_on) - self.assertEqual(self.device.set_temperature.call_count, 1) - self.assertEqual( - self.device.set_temperature.call_args, mock.call('House', 16) - ) + assert self.round1.is_away_mode_on + assert self.device.set_temperature.call_count == 1 + assert self.device.set_temperature.call_args == mock.call('House', 16) self.device.set_temperature.reset_mock() self.round1.turn_away_mode_off() - self.assertFalse(self.round1.is_away_mode_on) - self.assertEqual(self.device.cancel_temp_override.call_count, 1) - self.assertEqual( - self.device.cancel_temp_override.call_args, mock.call('House') - ) + assert not self.round1.is_away_mode_on + assert self.device.cancel_temp_override.call_count == 1 + assert self.device.cancel_temp_override.call_args == mock.call('House') def test_set_temperature(self): """Test setting the temperature.""" self.round1.set_temperature(temperature=25) - self.assertEqual(self.device.set_temperature.call_count, 1) - self.assertEqual( - self.device.set_temperature.call_args, mock.call('House', 25) - ) + assert self.device.set_temperature.call_count == 1 + assert self.device.set_temperature.call_args == mock.call('House', 25) def test_set_operation_mode(self) -> None: """Test setting the system operation.""" self.round1.set_operation_mode('cool') - self.assertEqual('cool', self.round1.current_operation) - self.assertEqual('cool', self.device.system_mode) + assert 'cool' == self.round1.current_operation + assert 'cool' == self.device.system_mode self.round1.set_operation_mode('heat') - self.assertEqual('heat', self.round1.current_operation) - self.assertEqual('heat', self.device.system_mode) + assert 'heat' == self.round1.current_operation + assert 'heat' == self.device.system_mode class TestHoneywellUS(unittest.TestCase): @@ -356,41 +347,41 @@ class TestHoneywellUS(unittest.TestCase): def test_properties(self): """Test the properties.""" - self.assertTrue(self.honeywell.is_fan_on) - self.assertEqual('test', self.honeywell.name) - self.assertEqual(72, self.honeywell.current_temperature) + assert self.honeywell.is_fan_on + assert 'test' == self.honeywell.name + assert 72 == self.honeywell.current_temperature def test_unit_of_measurement(self): """Test the unit of measurement.""" - self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.temperature_unit) + assert TEMP_FAHRENHEIT == self.honeywell.temperature_unit self.device.temperature_unit = 'C' - self.assertEqual(TEMP_CELSIUS, self.honeywell.temperature_unit) + assert TEMP_CELSIUS == self.honeywell.temperature_unit def test_target_temp(self): """Test the target temperature.""" - self.assertEqual(65, self.honeywell.target_temperature) + assert 65 == self.honeywell.target_temperature self.device.system_mode = 'cool' - self.assertEqual(78, self.honeywell.target_temperature) + assert 78 == self.honeywell.target_temperature def test_set_temp(self): """Test setting the temperature.""" self.honeywell.set_temperature(temperature=70) - self.assertEqual(70, self.device.setpoint_heat) - self.assertEqual(70, self.honeywell.target_temperature) + assert 70 == self.device.setpoint_heat + assert 70 == self.honeywell.target_temperature self.device.system_mode = 'cool' - self.assertEqual(78, self.honeywell.target_temperature) + assert 78 == self.honeywell.target_temperature self.honeywell.set_temperature(temperature=74) - self.assertEqual(74, self.device.setpoint_cool) - self.assertEqual(74, self.honeywell.target_temperature) + assert 74 == self.device.setpoint_cool + assert 74 == self.honeywell.target_temperature def test_set_operation_mode(self) -> None: """Test setting the operation mode.""" self.honeywell.set_operation_mode('cool') - self.assertEqual('cool', self.device.system_mode) + assert 'cool' == self.device.system_mode self.honeywell.set_operation_mode('heat') - self.assertEqual('heat', self.device.system_mode) + assert 'heat' == self.device.system_mode def test_set_temp_fail(self): """Test if setting the temperature fails.""" @@ -407,10 +398,10 @@ class TestHoneywellUS(unittest.TestCase): ATTR_FAN_LIST: somecomfort.FAN_MODES, ATTR_OPERATION_LIST: somecomfort.SYSTEM_MODES, } - self.assertEqual(expected, self.honeywell.device_state_attributes) + assert expected == self.honeywell.device_state_attributes expected['fan'] = 'idle' self.device.fan_running = False - self.assertEqual(expected, self.honeywell.device_state_attributes) + assert expected == self.honeywell.device_state_attributes def test_with_no_fan(self): """Test if there is on fan.""" @@ -423,24 +414,24 @@ class TestHoneywellUS(unittest.TestCase): ATTR_FAN_LIST: somecomfort.FAN_MODES, ATTR_OPERATION_LIST: somecomfort.SYSTEM_MODES, } - self.assertEqual(expected, self.honeywell.device_state_attributes) + assert expected == self.honeywell.device_state_attributes def test_heat_away_mode(self): """Test setting the heat away mode.""" self.honeywell.set_operation_mode('heat') - self.assertFalse(self.honeywell.is_away_mode_on) + assert not self.honeywell.is_away_mode_on self.honeywell.turn_away_mode_on() - self.assertTrue(self.honeywell.is_away_mode_on) - self.assertEqual(self.device.setpoint_heat, self.heat_away_temp) - self.assertEqual(self.device.hold_heat, True) + assert self.honeywell.is_away_mode_on + assert self.device.setpoint_heat == self.heat_away_temp + assert self.device.hold_heat is True self.honeywell.turn_away_mode_off() - self.assertFalse(self.honeywell.is_away_mode_on) - self.assertEqual(self.device.hold_heat, False) + assert not self.honeywell.is_away_mode_on + assert self.device.hold_heat is False @mock.patch('somecomfort.SomeComfort') def test_retry(self, test_somecomfort): """Test retry connection.""" old_device = self.honeywell._device self.honeywell._retry() - self.assertEqual(self.honeywell._device, old_device) + assert self.honeywell._device == old_device diff --git a/tests/components/climate/test_melissa.py b/tests/components/climate/test_melissa.py index 563f74383e5..538e642cd82 100644 --- a/tests/components/climate/test_melissa.py +++ b/tests/components/climate/test_melissa.py @@ -84,133 +84,129 @@ class TestMelissa(unittest.TestCase): def test_get_name(self): """Test name property.""" - self.assertEqual("Melissa 12345678", self.thermostat.name) + assert "Melissa 12345678" == self.thermostat.name def test_is_on(self): """Test name property.""" - self.assertTrue(self.thermostat.is_on) + assert self.thermostat.is_on self.thermostat._cur_settings = None - self.assertFalse(self.thermostat.is_on) + assert not self.thermostat.is_on def test_current_fan_mode(self): """Test current_fan_mode property.""" self.thermostat.update() - self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) + assert SPEED_LOW == self.thermostat.current_fan_mode self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.current_fan_mode) + assert self.thermostat.current_fan_mode is None def test_current_temperature(self): """Test current temperature.""" - self.assertEqual(27.4, self.thermostat.current_temperature) + assert 27.4 == self.thermostat.current_temperature def test_current_temperature_no_data(self): """Test current temperature without data.""" self.thermostat._data = None - self.assertIsNone(self.thermostat.current_temperature) + assert self.thermostat.current_temperature is None def test_target_temperature_step(self): """Test current target_temperature_step.""" - self.assertEqual(1, self.thermostat.target_temperature_step) + assert 1 == self.thermostat.target_temperature_step def test_current_operation(self): """Test current operation.""" self.thermostat.update() - self.assertEqual(self.thermostat.current_operation, STATE_HEAT) + assert self.thermostat.current_operation == STATE_HEAT self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.current_operation) + assert self.thermostat.current_operation is None def test_operation_list(self): """Test the operation list.""" - self.assertEqual( - [STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT], + assert [STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT] == \ self.thermostat.operation_list - ) def test_fan_list(self): """Test the fan list.""" - self.assertEqual( - [STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM], + assert [STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM] == \ self.thermostat.fan_list - ) def test_target_temperature(self): """Test target temperature.""" - self.assertEqual(16, self.thermostat.target_temperature) + assert 16 == self.thermostat.target_temperature self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.target_temperature) + assert self.thermostat.target_temperature is None def test_state(self): """Test state.""" - self.assertEqual(STATE_ON, self.thermostat.state) + assert STATE_ON == self.thermostat.state self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.state) + assert self.thermostat.state is None def test_temperature_unit(self): """Test temperature unit.""" - self.assertEqual(TEMP_CELSIUS, self.thermostat.temperature_unit) + assert TEMP_CELSIUS == self.thermostat.temperature_unit def test_min_temp(self): """Test min temp.""" - self.assertEqual(16, self.thermostat.min_temp) + assert 16 == self.thermostat.min_temp def test_max_temp(self): """Test max temp.""" - self.assertEqual(30, self.thermostat.max_temp) + assert 30 == self.thermostat.max_temp def test_supported_features(self): """Test supported_features property.""" features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF | SUPPORT_FAN_MODE) - self.assertEqual(features, self.thermostat.supported_features) + assert features == self.thermostat.supported_features def test_set_temperature(self): """Test set_temperature.""" self.api.send.return_value = True self.thermostat.update() self.thermostat.set_temperature(**{ATTR_TEMPERATURE: 25}) - self.assertEqual(25, self.thermostat.target_temperature) + assert 25 == self.thermostat.target_temperature def test_fan_mode(self): """Test set_fan_mode.""" self.api.send.return_value = True self.thermostat.set_fan_mode(SPEED_HIGH) - self.assertEqual(SPEED_HIGH, self.thermostat.current_fan_mode) + assert SPEED_HIGH == self.thermostat.current_fan_mode def test_set_operation_mode(self): """Test set_operation_mode.""" self.api.send.return_value = True self.thermostat.set_operation_mode(STATE_COOL) - self.assertEqual(STATE_COOL, self.thermostat.current_operation) + assert STATE_COOL == self.thermostat.current_operation def test_turn_on(self): """Test turn_on.""" self.thermostat.turn_on() - self.assertTrue(self.thermostat.state) + assert self.thermostat.state def test_turn_off(self): """Test turn_off.""" self.thermostat.turn_off() - self.assertEqual(STATE_OFF, self.thermostat.state) + assert STATE_OFF == self.thermostat.state def test_send(self): """Test send.""" self.thermostat.update() - self.assertTrue(self.thermostat.send( - {'fan': self.api.FAN_MEDIUM})) - self.assertEqual(SPEED_MEDIUM, self.thermostat.current_fan_mode) + assert self.thermostat.send( + {'fan': self.api.FAN_MEDIUM}) + assert SPEED_MEDIUM == self.thermostat.current_fan_mode self.api.send.return_value = False self.thermostat._cur_settings = None - self.assertFalse(self.thermostat.send({ - 'fan': self.api.FAN_LOW})) - self.assertNotEqual(SPEED_LOW, self.thermostat.current_fan_mode) - self.assertIsNone(self.thermostat._cur_settings) + assert not self.thermostat.send({ + 'fan': self.api.FAN_LOW}) + assert SPEED_LOW != self.thermostat.current_fan_mode + assert self.thermostat._cur_settings is None @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') def test_update(self, mocked_warning): """Test update.""" self.thermostat.update() - self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) - self.assertEqual(STATE_HEAT, self.thermostat.current_operation) + assert SPEED_LOW == self.thermostat.current_fan_mode + assert STATE_HEAT == self.thermostat.current_operation self.thermostat._api.status.side_effect = KeyError('boom') self.thermostat.update() mocked_warning.assert_called_once_with( @@ -218,37 +214,34 @@ class TestMelissa(unittest.TestCase): def test_melissa_state_to_hass(self): """Test for translate melissa states to hass.""" - self.assertEqual(STATE_OFF, self.thermostat.melissa_state_to_hass(0)) - self.assertEqual(STATE_ON, self.thermostat.melissa_state_to_hass(1)) - self.assertEqual(STATE_IDLE, self.thermostat.melissa_state_to_hass(2)) - self.assertEqual(None, - self.thermostat.melissa_state_to_hass(3)) + assert STATE_OFF == self.thermostat.melissa_state_to_hass(0) + assert STATE_ON == self.thermostat.melissa_state_to_hass(1) + assert STATE_IDLE == self.thermostat.melissa_state_to_hass(2) + assert self.thermostat.melissa_state_to_hass(3) is None def test_melissa_op_to_hass(self): """Test for translate melissa operations to hass.""" - self.assertEqual(STATE_FAN_ONLY, self.thermostat.melissa_op_to_hass(1)) - self.assertEqual(STATE_HEAT, self.thermostat.melissa_op_to_hass(2)) - self.assertEqual(STATE_COOL, self.thermostat.melissa_op_to_hass(3)) - self.assertEqual(STATE_DRY, self.thermostat.melissa_op_to_hass(4)) - self.assertEqual( - None, self.thermostat.melissa_op_to_hass(5)) + assert STATE_FAN_ONLY == self.thermostat.melissa_op_to_hass(1) + assert STATE_HEAT == self.thermostat.melissa_op_to_hass(2) + assert STATE_COOL == self.thermostat.melissa_op_to_hass(3) + assert STATE_DRY == self.thermostat.melissa_op_to_hass(4) + assert self.thermostat.melissa_op_to_hass(5) is None def test_melissa_fan_to_hass(self): """Test for translate melissa fan state to hass.""" - self.assertEqual(STATE_AUTO, self.thermostat.melissa_fan_to_hass(0)) - self.assertEqual(SPEED_LOW, self.thermostat.melissa_fan_to_hass(1)) - self.assertEqual(SPEED_MEDIUM, self.thermostat.melissa_fan_to_hass(2)) - self.assertEqual(SPEED_HIGH, self.thermostat.melissa_fan_to_hass(3)) - self.assertEqual(None, self.thermostat.melissa_fan_to_hass(4)) + assert STATE_AUTO == self.thermostat.melissa_fan_to_hass(0) + assert SPEED_LOW == self.thermostat.melissa_fan_to_hass(1) + assert SPEED_MEDIUM == self.thermostat.melissa_fan_to_hass(2) + assert SPEED_HIGH == self.thermostat.melissa_fan_to_hass(3) + assert self.thermostat.melissa_fan_to_hass(4) is None @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') def test_hass_mode_to_melissa(self, mocked_warning): """Test for hass operations to melssa.""" - self.assertEqual( - 1, self.thermostat.hass_mode_to_melissa(STATE_FAN_ONLY)) - self.assertEqual(2, self.thermostat.hass_mode_to_melissa(STATE_HEAT)) - self.assertEqual(3, self.thermostat.hass_mode_to_melissa(STATE_COOL)) - self.assertEqual(4, self.thermostat.hass_mode_to_melissa(STATE_DRY)) + assert 1 == self.thermostat.hass_mode_to_melissa(STATE_FAN_ONLY) + assert 2 == self.thermostat.hass_mode_to_melissa(STATE_HEAT) + assert 3 == self.thermostat.hass_mode_to_melissa(STATE_COOL) + assert 4 == self.thermostat.hass_mode_to_melissa(STATE_DRY) self.thermostat.hass_mode_to_melissa("test") mocked_warning.assert_called_once_with( "Melissa have no setting for %s mode", "test") @@ -256,10 +249,10 @@ class TestMelissa(unittest.TestCase): @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') def test_hass_fan_to_melissa(self, mocked_warning): """Test for translate melissa states to hass.""" - self.assertEqual(0, self.thermostat.hass_fan_to_melissa(STATE_AUTO)) - self.assertEqual(1, self.thermostat.hass_fan_to_melissa(SPEED_LOW)) - self.assertEqual(2, self.thermostat.hass_fan_to_melissa(SPEED_MEDIUM)) - self.assertEqual(3, self.thermostat.hass_fan_to_melissa(SPEED_HIGH)) + assert 0 == self.thermostat.hass_fan_to_melissa(STATE_AUTO) + assert 1 == self.thermostat.hass_fan_to_melissa(SPEED_LOW) + assert 2 == self.thermostat.hass_fan_to_melissa(SPEED_MEDIUM) + assert 3 == self.thermostat.hass_fan_to_melissa(SPEED_HIGH) self.thermostat.hass_fan_to_melissa("test") mocked_warning.assert_called_once_with( "Melissa have no setting for %s fan mode", "test") diff --git a/tests/components/climate/test_mqtt.py b/tests/components/climate/test_mqtt.py index 16fe0a6639d..61b481ed4db 100644 --- a/tests/components/climate/test_mqtt.py +++ b/tests/components/climate/test_mqtt.py @@ -52,12 +52,12 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(21, state.attributes.get('temperature')) - self.assertEqual("low", state.attributes.get('fan_mode')) - self.assertEqual("off", state.attributes.get('swing_mode')) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual(DEFAULT_MIN_TEMP, state.attributes.get('min_temp')) - self.assertEqual(DEFAULT_MAX_TEMP, state.attributes.get('max_temp')) + assert 21 == state.attributes.get('temperature') + assert "low" == state.attributes.get('fan_mode') + assert "off" == state.attributes.get('swing_mode') + assert "off" == state.attributes.get('operation_mode') + assert DEFAULT_MIN_TEMP == state.attributes.get('min_temp') + assert DEFAULT_MAX_TEMP == state.attributes.get('max_temp') def test_supported_features(self): """Test the supported_features.""" @@ -68,7 +68,7 @@ class TestMQTTClimate(unittest.TestCase): SUPPORT_SWING_MODE | SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_AUX_HEAT) - self.assertEqual(state.attributes.get("supported_features"), support) + assert state.attributes.get("supported_features") == support def test_get_operation_modes(self): """Test that the operation list returns the correct modes.""" @@ -76,10 +76,10 @@ class TestMQTTClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) modes = state.attributes.get('operation_list') - self.assertEqual([ + assert [ climate.STATE_AUTO, STATE_OFF, climate.STATE_COOL, climate.STATE_HEAT, climate.STATE_DRY, climate.STATE_FAN_ONLY - ], modes) + ] == modes def test_set_operation_bad_attr_and_state(self): """Test setting operation mode without required attribute. @@ -89,26 +89,26 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual("off", state.state) + assert "off" == state.attributes.get('operation_mode') + assert "off" == state.state common.set_operation_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual("off", state.state) + assert "off" == state.attributes.get('operation_mode') + assert "off" == state.state def test_set_operation(self): """Test setting of new operation mode.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual("off", state.state) + assert "off" == state.attributes.get('operation_mode') + assert "off" == state.state common.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state self.mock_publish.async_publish.assert_called_once_with( 'mode-topic', 'cool', 0, False) @@ -119,26 +119,26 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('operation_mode')) - self.assertEqual("unknown", state.state) + assert state.attributes.get('operation_mode') is None + assert "unknown" == state.state common.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('operation_mode')) - self.assertEqual("unknown", state.state) + assert state.attributes.get('operation_mode') is None + assert "unknown" == state.state fire_mqtt_message(self.hass, 'mode-state', 'cool') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state fire_mqtt_message(self.hass, 'mode-state', 'bogus mode') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) - self.assertEqual("cool", state.state) + assert "cool" == state.attributes.get('operation_mode') + assert "cool" == state.state def test_set_operation_with_power_command(self): """Test setting of new operation mode with power command enabled.""" @@ -147,13 +147,13 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual("off", state.state) + assert "off" == state.attributes.get('operation_mode') + assert "off" == state.state common.set_operation_mode(self.hass, "on", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("on", state.attributes.get('operation_mode')) - self.assertEqual("on", state.state) + assert "on" == state.attributes.get('operation_mode') + assert "on" == state.state self.mock_publish.async_publish.assert_has_calls([ unittest.mock.call('power-command', 'ON', 0, False), unittest.mock.call('mode-topic', 'on', 0, False) @@ -163,8 +163,8 @@ class TestMQTTClimate(unittest.TestCase): common.set_operation_mode(self.hass, "off", ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('operation_mode')) - self.assertEqual("off", state.state) + assert "off" == state.attributes.get('operation_mode') + assert "off" == state.state self.mock_publish.async_publish.assert_has_calls([ unittest.mock.call('power-command', 'OFF', 0, False), unittest.mock.call('mode-topic', 'off', 0, False) @@ -176,11 +176,11 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("low", state.attributes.get('fan_mode')) + assert "low" == state.attributes.get('fan_mode') common.set_fan_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("low", state.attributes.get('fan_mode')) + assert "low" == state.attributes.get('fan_mode') def test_set_fan_mode_pessimistic(self): """Test setting of new fan mode in pessimistic mode.""" @@ -189,46 +189,46 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('fan_mode')) + assert state.attributes.get('fan_mode') is None common.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('fan_mode')) + assert state.attributes.get('fan_mode') is None fire_mqtt_message(self.hass, 'fan-state', 'high') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('high', state.attributes.get('fan_mode')) + assert 'high' == state.attributes.get('fan_mode') fire_mqtt_message(self.hass, 'fan-state', 'bogus mode') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('high', state.attributes.get('fan_mode')) + assert 'high' == state.attributes.get('fan_mode') def test_set_fan_mode(self): """Test setting of new fan mode.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("low", state.attributes.get('fan_mode')) + assert "low" == state.attributes.get('fan_mode') common.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'fan-mode-topic', 'high', 0, False) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('high', state.attributes.get('fan_mode')) + assert 'high' == state.attributes.get('fan_mode') def test_set_swing_mode_bad_attr(self): """Test setting swing mode without required attribute.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('swing_mode')) + assert "off" == state.attributes.get('swing_mode') common.set_swing_mode(self.hass, None, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('swing_mode')) + assert "off" == state.attributes.get('swing_mode') def test_set_swing_pessimistic(self): """Test setting swing mode in pessimistic mode.""" @@ -237,46 +237,46 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('swing_mode')) + assert state.attributes.get('swing_mode') is None common.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('swing_mode')) + assert state.attributes.get('swing_mode') is None fire_mqtt_message(self.hass, 'swing-state', 'on') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("on", state.attributes.get('swing_mode')) + assert "on" == state.attributes.get('swing_mode') fire_mqtt_message(self.hass, 'swing-state', 'bogus state') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("on", state.attributes.get('swing_mode')) + assert "on" == state.attributes.get('swing_mode') def test_set_swing(self): """Test setting of new swing mode.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("off", state.attributes.get('swing_mode')) + assert "off" == state.attributes.get('swing_mode') common.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'swing-mode-topic', 'on', 0, False) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("on", state.attributes.get('swing_mode')) + assert "on" == state.attributes.get('swing_mode') def test_set_target_temperature(self): """Test setting the target temperature.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(21, state.attributes.get('temperature')) + assert 21 == state.attributes.get('temperature') common.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('heat', state.attributes.get('operation_mode')) + assert 'heat' == state.attributes.get('operation_mode') self.mock_publish.async_publish.assert_called_once_with( 'mode-topic', 'heat', 0, False) self.mock_publish.async_publish.reset_mock() @@ -284,7 +284,7 @@ class TestMQTTClimate(unittest.TestCase): entity_id=ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(47, state.attributes.get('temperature')) + assert 47 == state.attributes.get('temperature') self.mock_publish.async_publish.assert_called_once_with( 'temperature-topic', 47, 0, False) @@ -295,8 +295,8 @@ class TestMQTTClimate(unittest.TestCase): entity_id=ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('cool', state.attributes.get('operation_mode')) - self.assertEqual(21, state.attributes.get('temperature')) + assert 'cool' == state.attributes.get('operation_mode') + assert 21 == state.attributes.get('temperature') self.mock_publish.async_publish.assert_has_calls([ unittest.mock.call('mode-topic', 'cool', 0, False), unittest.mock.call('temperature-topic', 21, 0, False) @@ -310,24 +310,24 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('temperature')) + assert state.attributes.get('temperature') is None common.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE) self.hass.block_till_done() common.set_temperature(self.hass, temperature=47, entity_id=ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('temperature')) + assert state.attributes.get('temperature') is None fire_mqtt_message(self.hass, 'temperature-state', '1701') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(1701, state.attributes.get('temperature')) + assert 1701 == state.attributes.get('temperature') fire_mqtt_message(self.hass, 'temperature-state', 'not a number') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(1701, state.attributes.get('temperature')) + assert 1701 == state.attributes.get('temperature') def test_receive_mqtt_temperature(self): """Test getting the current temperature via MQTT.""" @@ -339,7 +339,7 @@ class TestMQTTClimate(unittest.TestCase): fire_mqtt_message(self.hass, 'current_temperature', '47') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(47, state.attributes.get('current_temperature')) + assert 47 == state.attributes.get('current_temperature') def test_set_away_mode_pessimistic(self): """Test setting of the away mode.""" @@ -348,27 +348,27 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') common.set_away_mode(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') fire_mqtt_message(self.hass, 'away-state', 'ON') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') fire_mqtt_message(self.hass, 'away-state', 'OFF') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') fire_mqtt_message(self.hass, 'away-state', 'nonsense') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') def test_set_away_mode(self): """Test setting of the away mode.""" @@ -379,21 +379,21 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') common.set_away_mode(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'away-mode-topic', 'AN', 0, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') common.set_away_mode(self.hass, False, ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'away-mode-topic', 'AUS', 0, False) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') def test_set_hold_pessimistic(self): """Test setting the hold mode in pessimistic mode.""" @@ -402,43 +402,43 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('hold_mode')) + assert state.attributes.get('hold_mode') is None common.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('hold_mode')) + assert state.attributes.get('hold_mode') is None fire_mqtt_message(self.hass, 'hold-state', 'on') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('hold_mode')) + assert 'on' == state.attributes.get('hold_mode') fire_mqtt_message(self.hass, 'hold-state', 'off') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('hold_mode')) + assert 'off' == state.attributes.get('hold_mode') def test_set_hold(self): """Test setting the hold mode.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('hold_mode')) + assert state.attributes.get('hold_mode') is None common.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'hold-topic', 'on', 0, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('hold_mode')) + assert 'on' == state.attributes.get('hold_mode') common.set_hold_mode(self.hass, 'off', ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'hold-topic', 'off', 0, False) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('hold_mode')) + assert 'off' == state.attributes.get('hold_mode') def test_set_aux_pessimistic(self): """Test setting of the aux heating in pessimistic mode.""" @@ -447,48 +447,48 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') common.set_aux_heat(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') fire_mqtt_message(self.hass, 'aux-state', 'ON') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('aux_heat')) + assert 'on' == state.attributes.get('aux_heat') fire_mqtt_message(self.hass, 'aux-state', 'OFF') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') fire_mqtt_message(self.hass, 'aux-state', 'nonsense') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') def test_set_aux(self): """Test setting of the aux heating.""" assert setup_component(self.hass, climate.DOMAIN, DEFAULT_CONFIG) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') common.set_aux_heat(self.hass, True, ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'aux-topic', 'ON', 0, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('aux_heat')) + assert 'on' == state.attributes.get('aux_heat') common.set_aux_heat(self.hass, False, ENTITY_CLIMATE) self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( 'aux-topic', 'OFF', 0, False) state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" @@ -500,19 +500,19 @@ class TestMQTTClimate(unittest.TestCase): assert setup_component(self.hass, climate.DOMAIN, config) state = self.hass.states.get('climate.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('climate.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('climate.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_set_with_templates(self): """Test setting of new fan mode in pessimistic mode.""" @@ -539,32 +539,32 @@ class TestMQTTClimate(unittest.TestCase): # Operation Mode state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(None, state.attributes.get('operation_mode')) + assert state.attributes.get('operation_mode') is None fire_mqtt_message(self.hass, 'mode-state', '"cool"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("cool", state.attributes.get('operation_mode')) + assert "cool" == state.attributes.get('operation_mode') # Fan Mode - self.assertEqual(None, state.attributes.get('fan_mode')) + assert state.attributes.get('fan_mode') is None fire_mqtt_message(self.hass, 'fan-state', '"high"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('high', state.attributes.get('fan_mode')) + assert 'high' == state.attributes.get('fan_mode') # Swing Mode - self.assertEqual(None, state.attributes.get('swing_mode')) + assert state.attributes.get('swing_mode') is None fire_mqtt_message(self.hass, 'swing-state', '"on"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual("on", state.attributes.get('swing_mode')) + assert "on" == state.attributes.get('swing_mode') # Temperature - with valid value - self.assertEqual(None, state.attributes.get('temperature')) + assert state.attributes.get('temperature') is None fire_mqtt_message(self.hass, 'temperature-state', '"1031"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(1031, state.attributes.get('temperature')) + assert 1031 == state.attributes.get('temperature') # Temperature - with invalid value with self.assertLogs(level='ERROR') as log: @@ -572,60 +572,58 @@ class TestMQTTClimate(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) # make sure, the invalid value gets logged... - self.assertEqual(len(log.output), 1) - self.assertEqual(len(log.records), 1) - self.assertIn( - "Could not parse temperature from -INVALID-", + assert len(log.output) == 1 + assert len(log.records) == 1 + assert "Could not parse temperature from -INVALID-" in \ log.output[0] - ) # ... but the actual value stays unchanged. - self.assertEqual(1031, state.attributes.get('temperature')) + assert 1031 == state.attributes.get('temperature') # Away Mode - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') fire_mqtt_message(self.hass, 'away-state', '"ON"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') # Away Mode with JSON values fire_mqtt_message(self.hass, 'away-state', 'false') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') fire_mqtt_message(self.hass, 'away-state', 'true') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') # Hold Mode - self.assertEqual(None, state.attributes.get('hold_mode')) + assert state.attributes.get('hold_mode') is None fire_mqtt_message(self.hass, 'hold-state', """ { "attribute": "somemode" } """) self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('somemode', state.attributes.get('hold_mode')) + assert 'somemode' == state.attributes.get('hold_mode') # Aux mode - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') fire_mqtt_message(self.hass, 'aux-state', 'switchmeon') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('on', state.attributes.get('aux_heat')) + assert 'on' == state.attributes.get('aux_heat') # anything other than 'switchmeon' should turn Aux mode off fire_mqtt_message(self.hass, 'aux-state', 'somerandomstring') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual('off', state.attributes.get('aux_heat')) + assert 'off' == state.attributes.get('aux_heat') # Current temperature fire_mqtt_message(self.hass, 'current-temperature', '"74656"') self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) - self.assertEqual(74656, state.attributes.get('current_temperature')) + assert 74656 == state.attributes.get('current_temperature') def test_min_temp_custom(self): """Test a custom min temp.""" @@ -637,8 +635,8 @@ class TestMQTTClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) min_temp = state.attributes.get('min_temp') - self.assertIsInstance(min_temp, float) - self.assertEqual(26, state.attributes.get('min_temp')) + assert isinstance(min_temp, float) + assert 26 == state.attributes.get('min_temp') def test_max_temp_custom(self): """Test a custom max temp.""" @@ -650,8 +648,8 @@ class TestMQTTClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) max_temp = state.attributes.get('max_temp') - self.assertIsInstance(max_temp, float) - self.assertEqual(60, max_temp) + assert isinstance(max_temp, float) + assert 60 == max_temp def test_temp_step_custom(self): """Test a custom temp step.""" @@ -663,8 +661,8 @@ class TestMQTTClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) temp_step = state.attributes.get('target_temp_step') - self.assertIsInstance(temp_step, float) - self.assertEqual(0.01, temp_step) + assert isinstance(temp_step, float) + assert 0.01 == temp_step async def test_discovery_removal_climate(hass, mqtt_mock, caplog): diff --git a/tests/components/climate/test_nuheat.py b/tests/components/climate/test_nuheat.py index 5b47a5a75af..40b0732f661 100644 --- a/tests/components/climate/test_nuheat.py +++ b/tests/components/climate/test_nuheat.py @@ -104,111 +104,105 @@ class TestNuHeat(unittest.TestCase): def test_name(self): """Test name property.""" - self.assertEqual(self.thermostat.name, "Master bathroom") + assert self.thermostat.name == "Master bathroom" def test_icon(self): """Test name property.""" - self.assertEqual(self.thermostat.icon, "mdi:thermometer") + assert self.thermostat.icon == "mdi:thermometer" def test_supported_features(self): """Test name property.""" features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE) - self.assertEqual(self.thermostat.supported_features, features) + assert self.thermostat.supported_features == features def test_temperature_unit(self): """Test temperature unit.""" - self.assertEqual(self.thermostat.temperature_unit, TEMP_FAHRENHEIT) + assert self.thermostat.temperature_unit == TEMP_FAHRENHEIT self.thermostat._temperature_unit = "C" - self.assertEqual(self.thermostat.temperature_unit, TEMP_CELSIUS) + assert self.thermostat.temperature_unit == TEMP_CELSIUS def test_current_temperature(self): """Test current temperature.""" - self.assertEqual(self.thermostat.current_temperature, 72) + assert self.thermostat.current_temperature == 72 self.thermostat._temperature_unit = "C" - self.assertEqual(self.thermostat.current_temperature, 22) + assert self.thermostat.current_temperature == 22 def test_current_operation(self): """Test current operation.""" - self.assertEqual(self.thermostat.current_operation, STATE_HEAT) + assert self.thermostat.current_operation == STATE_HEAT self.thermostat._thermostat.heating = False - self.assertEqual(self.thermostat.current_operation, STATE_IDLE) + assert self.thermostat.current_operation == STATE_IDLE def test_min_temp(self): """Test min temp.""" - self.assertEqual(self.thermostat.min_temp, 41) + assert self.thermostat.min_temp == 41 self.thermostat._temperature_unit = "C" - self.assertEqual(self.thermostat.min_temp, 5) + assert self.thermostat.min_temp == 5 def test_max_temp(self): """Test max temp.""" - self.assertEqual(self.thermostat.max_temp, 157) + assert self.thermostat.max_temp == 157 self.thermostat._temperature_unit = "C" - self.assertEqual(self.thermostat.max_temp, 69) + assert self.thermostat.max_temp == 69 def test_target_temperature(self): """Test target temperature.""" - self.assertEqual(self.thermostat.target_temperature, 72) + assert self.thermostat.target_temperature == 72 self.thermostat._temperature_unit = "C" - self.assertEqual(self.thermostat.target_temperature, 22) + assert self.thermostat.target_temperature == 22 def test_current_hold_mode(self): """Test current hold mode.""" self.thermostat._thermostat.schedule_mode = SCHEDULE_RUN - self.assertEqual(self.thermostat.current_hold_mode, nuheat.MODE_AUTO) + assert self.thermostat.current_hold_mode == nuheat.MODE_AUTO self.thermostat._thermostat.schedule_mode = SCHEDULE_HOLD - self.assertEqual( - self.thermostat.current_hold_mode, nuheat.MODE_HOLD_TEMPERATURE) + assert self.thermostat.current_hold_mode == \ + nuheat.MODE_HOLD_TEMPERATURE self.thermostat._thermostat.schedule_mode = SCHEDULE_TEMPORARY_HOLD - self.assertEqual( - self.thermostat.current_hold_mode, nuheat.MODE_TEMPORARY_HOLD) + assert self.thermostat.current_hold_mode == nuheat.MODE_TEMPORARY_HOLD self.thermostat._thermostat.schedule_mode = None - self.assertEqual( - self.thermostat.current_hold_mode, nuheat.MODE_AUTO) + assert self.thermostat.current_hold_mode == nuheat.MODE_AUTO def test_operation_list(self): """Test the operation list.""" - self.assertEqual( - self.thermostat.operation_list, + assert self.thermostat.operation_list == \ [STATE_HEAT, STATE_IDLE] - ) def test_resume_program(self): """Test resume schedule.""" self.thermostat.resume_program() self.thermostat._thermostat.resume_schedule.assert_called_once_with() - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._force_update def test_set_hold_mode(self): """Test set hold mode.""" self.thermostat.set_hold_mode("temperature") - self.assertEqual( - self.thermostat._thermostat.schedule_mode, SCHEDULE_HOLD) - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._thermostat.schedule_mode == SCHEDULE_HOLD + assert self.thermostat._force_update self.thermostat.set_hold_mode("temporary_temperature") - self.assertEqual( - self.thermostat._thermostat.schedule_mode, SCHEDULE_TEMPORARY_HOLD) - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._thermostat.schedule_mode == \ + SCHEDULE_TEMPORARY_HOLD + assert self.thermostat._force_update self.thermostat.set_hold_mode("auto") - self.assertEqual( - self.thermostat._thermostat.schedule_mode, SCHEDULE_RUN) - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._thermostat.schedule_mode == SCHEDULE_RUN + assert self.thermostat._force_update def test_set_temperature(self): """Test set temperature.""" self.thermostat.set_temperature(temperature=85) - self.assertEqual(self.thermostat._thermostat.target_fahrenheit, 85) - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._thermostat.target_fahrenheit == 85 + assert self.thermostat._force_update self.thermostat._temperature_unit = "C" self.thermostat.set_temperature(temperature=23) - self.assertEqual(self.thermostat._thermostat.target_celsius, 23) - self.assertTrue(self.thermostat._force_update) + assert self.thermostat._thermostat.target_celsius == 23 + assert self.thermostat._force_update @patch.object(nuheat.NuHeatThermostat, "_throttled_update") def test_update_without_throttle(self, throttled_update): @@ -216,7 +210,7 @@ class TestNuHeat(unittest.TestCase): self.thermostat._force_update = True self.thermostat.update() throttled_update.assert_called_once_with(no_throttle=True) - self.assertFalse(self.thermostat._force_update) + assert not self.thermostat._force_update @patch.object(nuheat.NuHeatThermostat, "_throttled_update") def test_update_with_throttle(self, throttled_update): @@ -224,7 +218,7 @@ class TestNuHeat(unittest.TestCase): self.thermostat._force_update = False self.thermostat.update() throttled_update.assert_called_once_with() - self.assertFalse(self.thermostat._force_update) + assert not self.thermostat._force_update def test_throttled_update(self): """Test update with throttle.""" diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index 929d96d4650..78ca72dd1e4 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -39,8 +39,7 @@ class TestCounter(unittest.TestCase): ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_config_options(self): """Test configuration options.""" @@ -66,23 +65,23 @@ class TestCounter(unittest.TestCase): _LOGGER.debug('ENTITIES: %s', self.hass.states.entity_ids()) - self.assertEqual(count_start + 2, len(self.hass.states.entity_ids())) + assert count_start + 2 == len(self.hass.states.entity_ids()) self.hass.block_till_done() state_1 = self.hass.states.get('counter.test_1') state_2 = self.hass.states.get('counter.test_2') - self.assertIsNotNone(state_1) - self.assertIsNotNone(state_2) + assert state_1 is not None + assert state_2 is not None - self.assertEqual(0, int(state_1.state)) - self.assertNotIn(ATTR_ICON, state_1.attributes) - self.assertNotIn(ATTR_FRIENDLY_NAME, state_1.attributes) + assert 0 == int(state_1.state) + assert ATTR_ICON not in state_1.attributes + assert ATTR_FRIENDLY_NAME not in state_1.attributes - self.assertEqual(10, int(state_2.state)) - self.assertEqual('Hello World', - state_2.attributes.get(ATTR_FRIENDLY_NAME)) - self.assertEqual('mdi:work', state_2.attributes.get(ATTR_ICON)) + assert 10 == int(state_2.state) + assert 'Hello World' == \ + state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert 'mdi:work' == state_2.attributes.get(ATTR_ICON) def test_methods(self): """Test increment, decrement, and reset methods.""" @@ -97,31 +96,31 @@ class TestCounter(unittest.TestCase): entity_id = 'counter.test_1' state = self.hass.states.get(entity_id) - self.assertEqual(0, int(state.state)) + assert 0 == int(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(1, int(state.state)) + assert 1 == int(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(2, int(state.state)) + assert 2 == int(state.state) decrement(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(1, int(state.state)) + assert 1 == int(state.state) reset(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(0, int(state.state)) + assert 0 == int(state.state) def test_methods_with_config(self): """Test increment, decrement, and reset methods with configuration.""" @@ -140,25 +139,25 @@ class TestCounter(unittest.TestCase): entity_id = 'counter.test' state = self.hass.states.get(entity_id) - self.assertEqual(10, int(state.state)) + assert 10 == int(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(15, int(state.state)) + assert 15 == int(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(20, int(state.state)) + assert 20 == int(state.state) decrement(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(15, int(state.state)) + assert 15 == int(state.state) @asyncio.coroutine diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index 282b1d2873f..09ac04f359d 100644 --- a/tests/components/cover/test_mqtt.py +++ b/tests/components/cover/test_mqtt.py @@ -33,7 +33,7 @@ class TestCoverMQTT(unittest.TestCase): def test_state_via_state_topic(self): """Test the controlling state via topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -44,45 +44,45 @@ class TestCoverMQTT(unittest.TestCase): 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNKNOWN == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'state-topic', '0') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_CLOSED, state.state) + assert STATE_CLOSED == state.state fire_mqtt_message(self.hass, 'state-topic', '50') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_OPEN, state.state) + assert STATE_OPEN == state.state fire_mqtt_message(self.hass, 'state-topic', '100') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_OPEN, state.state) + assert STATE_OPEN == state.state fire_mqtt_message(self.hass, 'state-topic', STATE_CLOSED) self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_CLOSED, state.state) + assert STATE_CLOSED == state.state fire_mqtt_message(self.hass, 'state-topic', STATE_OPEN) self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_OPEN, state.state) + assert STATE_OPEN == state.state def test_state_via_template(self): """Test the controlling state via topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -91,37 +91,37 @@ class TestCoverMQTT(unittest.TestCase): 'qos': 0, 'value_template': '{{ (value | multiply(0.01)) | int }}', } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state fire_mqtt_message(self.hass, 'state-topic', '10000') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_OPEN, state.state) + assert STATE_OPEN == state.state fire_mqtt_message(self.hass, 'state-topic', '99') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_CLOSED, state.state) + assert STATE_CLOSED == state.state def test_optimistic_state_change(self): """Test changing state optimistically.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'command_topic': 'command-topic', 'qos': 0, } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNKNOWN == state.state + assert state.attributes.get(ATTR_ASSUMED_STATE) self.hass.services.call( cover.DOMAIN, SERVICE_OPEN_COVER, @@ -132,7 +132,7 @@ class TestCoverMQTT(unittest.TestCase): 'command-topic', 'OPEN', 0, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_OPEN, state.state) + assert STATE_OPEN == state.state self.hass.services.call( cover.DOMAIN, SERVICE_CLOSE_COVER, @@ -142,11 +142,11 @@ class TestCoverMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'CLOSE', 0, False) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_CLOSED, state.state) + assert STATE_CLOSED == state.state def test_send_open_cover_command(self): """Test the sending of open_cover.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -154,10 +154,10 @@ class TestCoverMQTT(unittest.TestCase): 'command_topic': 'command-topic', 'qos': 2 } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state self.hass.services.call( cover.DOMAIN, SERVICE_OPEN_COVER, @@ -167,11 +167,11 @@ class TestCoverMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'OPEN', 2, False) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state def test_send_close_cover_command(self): """Test the sending of close_cover.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -179,10 +179,10 @@ class TestCoverMQTT(unittest.TestCase): 'command_topic': 'command-topic', 'qos': 2 } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state self.hass.services.call( cover.DOMAIN, SERVICE_CLOSE_COVER, @@ -192,11 +192,11 @@ class TestCoverMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'CLOSE', 2, False) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state def test_send_stop__cover_command(self): """Test the sending of stop_cover.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -204,10 +204,10 @@ class TestCoverMQTT(unittest.TestCase): 'command_topic': 'command-topic', 'qos': 2 } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state self.hass.services.call( cover.DOMAIN, SERVICE_STOP_COVER, @@ -217,11 +217,11 @@ class TestCoverMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'STOP', 2, False) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state def test_current_cover_position(self): """Test the current cover position.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -231,42 +231,42 @@ class TestCoverMQTT(unittest.TestCase): 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) state_attributes_dict = self.hass.states.get( 'cover.test').attributes - self.assertFalse('current_position' in state_attributes_dict) - self.assertFalse('current_tilt_position' in state_attributes_dict) - self.assertFalse(4 & self.hass.states.get( + assert not ('current_position' in state_attributes_dict) + assert not ('current_tilt_position' in state_attributes_dict) + assert not (4 & self.hass.states.get( 'cover.test').attributes['supported_features'] == 4) fire_mqtt_message(self.hass, 'state-topic', '0') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] - self.assertEqual(0, current_cover_position) + assert 0 == current_cover_position fire_mqtt_message(self.hass, 'state-topic', '50') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] - self.assertEqual(50, current_cover_position) + assert 50 == current_cover_position fire_mqtt_message(self.hass, 'state-topic', '101') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] - self.assertEqual(50, current_cover_position) + assert 50 == current_cover_position fire_mqtt_message(self.hass, 'state-topic', 'non-numeric') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] - self.assertEqual(50, current_cover_position) + assert 50 == current_cover_position def test_set_cover_position(self): """Test setting cover position.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -277,29 +277,29 @@ class TestCoverMQTT(unittest.TestCase): 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) state_attributes_dict = self.hass.states.get( 'cover.test').attributes - self.assertFalse('current_position' in state_attributes_dict) - self.assertFalse('current_tilt_position' in state_attributes_dict) + assert not ('current_position' in state_attributes_dict) + assert not ('current_tilt_position' in state_attributes_dict) - self.assertTrue(4 & self.hass.states.get( - 'cover.test').attributes['supported_features'] == 4) + assert 4 & self.hass.states.get( + 'cover.test').attributes['supported_features'] == 4 fire_mqtt_message(self.hass, 'state-topic', '22') self.hass.block_till_done() state_attributes_dict = self.hass.states.get( 'cover.test').attributes - self.assertTrue('current_position' in state_attributes_dict) - self.assertFalse('current_tilt_position' in state_attributes_dict) + assert 'current_position' in state_attributes_dict + assert not ('current_tilt_position' in state_attributes_dict) current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] - self.assertEqual(22, current_cover_position) + assert 22 == current_cover_position def test_set_position_templated(self): """Test setting cover position via template.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -311,7 +311,7 @@ class TestCoverMQTT(unittest.TestCase): 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_POSITION, @@ -323,7 +323,7 @@ class TestCoverMQTT(unittest.TestCase): def test_set_position_untemplated(self): """Test setting cover position via template.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -334,7 +334,7 @@ class TestCoverMQTT(unittest.TestCase): 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_POSITION, @@ -346,7 +346,7 @@ class TestCoverMQTT(unittest.TestCase): def test_no_command_topic(self): """Test with no command topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -357,14 +357,14 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_command_topic': 'tilt-command', 'tilt_status_topic': 'tilt-status' } - })) + }) - self.assertEqual(240, self.hass.states.get( - 'cover.test').attributes['supported_features']) + assert 240 == self.hass.states.get( + 'cover.test').attributes['supported_features'] def test_with_command_topic_and_tilt(self): """Test with command topic and tilt config.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'command_topic': 'test', 'platform': 'mqtt', @@ -376,14 +376,14 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_command_topic': 'tilt-command', 'tilt_status_topic': 'tilt-status' } - })) + }) - self.assertEqual(251, self.hass.states.get( - 'cover.test').attributes['supported_features']) + assert 251 == self.hass.states.get( + 'cover.test').attributes['supported_features'] def test_tilt_defaults(self): """Test the defaults.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -396,19 +396,19 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_command_topic': 'tilt-command', 'tilt_status_topic': 'tilt-status' } - })) + }) state_attributes_dict = self.hass.states.get( 'cover.test').attributes - self.assertTrue('current_tilt_position' in state_attributes_dict) + assert 'current_tilt_position' in state_attributes_dict current_cover_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(STATE_UNKNOWN, current_cover_position) + assert STATE_UNKNOWN == current_cover_position def test_tilt_via_invocation_defaults(self): """Test tilt defaults on close/open.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -421,7 +421,7 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_command_topic': 'tilt-command-topic', 'tilt_status_topic': 'tilt-status-topic' } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_OPEN_COVER_TILT, @@ -442,7 +442,7 @@ class TestCoverMQTT(unittest.TestCase): def test_tilt_given_value(self): """Test tilting to a given value.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -457,7 +457,7 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_opened_value': 400, 'tilt_closed_value': 125 } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_OPEN_COVER_TILT, @@ -478,7 +478,7 @@ class TestCoverMQTT(unittest.TestCase): def test_tilt_via_topic(self): """Test tilt by updating status via MQTT.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -493,25 +493,25 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_opened_value': 400, 'tilt_closed_value': 125 } - })) + }) fire_mqtt_message(self.hass, 'tilt-status-topic', '0') self.hass.block_till_done() current_cover_tilt_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(0, current_cover_tilt_position) + assert 0 == current_cover_tilt_position fire_mqtt_message(self.hass, 'tilt-status-topic', '50') self.hass.block_till_done() current_cover_tilt_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(50, current_cover_tilt_position) + assert 50 == current_cover_tilt_position def test_tilt_via_topic_altered_range(self): """Test tilt status via MQTT with altered tilt range.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -528,32 +528,32 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_min': 0, 'tilt_max': 50 } - })) + }) fire_mqtt_message(self.hass, 'tilt-status-topic', '0') self.hass.block_till_done() current_cover_tilt_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(0, current_cover_tilt_position) + assert 0 == current_cover_tilt_position fire_mqtt_message(self.hass, 'tilt-status-topic', '50') self.hass.block_till_done() current_cover_tilt_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(100, current_cover_tilt_position) + assert 100 == current_cover_tilt_position fire_mqtt_message(self.hass, 'tilt-status-topic', '25') self.hass.block_till_done() current_cover_tilt_position = self.hass.states.get( 'cover.test').attributes['current_tilt_position'] - self.assertEqual(50, current_cover_tilt_position) + assert 50 == current_cover_tilt_position def test_tilt_position(self): """Test tilt via method invocation.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -568,7 +568,7 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_opened_value': 400, 'tilt_closed_value': 125 } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_TILT_POSITION, @@ -581,7 +581,7 @@ class TestCoverMQTT(unittest.TestCase): def test_tilt_position_altered_range(self): """Test tilt via method invocation with altered range.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -598,7 +598,7 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_min': 0, 'tilt_max': 50 } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_TILT_POSITION, @@ -618,7 +618,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 100, 0, 0, 100, False, False, None, None, None, None, None) - self.assertEqual(44, mqtt_cover.find_percentage_in_range(44)) + assert 44 == mqtt_cover.find_percentage_in_range(44) def test_find_percentage_in_range_altered(self): """Test find percentage in range with altered range.""" @@ -629,7 +629,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 180, 80, 80, 180, False, False, None, None, None, None, None) - self.assertEqual(40, mqtt_cover.find_percentage_in_range(120)) + assert 40 == mqtt_cover.find_percentage_in_range(120) def test_find_percentage_in_range_defaults_inverted(self): """Test find percentage in range with default range but inverted.""" @@ -640,7 +640,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 100, 0, 0, 100, False, True, None, None, None, None, None) - self.assertEqual(56, mqtt_cover.find_percentage_in_range(44)) + assert 56 == mqtt_cover.find_percentage_in_range(44) def test_find_percentage_in_range_altered_inverted(self): """Test find percentage in range with altered range and inverted.""" @@ -651,7 +651,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 180, 80, 80, 180, False, True, None, None, None, None, None) - self.assertEqual(60, mqtt_cover.find_percentage_in_range(120)) + assert 60 == mqtt_cover.find_percentage_in_range(120) def test_find_in_range_defaults(self): """Test find in range with default range.""" @@ -662,7 +662,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 100, 0, 0, 100, False, False, None, None, None, None, None) - self.assertEqual(44, mqtt_cover.find_in_range_from_percent(44)) + assert 44 == mqtt_cover.find_in_range_from_percent(44) def test_find_in_range_altered(self): """Test find in range with altered range.""" @@ -673,7 +673,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 180, 80, 80, 180, False, False, None, None, None, None, None) - self.assertEqual(120, mqtt_cover.find_in_range_from_percent(40)) + assert 120 == mqtt_cover.find_in_range_from_percent(40) def test_find_in_range_defaults_inverted(self): """Test find in range with default range but inverted.""" @@ -684,7 +684,7 @@ class TestCoverMQTT(unittest.TestCase): False, None, 100, 0, 0, 100, False, True, None, None, None, None, None) - self.assertEqual(44, mqtt_cover.find_in_range_from_percent(56)) + assert 44 == mqtt_cover.find_in_range_from_percent(56) def test_find_in_range_altered_inverted(self): """Test find in range with altered range and inverted.""" @@ -695,25 +695,25 @@ class TestCoverMQTT(unittest.TestCase): False, None, 180, 80, 80, 180, False, True, None, None, None, None, None) - self.assertEqual(120, mqtt_cover.find_in_range_from_percent(60)) + assert 120 == mqtt_cover.find_in_range_from_percent(60) def test_availability_without_topic(self): """Test availability without defined availability topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'state-topic', 'command_topic': 'command-topic' } - })) + }) state = self.hass.states.get('cover.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state def test_availability_by_defaults(self): """Test availability by defaults with defined topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -721,26 +721,26 @@ class TestCoverMQTT(unittest.TestCase): 'command_topic': 'command-topic', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_availability_by_custom_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + assert setup_component(self.hass, cover.DOMAIN, { cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -750,22 +750,22 @@ class TestCoverMQTT(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('cover.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal_cover(hass, mqtt_mock, caplog): diff --git a/tests/components/cover/test_rfxtrx.py b/tests/components/cover/test_rfxtrx.py index ab8b8f9a93c..474e360500c 100644 --- a/tests/components/cover/test_rfxtrx.py +++ b/tests/components/cover/test_rfxtrx.py @@ -28,18 +28,18 @@ class TestCoverRfxtrx(unittest.TestCase): def test_valid_config(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'0b1100cd0213c7f210010f51': { 'name': 'Test', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config_capital_letters(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'cover', { + assert not setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': @@ -47,11 +47,11 @@ class TestCoverRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', 'signal_repetitions': 3} - }}})) + }}}) def test_invalid_config_extra_key(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'cover', { + assert not setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, 'invalid_key': 'afda', @@ -60,11 +60,11 @@ class TestCoverRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config_capital_packetid(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'cover', { + assert not setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': @@ -72,52 +72,52 @@ class TestCoverRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': 'AA1100cd0213c7f210010f51', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config_missing_packetid(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'cover', { + assert not setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'213c7f216': { 'name': 'Test', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_default_config(self): """Test with 0 cover.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', - 'devices': {}}})) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + 'devices': {}}}) + assert 0 == len(rfxtrx_core.RFX_DEVICES) def test_one_cover(self): """Test with 1 cover.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'devices': {'0b1400cd0213c7f210010f51': { 'name': 'Test' - }}}})) + }}}}) import RFXtrx as rfxtrxmod rfxtrx_core.RFXOBJECT =\ rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) for id in rfxtrx_core.RFX_DEVICES: entity = rfxtrx_core.RFX_DEVICES[id] - self.assertEqual(entity.signal_repetitions, 1) - self.assertFalse(entity.should_fire_event) - self.assertFalse(entity.should_poll) + assert entity.signal_repetitions == 1 + assert not entity.should_fire_event + assert not entity.should_poll entity.open_cover() entity.close_cover() entity.stop_cover() def test_several_covers(self): """Test with 3 covers.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'signal_repetitions': 3, 'devices': @@ -127,13 +127,13 @@ class TestCoverRfxtrx(unittest.TestCase): 'name': 'Bath'}, '0b1100101118cdea02010f70': { 'name': 'Living'} - }}})) + }}}) - self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES)) + assert 3 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: entity = rfxtrx_core.RFX_DEVICES[id] - self.assertEqual(entity.signal_repetitions, 3) + assert entity.signal_repetitions == 3 if entity.name == 'Living': device_num = device_num + 1 elif entity.name == 'Bath': @@ -141,14 +141,14 @@ class TestCoverRfxtrx(unittest.TestCase): elif entity.name == 'Test': device_num = device_num + 1 - self.assertEqual(3, device_num) + assert 3 == device_num def test_discover_covers(self): """Test with discovery of covers.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': True, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0a140002f38cae010f0070') event.data = bytearray([0x0A, 0x14, 0x00, 0x02, 0xF3, 0x8C, @@ -156,7 +156,7 @@ class TestCoverRfxtrx(unittest.TestCase): for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, @@ -164,14 +164,14 @@ class TestCoverRfxtrx(unittest.TestCase): for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a light event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') @@ -179,14 +179,14 @@ class TestCoverRfxtrx(unittest.TestCase): 0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70]) for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) def test_discover_cover_noautoadd(self): """Test with discovery of cover when auto add is False.""" - self.assertTrue(setup_component(self.hass, 'cover', { + assert setup_component(self.hass, 'cover', { 'cover': {'platform': 'rfxtrx', 'automatic_add': False, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0a1400adf394ab010d0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, @@ -194,21 +194,21 @@ class TestCoverRfxtrx(unittest.TestCase): for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, 0xAB, 0x02, 0x0E, 0x00, 0x60]) for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a light event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') @@ -216,4 +216,4 @@ class TestCoverRfxtrx(unittest.TestCase): 0x18, 0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70]) for evt_sub in rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS: evt_sub(event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) diff --git a/tests/components/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index d43a7d53969..09f14dc9700 100644 --- a/tests/components/device_tracker/test_asuswrt.py +++ b/tests/components/device_tracker/test_asuswrt.py @@ -103,5 +103,5 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase): conf_dict[DOMAIN][CONF_MODE] = 'router' conf_dict[DOMAIN][CONF_PROTOCOL] = 'ssh' conf_dict[DOMAIN][CONF_PORT] = 22 - self.assertEqual(asuswrt_mock.call_count, 1) - self.assertEqual(asuswrt_mock.call_args, mock.call(conf_dict[DOMAIN])) + assert asuswrt_mock.call_count == 1 + assert asuswrt_mock.call_args == mock.call(conf_dict[DOMAIN]) diff --git a/tests/components/device_tracker/test_ddwrt.py b/tests/components/device_tracker/test_ddwrt.py index 3e60e1bae46..457ef6b47d0 100644 --- a/tests/components/device_tracker/test_ddwrt.py +++ b/tests/components/device_tracker/test_ddwrt.py @@ -69,9 +69,8 @@ class TestDdwrt(unittest.TestCase): CONF_PASSWORD: '0' }}) - self.assertTrue( - 'Failed to authenticate' in - str(mock_error.call_args_list[-1])) + assert 'Failed to authenticate' in \ + str(mock_error.call_args_list[-1]) @mock.patch('homeassistant.components.device_tracker.ddwrt._LOGGER.error') def test_invalid_response(self, mock_error): @@ -89,9 +88,8 @@ class TestDdwrt(unittest.TestCase): CONF_PASSWORD: '0' }}) - self.assertTrue( - 'Invalid response from DD-WRT' in - str(mock_error.call_args_list[-1])) + assert 'Invalid response from DD-WRT' in \ + str(mock_error.call_args_list[-1]) @mock.patch('homeassistant.components.device_tracker._LOGGER.error') @mock.patch('homeassistant.components.device_tracker.' @@ -106,9 +104,8 @@ class TestDdwrt(unittest.TestCase): CONF_USERNAME: 'fake_user', CONF_PASSWORD: '0' }}) - self.assertTrue( - 'Error setting up platform' in - str(error_mock.call_args_list[-1])) + assert 'Error setting up platform' in \ + str(error_mock.call_args_list[-1]) @mock.patch('homeassistant.components.device_tracker.ddwrt.requests.get', side_effect=requests.exceptions.Timeout) @@ -124,9 +121,8 @@ class TestDdwrt(unittest.TestCase): CONF_PASSWORD: '0' }}) - self.assertTrue( - 'Connection to the router timed out' in - str(mock_error.call_args_list[-1])) + assert 'Connection to the router timed out' in \ + str(mock_error.call_args_list[-1]) def test_scan_devices(self): """Test creating device info (MAC, name) from response. @@ -158,8 +154,8 @@ class TestDdwrt(unittest.TestCase): path = self.hass.config.path(device_tracker.YAML_DEVICES) devices = config.load_yaml_config_file(path) for device in devices: - self.assertIn(devices[device]['mac'], status_lan) - self.assertIn(slugify(devices[device]['name']), status_lan) + assert devices[device]['mac'] in status_lan + assert slugify(devices[device]['name']) in status_lan def test_device_name_no_data(self): """Test creating device info (MAC only) when no response.""" @@ -185,7 +181,7 @@ class TestDdwrt(unittest.TestCase): status_lan = load_fixture('Ddwrt_Status_Lan.txt') for device in devices: _LOGGER.error(devices[device]) - self.assertIn(devices[device]['mac'], status_lan) + assert devices[device]['mac'] in status_lan def test_device_name_no_dhcp(self): """Test creating device info (MAC) when missing dhcp response.""" @@ -213,7 +209,7 @@ class TestDdwrt(unittest.TestCase): status_lan = load_fixture('Ddwrt_Status_Lan.txt') for device in devices: _LOGGER.error(devices[device]) - self.assertIn(devices[device]['mac'], status_lan) + assert devices[device]['mac'] in status_lan def test_update_no_data(self): """Test error handling of no response when active devices checked.""" diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index b1b68ff92df..6ceb4674faf 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -25,6 +25,7 @@ from homeassistant.helpers.json import JSONEncoder from tests.common import ( get_test_home_assistant, fire_time_changed, patch_yaml_files, assert_setup_component, mock_restore_cache) +import pytest TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}} @@ -57,11 +58,11 @@ class TestComponentsDeviceTracker(unittest.TestCase): self.hass.states.set(entity_id, STATE_HOME) - self.assertTrue(device_tracker.is_on(self.hass, entity_id)) + assert device_tracker.is_on(self.hass, entity_id) self.hass.states.set(entity_id, STATE_NOT_HOME) - self.assertFalse(device_tracker.is_on(self.hass, entity_id)) + assert not device_tracker.is_on(self.hass, entity_id) # pylint: disable=no-self-use def test_reading_broken_yaml_config(self): @@ -103,13 +104,13 @@ class TestComponentsDeviceTracker(unittest.TestCase): TEST_PLATFORM) config = device_tracker.load_config(self.yaml_devices, self.hass, device.consider_home)[0] - self.assertEqual(device.dev_id, config.dev_id) - self.assertEqual(device.track, config.track) - self.assertEqual(device.mac, config.mac) - self.assertEqual(device.config_picture, config.config_picture) - self.assertEqual(device.away_hide, config.away_hide) - self.assertEqual(device.consider_home, config.consider_home) - self.assertEqual(device.icon, config.icon) + assert device.dev_id == config.dev_id + assert device.track == config.track + assert device.mac == config.mac + assert device.config_picture == config.config_picture + assert device.away_hide == config.away_hide + assert device.consider_home == config.consider_home + assert device.icon == config.icon # pylint: disable=invalid-name @patch('homeassistant.components.device_tracker._LOGGER.warning') @@ -157,7 +158,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): 'AB:CD:EF:GH:IJ', 'Test name', gravatar='test@example.com') gravatar_url = ("https://www.gravatar.com/avatar/" "55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar") - self.assertEqual(device.config_picture, gravatar_url) + assert device.config_picture == gravatar_url def test_gravatar_and_picture(self): """Test that Gravatar overrides picture.""" @@ -168,7 +169,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): gravatar='test@example.com') gravatar_url = ("https://www.gravatar.com/avatar/" "55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar") - self.assertEqual(device.config_picture, gravatar_url) + assert device.config_picture == gravatar_url @patch( 'homeassistant.components.device_tracker.DeviceTracker.see') @@ -206,8 +207,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): }}) self.hass.block_till_done() - self.assertEqual(STATE_HOME, - self.hass.states.get('device_tracker.dev1').state) + assert STATE_HOME == \ + self.hass.states.get('device_tracker.dev1').state scanner.leave_home('DEV1') @@ -216,8 +217,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): fire_time_changed(self.hass, scan_time) self.hass.block_till_done() - self.assertEqual(STATE_NOT_HOME, - self.hass.states.get('device_tracker.dev1').state) + assert STATE_NOT_HOME == \ + self.hass.states.get('device_tracker.dev1').state def test_entity_attributes(self): """Test the entity attributes.""" @@ -238,9 +239,9 @@ class TestComponentsDeviceTracker(unittest.TestCase): attrs = self.hass.states.get(entity_id).attributes - self.assertEqual(friendly_name, attrs.get(ATTR_FRIENDLY_NAME)) - self.assertEqual(icon, attrs.get(ATTR_ICON)) - self.assertEqual(picture, attrs.get(ATTR_ENTITY_PICTURE)) + assert friendly_name == attrs.get(ATTR_FRIENDLY_NAME) + assert icon == attrs.get(ATTR_ICON) + assert picture == attrs.get(ATTR_ENTITY_PICTURE) def test_device_hidden(self): """Test hidden devices.""" @@ -258,8 +259,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): assert setup_component(self.hass, device_tracker.DOMAIN, TEST_PLATFORM) - self.assertTrue(self.hass.states.get(entity_id) - .attributes.get(ATTR_HIDDEN)) + assert self.hass.states.get(entity_id) \ + .attributes.get(ATTR_HIDDEN) def test_group_all_devices(self): """Test grouping of devices.""" @@ -279,10 +280,9 @@ class TestComponentsDeviceTracker(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES) - self.assertIsNotNone(state) - self.assertEqual(STATE_NOT_HOME, state.state) - self.assertSequenceEqual((entity_id,), - state.attributes.get(ATTR_ENTITY_ID)) + assert state is not None + assert STATE_NOT_HOME == state.state + assert (entity_id,) == state.attributes.get(ATTR_ENTITY_ID) @patch('homeassistant.components.device_tracker.DeviceTracker.async_see') def test_see_service(self, mock_see): @@ -302,8 +302,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): device_tracker.see(self.hass, **params) self.hass.block_till_done() assert mock_see.call_count == 1 - self.assertEqual(mock_see.call_count, 1) - self.assertEqual(mock_see.call_args, call(**params)) + assert mock_see.call_count == 1 + assert mock_see.call_args == call(**params) mock_see.reset_mock() params['dev_id'] += chr(233) # e' acute accent from icloud @@ -311,8 +311,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): device_tracker.see(self.hass, **params) self.hass.block_till_done() assert mock_see.call_count == 1 - self.assertEqual(mock_see.call_count, 1) - self.assertEqual(mock_see.call_args, call(**params)) + assert mock_see.call_count == 1 + assert mock_see.call_args == call(**params) def test_new_device_event_fired(self): """Test that the device tracker will fire an event.""" @@ -375,8 +375,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): def test_see_state(self): """Test device tracker see records state correctly.""" - self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, - TEST_PLATFORM)) + assert setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM) params = { 'mac': 'AA:BB:CC:DD:EE:FF', @@ -401,17 +401,17 @@ class TestComponentsDeviceTracker(unittest.TestCase): state = self.hass.states.get('device_tracker.examplecom') attrs = state.attributes - self.assertEqual(state.state, 'Work') - self.assertEqual(state.object_id, 'examplecom') - self.assertEqual(state.name, 'example.com') - self.assertEqual(attrs['friendly_name'], 'example.com') - self.assertEqual(attrs['battery'], 100) - self.assertEqual(attrs['latitude'], 0.3) - self.assertEqual(attrs['longitude'], 0.8) - self.assertEqual(attrs['test'], 'test') - self.assertEqual(attrs['gps_accuracy'], 1) - self.assertEqual(attrs['source_type'], 'gps') - self.assertEqual(attrs['number'], 1) + assert state.state == 'Work' + assert state.object_id == 'examplecom' + assert state.name == 'example.com' + assert attrs['friendly_name'] == 'example.com' + assert attrs['battery'] == 100 + assert attrs['latitude'] == 0.3 + assert attrs['longitude'] == 0.8 + assert attrs['test'] == 'test' + assert attrs['gps_accuracy'] == 1 + assert attrs['source_type'] == 'gps' + assert attrs['number'] == 1 def test_see_passive_zone_state(self): """Test that the device tracker sets gps for passive trackers.""" @@ -447,15 +447,15 @@ class TestComponentsDeviceTracker(unittest.TestCase): state = self.hass.states.get('device_tracker.dev1') attrs = state.attributes - self.assertEqual(STATE_HOME, state.state) - self.assertEqual(state.object_id, 'dev1') - self.assertEqual(state.name, 'dev1') - self.assertEqual(attrs.get('friendly_name'), 'dev1') - self.assertEqual(attrs.get('latitude'), 1) - self.assertEqual(attrs.get('longitude'), 2) - self.assertEqual(attrs.get('gps_accuracy'), 0) - self.assertEqual(attrs.get('source_type'), - device_tracker.SOURCE_TYPE_ROUTER) + assert STATE_HOME == state.state + assert state.object_id == 'dev1' + assert state.name == 'dev1' + assert attrs.get('friendly_name') == 'dev1' + assert attrs.get('latitude') == 1 + assert attrs.get('longitude') == 2 + assert attrs.get('gps_accuracy') == 0 + assert attrs.get('source_type') == \ + device_tracker.SOURCE_TYPE_ROUTER scanner.leave_home('dev1') @@ -466,15 +466,15 @@ class TestComponentsDeviceTracker(unittest.TestCase): state = self.hass.states.get('device_tracker.dev1') attrs = state.attributes - self.assertEqual(STATE_NOT_HOME, state.state) - self.assertEqual(state.object_id, 'dev1') - self.assertEqual(state.name, 'dev1') - self.assertEqual(attrs.get('friendly_name'), 'dev1') - self.assertEqual(attrs.get('latitude'), None) - self.assertEqual(attrs.get('longitude'), None) - self.assertEqual(attrs.get('gps_accuracy'), None) - self.assertEqual(attrs.get('source_type'), - device_tracker.SOURCE_TYPE_ROUTER) + assert STATE_NOT_HOME == state.state + assert state.object_id == 'dev1' + assert state.name == 'dev1' + assert attrs.get('friendly_name') == 'dev1' + assert attrs.get('latitude')is None + assert attrs.get('longitude')is None + assert attrs.get('gps_accuracy')is None + assert attrs.get('source_type') == \ + device_tracker.SOURCE_TYPE_ROUTER @patch('homeassistant.components.device_tracker._LOGGER.warning') def test_see_failures(self, mock_warning): @@ -486,7 +486,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): tracker.see(mac=567, host_name="Number MAC") # No device id or MAC(not added) - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): run_coroutine_threadsafe( tracker.async_see(), self.hass.loop).result() assert mock_warning.call_count == 0 diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py index 8e4d0dc2769..e760db151df 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -36,7 +36,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): @asyncio.coroutine def mock_setup_scanner(hass, config, see, discovery_info=None): """Check that Qos was added by validation.""" - self.assertTrue('qos' in config) + assert 'qos' in config with patch('homeassistant.components.device_tracker.mqtt.' 'async_setup_scanner', autospec=True, @@ -68,7 +68,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertEqual(location, self.hass.states.get(entity_id).state) + assert location == self.hass.states.get(entity_id).state def test_single_level_wildcard_topic(self): """Test single level wildcard topic.""" @@ -87,7 +87,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertEqual(location, self.hass.states.get(entity_id).state) + assert location == self.hass.states.get(entity_id).state def test_multi_level_wildcard_topic(self): """Test multi level wildcard topic.""" @@ -106,7 +106,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertEqual(location, self.hass.states.get(entity_id).state) + assert location == self.hass.states.get(entity_id).state def test_single_level_wildcard_topic_not_matching(self): """Test not matching single level wildcard topic.""" @@ -125,7 +125,7 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIsNone(self.hass.states.get(entity_id)) + assert self.hass.states.get(entity_id) is None def test_multi_level_wildcard_topic_not_matching(self): """Test not matching multi level wildcard topic.""" @@ -144,4 +144,4 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIsNone(self.hass.states.get(entity_id)) + assert self.hass.states.get(entity_id) is None diff --git a/tests/components/device_tracker/test_mqtt_json.py b/tests/components/device_tracker/test_mqtt_json.py index 41c1d9c0885..44d687a4d45 100644 --- a/tests/components/device_tracker/test_mqtt_json.py +++ b/tests/components/device_tracker/test_mqtt_json.py @@ -46,7 +46,7 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): @asyncio.coroutine def mock_setup_scanner(hass, config, see, discovery_info=None): """Check that Qos was added by validation.""" - self.assertTrue('qos' in config) + assert 'qos' in config with patch('homeassistant.components.device_tracker.mqtt_json.' 'async_setup_scanner', autospec=True, @@ -77,8 +77,8 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() state = self.hass.states.get('device_tracker.zanzito') - self.assertEqual(state.attributes.get('latitude'), 2.0) - self.assertEqual(state.attributes.get('longitude'), 1.0) + assert state.attributes.get('latitude') == 2.0 + assert state.attributes.get('longitude') == 1.0 def test_non_json_message(self): """Test receiving a non JSON message.""" @@ -96,10 +96,9 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): with self.assertLogs(level='ERROR') as test_handle: fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIn( - "ERROR:homeassistant.components.device_tracker.mqtt_json:" - "Error parsing JSON payload: home", - test_handle.output[0]) + assert "ERROR:homeassistant.components.device_tracker.mqtt_json:" \ + "Error parsing JSON payload: home" in \ + test_handle.output[0] def test_incomplete_message(self): """Test receiving an incomplete message.""" @@ -117,11 +116,10 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): with self.assertLogs(level='ERROR') as test_handle: fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIn( - "ERROR:homeassistant.components.device_tracker.mqtt_json:" - "Skipping update for following data because of missing " - "or malformatted data: {\"longitude\": 2.0}", - test_handle.output[0]) + assert "ERROR:homeassistant.components.device_tracker.mqtt_json:" \ + "Skipping update for following data because of missing " \ + "or malformatted data: {\"longitude\": 2.0}" in \ + test_handle.output[0] def test_single_level_wildcard_topic(self): """Test single level wildcard topic.""" @@ -139,8 +137,8 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() state = self.hass.states.get('device_tracker.zanzito') - self.assertEqual(state.attributes.get('latitude'), 2.0) - self.assertEqual(state.attributes.get('longitude'), 1.0) + assert state.attributes.get('latitude') == 2.0 + assert state.attributes.get('longitude') == 1.0 def test_multi_level_wildcard_topic(self): """Test multi level wildcard topic.""" @@ -158,8 +156,8 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() state = self.hass.states.get('device_tracker.zanzito') - self.assertEqual(state.attributes.get('latitude'), 2.0) - self.assertEqual(state.attributes.get('longitude'), 1.0) + assert state.attributes.get('latitude') == 2.0 + assert state.attributes.get('longitude') == 1.0 def test_single_level_wildcard_topic_not_matching(self): """Test not matching single level wildcard topic.""" @@ -177,7 +175,7 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIsNone(self.hass.states.get(entity_id)) + assert self.hass.states.get(entity_id) is None def test_multi_level_wildcard_topic_not_matching(self): """Test not matching multi level wildcard topic.""" @@ -195,4 +193,4 @@ class TestComponentsDeviceTrackerJSONMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, topic, location) self.hass.block_till_done() - self.assertIsNone(self.hass.states.get(entity_id)) + assert self.hass.states.get(entity_id) is None diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 8883ea22600..dcd66ed2a7c 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -299,27 +299,27 @@ class BaseMQTT(unittest.TestCase): def assert_location_state(self, location): """Test the assertion of a location state.""" state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.state, location) + assert state.state == location def assert_location_latitude(self, latitude): """Test the assertion of a location latitude.""" state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('latitude'), latitude) + assert state.attributes.get('latitude') == latitude def assert_location_longitude(self, longitude): """Test the assertion of a location longitude.""" state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('longitude'), longitude) + assert state.attributes.get('longitude') == longitude def assert_location_accuracy(self, accuracy): """Test the assertion of a location accuracy.""" state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) + assert state.attributes.get('gps_accuracy') == accuracy def assert_location_source_type(self, source_type): """Test the assertion of source_type.""" state = self.hass.states.get(DEVICE_TRACKER_STATE) - self.assertEqual(state.attributes.get('source_type'), source_type) + assert state.attributes.get('source_type') == source_type class TestDeviceTrackerOwnTracks(BaseMQTT): @@ -382,19 +382,19 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): """Test the assertion of a mobile beacon tracker state.""" dev_id = MOBILE_BEACON_FMT.format(beacon) state = self.hass.states.get(dev_id) - self.assertEqual(state.state, location) + assert state.state == location def assert_mobile_tracker_latitude(self, latitude, beacon=IBEACON_DEVICE): """Test the assertion of a mobile beacon tracker latitude.""" dev_id = MOBILE_BEACON_FMT.format(beacon) state = self.hass.states.get(dev_id) - self.assertEqual(state.attributes.get('latitude'), latitude) + assert state.attributes.get('latitude') == latitude def assert_mobile_tracker_accuracy(self, accuracy, beacon=IBEACON_DEVICE): """Test the assertion of a mobile beacon tracker accuracy.""" dev_id = MOBILE_BEACON_FMT.format(beacon) state = self.hass.states.get(dev_id) - self.assertEqual(state.attributes.get('gps_accuracy'), accuracy) + assert state.attributes.get('gps_accuracy') == accuracy def test_location_invalid_devid(self): # pylint: disable=invalid-name """Test the update of a location.""" @@ -460,7 +460,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.assert_location_state('outer') # Left clean zone state - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] self.send_message(LOCATION_TOPIC, LOCATION_MESSAGE) @@ -480,7 +480,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(EVENT_TOPIC, message) # Left clean zone state - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] def test_event_gps_entry_inaccurate(self): """Test the event for inaccurate entry.""" @@ -511,7 +511,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.assert_location_state('inner') # But does exit region correctly - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] def test_event_gps_entry_exit_zero_accuracy(self): """Test entry/exit events with accuracy zero.""" @@ -530,7 +530,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.assert_location_state('inner') # But does exit region correctly - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] def test_event_gps_exit_outside_zone_sets_away(self): """Test the event for exit zone.""" @@ -730,7 +730,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.assert_location_state('inner') # Left clean zone state - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] # Now sending a location update moves me again. self.send_message(LOCATION_TOPIC, LOCATION_MESSAGE) @@ -749,7 +749,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(EVENT_TOPIC, message) # Left clean zone state - self.assertFalse(self.context.regions_entered[USER]) + assert not self.context.regions_entered[USER] def test_event_region_entry_exit_right_order(self): """Test the event for ordering.""" @@ -959,8 +959,8 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.hass.block_till_done() self.send_message(EVENT_TOPIC, MOBILE_BEACON_LEAVE_EVENT_MESSAGE) - self.assertEqual(len(self.context.mobile_beacons_active['greg_phone']), - 0) + assert len(self.context.mobile_beacons_active['greg_phone']) == \ + 0 def test_mobile_multiple_enter_exit(self): """Test the multiple entering.""" @@ -968,8 +968,8 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(EVENT_TOPIC, MOBILE_BEACON_ENTER_EVENT_MESSAGE) self.send_message(EVENT_TOPIC, MOBILE_BEACON_LEAVE_EVENT_MESSAGE) - self.assertEqual(len(self.context.mobile_beacons_active['greg_phone']), - 0) + assert len(self.context.mobile_beacons_active['greg_phone']) == \ + 0 def test_complex_movement(self): """Test a complex sequence representative of real-world use.""" @@ -1168,9 +1168,9 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(WAYPOINTS_TOPIC, waypoints_message) # Check if it made it into states wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) - self.assertTrue(wayp is not None) + assert wayp is not None wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[1]) - self.assertTrue(wayp is not None) + assert wayp is not None def test_waypoint_import_blacklist(self): """Test import of list of waypoints for blacklisted user.""" @@ -1178,9 +1178,9 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(WAYPOINTS_TOPIC_BLOCKED, waypoints_message) # Check if it made it into states wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) - self.assertTrue(wayp is None) + assert wayp is None wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) - self.assertTrue(wayp is None) + assert wayp is None def test_waypoint_import_no_whitelist(self): """Test import of list of waypoints with no whitelist set.""" @@ -1201,9 +1201,9 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(WAYPOINTS_TOPIC_BLOCKED, waypoints_message) # Check if it made it into states wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) - self.assertTrue(wayp is not None) + assert wayp is not None wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) - self.assertTrue(wayp is not None) + assert wayp is not None def test_waypoint_import_bad_json(self): """Test importing a bad JSON payload.""" @@ -1211,9 +1211,9 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): self.send_message(WAYPOINTS_TOPIC, waypoints_message, True) # Check if it made it into states wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) - self.assertTrue(wayp is None) + assert wayp is None wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) - self.assertTrue(wayp is None) + assert wayp is None def test_waypoint_import_existing(self): """Test importing a zone that exists.""" @@ -1225,14 +1225,14 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): waypoints_message = WAYPOINTS_UPDATED_MESSAGE.copy() self.send_message(WAYPOINTS_TOPIC, waypoints_message) new_wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) - self.assertTrue(wayp == new_wayp) + assert wayp == new_wayp def test_single_waypoint_import(self): """Test single waypoint message.""" waypoint_message = WAYPOINT_MESSAGE.copy() self.send_message(WAYPOINT_TOPIC, waypoint_message) wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) - self.assertTrue(wayp is not None) + assert wayp is not None def test_not_implemented_message(self): """Handle not implemented message type.""" @@ -1240,7 +1240,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): 'owntracks.async_handle_not_impl_msg', return_value=mock_coro(False)) patch_handler.start() - self.assertFalse(self.send_message(LWT_TOPIC, LWT_MESSAGE)) + assert not self.send_message(LWT_TOPIC, LWT_MESSAGE) patch_handler.stop() def test_unsupported_message(self): @@ -1249,7 +1249,7 @@ class TestDeviceTrackerOwnTracks(BaseMQTT): 'owntracks.async_handle_unsupported_msg', return_value=mock_coro(False)) patch_handler.start() - self.assertFalse(self.send_message(BAD_TOPIC, BAD_MESSAGE)) + assert not self.send_message(BAD_TOPIC, BAD_MESSAGE) patch_handler.stop() @@ -1465,7 +1465,7 @@ class TestDeviceTrackerOwnTrackConfigs(BaseMQTT): 'zone.inner', 'zoning', INNER_ZONE) message = build_message({'desc': 'foo'}, REGION_GPS_ENTER_MESSAGE) - self.assertEqual(message['desc'], 'foo') + assert message['desc'] == 'foo' self.send_message(EVENT_TOPIC, message) self.assert_location_state('inner') diff --git a/tests/components/device_tracker/test_tplink.py b/tests/components/device_tracker/test_tplink.py index b9f1f5f5e5a..b50d1c67511 100644 --- a/tests/components/device_tracker/test_tplink.py +++ b/tests/components/device_tracker/test_tplink.py @@ -40,8 +40,7 @@ class TestTplink4DeviceScanner(unittest.TestCase): # Mock the token retrieval process FAKE_TOKEN = 'fake_token' fake_auth_token_response = 'window.parent.location.href = ' \ - '"https://a/{}/userRpm/Index.htm";'.format( - FAKE_TOKEN) + '"https://a/{}/userRpm/Index.htm";'.format(FAKE_TOKEN) m.get('http://{}/userRpm/LoginRpm.htm?Save=Save'.format( conf_dict[CONF_HOST]), text=fake_auth_token_response) @@ -65,4 +64,4 @@ class TestTplink4DeviceScanner(unittest.TestCase): expected_mac_results = [mac.replace('-', ':') for mac in [FAKE_MAC_1, FAKE_MAC_2, FAKE_MAC_3]] - self.assertEqual(tplink.last_results, expected_mac_results) + assert tplink.last_results == expected_mac_results diff --git a/tests/components/device_tracker/test_unifi_direct.py b/tests/components/device_tracker/test_unifi_direct.py index 1f9cbd24f12..6e2830eee52 100644 --- a/tests/components/device_tracker/test_unifi_direct.py +++ b/tests/components/device_tracker/test_unifi_direct.py @@ -66,7 +66,7 @@ class TestComponentsDeviceTrackerUnifiDirect(unittest.TestCase): assert setup_component(self.hass, DOMAIN, conf_dict) conf_dict[DOMAIN][CONF_PORT] = 22 - self.assertEqual(unifi_mock.call_args, mock.call(conf_dict[DOMAIN])) + assert unifi_mock.call_args == mock.call(conf_dict[DOMAIN]) @patch('pexpect.pxssh.pxssh') def test_get_device_name(self, mock_ssh): @@ -85,11 +85,11 @@ class TestComponentsDeviceTrackerUnifiDirect(unittest.TestCase): mock_ssh.return_value.before = load_fixture('unifi_direct.txt') scanner = get_scanner(self.hass, conf_dict) devices = scanner.scan_devices() - self.assertEqual(23, len(devices)) - self.assertEqual("iPhone", - scanner.get_device_name("98:00:c6:56:34:12")) - self.assertEqual("iPhone", - scanner.get_device_name("98:00:C6:56:34:12")) + assert 23 == len(devices) + assert "iPhone" == \ + scanner.get_device_name("98:00:c6:56:34:12") + assert "iPhone" == \ + scanner.get_device_name("98:00:C6:56:34:12") @patch('pexpect.pxssh.pxssh.logout') @patch('pexpect.pxssh.pxssh.login') @@ -111,7 +111,7 @@ class TestComponentsDeviceTrackerUnifiDirect(unittest.TestCase): mock_login.side_effect = exceptions.EOF("Test") scanner = get_scanner(self.hass, conf_dict) - self.assertFalse(scanner) + assert not scanner @patch('pexpect.pxssh.pxssh.logout') @patch('pexpect.pxssh.pxssh.login', autospec=True) @@ -136,16 +136,16 @@ class TestComponentsDeviceTrackerUnifiDirect(unittest.TestCase): # mock_sendline.side_effect = AssertionError("Test") mock_prompt.side_effect = AssertionError("Test") devices = scanner._get_update() # pylint: disable=protected-access - self.assertTrue(devices is None) + assert devices is None def test_good_response_parses(self): """Test that the response form the AP parses to JSON correctly.""" response = _response_to_json(load_fixture('unifi_direct.txt')) - self.assertTrue(response != {}) + assert response != {} def test_bad_response_returns_none(self): """Test that a bad response form the AP parses to JSON correctly.""" - self.assertTrue(_response_to_json("{(}") == {}) + assert _response_to_json("{(}") == {} def test_config_error(): diff --git a/tests/components/device_tracker/test_xiaomi.py b/tests/components/device_tracker/test_xiaomi.py index 0705fb2c399..9c7c13ee741 100644 --- a/tests/components/device_tracker/test_xiaomi.py +++ b/tests/components/device_tracker/test_xiaomi.py @@ -176,13 +176,13 @@ class TestXiaomiDeviceScanner(unittest.TestCase): }) } xiaomi.get_scanner(self.hass, config) - self.assertEqual(xiaomi_mock.call_count, 1) - self.assertEqual(xiaomi_mock.call_args, mock.call(config[DOMAIN])) + assert xiaomi_mock.call_count == 1 + assert xiaomi_mock.call_args == mock.call(config[DOMAIN]) call_arg = xiaomi_mock.call_args[0][0] - self.assertEqual(call_arg['username'], 'admin') - self.assertEqual(call_arg['password'], 'passwordTest') - self.assertEqual(call_arg['host'], '192.168.0.1') - self.assertEqual(call_arg['platform'], 'device_tracker') + assert call_arg['username'] == 'admin' + assert call_arg['password'] == 'passwordTest' + assert call_arg['host'] == '192.168.0.1' + assert call_arg['platform'] == 'device_tracker' @mock.patch( 'homeassistant.components.device_tracker.xiaomi.XiaomiDeviceScanner', @@ -198,13 +198,13 @@ class TestXiaomiDeviceScanner(unittest.TestCase): }) } xiaomi.get_scanner(self.hass, config) - self.assertEqual(xiaomi_mock.call_count, 1) - self.assertEqual(xiaomi_mock.call_args, mock.call(config[DOMAIN])) + assert xiaomi_mock.call_count == 1 + assert xiaomi_mock.call_args == mock.call(config[DOMAIN]) call_arg = xiaomi_mock.call_args[0][0] - self.assertEqual(call_arg['username'], 'alternativeAdminName') - self.assertEqual(call_arg['password'], 'passwordTest') - self.assertEqual(call_arg['host'], '192.168.0.1') - self.assertEqual(call_arg['platform'], 'device_tracker') + assert call_arg['username'] == 'alternativeAdminName' + assert call_arg['password'] == 'passwordTest' + assert call_arg['host'] == '192.168.0.1' + assert call_arg['platform'] == 'device_tracker' @patch('requests.get', side_effect=mocked_requests) @patch('requests.post', side_effect=mocked_requests) @@ -218,7 +218,7 @@ class TestXiaomiDeviceScanner(unittest.TestCase): CONF_PASSWORD: 'passwordTest' }) } - self.assertIsNone(get_scanner(self.hass, config)) + assert get_scanner(self.hass, config) is None @patch('requests.get', side_effect=mocked_requests) @patch('requests.post', side_effect=mocked_requests) @@ -233,12 +233,12 @@ class TestXiaomiDeviceScanner(unittest.TestCase): }) } scanner = get_scanner(self.hass, config) - self.assertIsNotNone(scanner) - self.assertEqual(2, len(scanner.scan_devices())) - self.assertEqual("Device1", - scanner.get_device_name("23:83:BF:F6:38:A0")) - self.assertEqual("Device2", - scanner.get_device_name("1D:98:EC:5E:D5:A6")) + assert scanner is not None + assert 2 == len(scanner.scan_devices()) + assert "Device1" == \ + scanner.get_device_name("23:83:BF:F6:38:A0") + assert "Device2" == \ + scanner.get_device_name("1D:98:EC:5E:D5:A6") @patch('requests.get', side_effect=mocked_requests) @patch('requests.post', side_effect=mocked_requests) @@ -256,9 +256,9 @@ class TestXiaomiDeviceScanner(unittest.TestCase): }) } scanner = get_scanner(self.hass, config) - self.assertIsNotNone(scanner) - self.assertEqual(2, len(scanner.scan_devices())) - self.assertEqual("Device1", - scanner.get_device_name("23:83:BF:F6:38:A0")) - self.assertEqual("Device2", - scanner.get_device_name("1D:98:EC:5E:D5:A6")) + assert scanner is not None + assert 2 == len(scanner.scan_devices()) + assert "Device1" == \ + scanner.get_device_name("23:83:BF:F6:38:A0") + assert "Device2" == \ + scanner.get_device_name("1D:98:EC:5E:D5:A6") diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index f5377b1812c..9c549f00ee8 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -83,8 +83,8 @@ class TestEmulatedHue(unittest.TestCase): result = requests.get( BRIDGE_URL_BASE.format('/description.xml'), timeout=5) - self.assertEqual(result.status_code, 200) - self.assertTrue('text/xml' in result.headers['content-type']) + assert result.status_code == 200 + assert 'text/xml' in result.headers['content-type'] # Make sure the XML is parsable try: @@ -100,14 +100,14 @@ class TestEmulatedHue(unittest.TestCase): BRIDGE_URL_BASE.format('/api'), data=json.dumps(request_json), timeout=5) - self.assertEqual(result.status_code, 200) - self.assertTrue('application/json' in result.headers['content-type']) + assert result.status_code == 200 + assert 'application/json' in result.headers['content-type'] resp_json = result.json() success_json = resp_json[0] - self.assertTrue('success' in success_json) - self.assertTrue('username' in success_json['success']) + assert 'success' in success_json + assert 'username' in success_json['success'] def test_valid_username_request(self): """Test request with a valid username.""" @@ -117,4 +117,4 @@ class TestEmulatedHue(unittest.TestCase): BRIDGE_URL_BASE.format('/api'), data=json.dumps(request_json), timeout=5) - self.assertEqual(result.status_code, 400) + assert result.status_code == 400 diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py index 48704ca4464..35835ac37e5 100644 --- a/tests/components/fan/test_demo.py +++ b/tests/components/fan/test_demo.py @@ -22,9 +22,9 @@ class TestDemoFan(unittest.TestCase): def setUp(self): """Initialize unit test data.""" self.hass = get_test_home_assistant() - self.assertTrue(setup_component(self.hass, fan.DOMAIN, {'fan': { + assert setup_component(self.hass, fan.DOMAIN, {'fan': { 'platform': 'demo', - }})) + }}) self.hass.block_till_done() def tearDown(self): @@ -33,76 +33,76 @@ class TestDemoFan(unittest.TestCase): def test_turn_on(self): """Test turning on the device.""" - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state common.turn_on(self.hass, FAN_ENTITY_ID) self.hass.block_till_done() - self.assertNotEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF != self.get_entity().state common.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.get_entity().state) - self.assertEqual(fan.SPEED_HIGH, - self.get_entity().attributes[fan.ATTR_SPEED]) + assert STATE_ON == self.get_entity().state + assert fan.SPEED_HIGH == \ + self.get_entity().attributes[fan.ATTR_SPEED] def test_turn_off(self): """Test turning off the device.""" - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state common.turn_on(self.hass, FAN_ENTITY_ID) self.hass.block_till_done() - self.assertNotEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF != self.get_entity().state common.turn_off(self.hass, FAN_ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state def test_turn_off_without_entity_id(self): """Test turning off all fans.""" - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state common.turn_on(self.hass, FAN_ENTITY_ID) self.hass.block_till_done() - self.assertNotEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF != self.get_entity().state common.turn_off(self.hass) self.hass.block_till_done() - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state def test_set_direction(self): """Test setting the direction of the device.""" - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state common.set_direction(self.hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE) self.hass.block_till_done() - self.assertEqual(fan.DIRECTION_REVERSE, - self.get_entity().attributes.get('direction')) + assert fan.DIRECTION_REVERSE == \ + self.get_entity().attributes.get('direction') def test_set_speed(self): """Test setting the speed of the device.""" - self.assertEqual(STATE_OFF, self.get_entity().state) + assert STATE_OFF == self.get_entity().state common.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW) self.hass.block_till_done() - self.assertEqual(fan.SPEED_LOW, - self.get_entity().attributes.get('speed')) + assert fan.SPEED_LOW == \ + self.get_entity().attributes.get('speed') def test_oscillate(self): """Test oscillating the fan.""" - self.assertFalse(self.get_entity().attributes.get('oscillating')) + assert not self.get_entity().attributes.get('oscillating') common.oscillate(self.hass, FAN_ENTITY_ID, True) self.hass.block_till_done() - self.assertTrue(self.get_entity().attributes.get('oscillating')) + assert self.get_entity().attributes.get('oscillating') common.oscillate(self.hass, FAN_ENTITY_ID, False) self.hass.block_till_done() - self.assertFalse(self.get_entity().attributes.get('oscillating')) + assert not self.get_entity().attributes.get('oscillating') def test_is_on(self): """Test is on service call.""" - self.assertFalse(fan.is_on(self.hass, FAN_ENTITY_ID)) + assert not fan.is_on(self.hass, FAN_ENTITY_ID) common.turn_on(self.hass, FAN_ENTITY_ID) self.hass.block_till_done() - self.assertTrue(fan.is_on(self.hass, FAN_ENTITY_ID)) + assert fan.is_on(self.hass, FAN_ENTITY_ID) diff --git a/tests/components/fan/test_dyson.py b/tests/components/fan/test_dyson.py index 452f8e199eb..aacab700f05 100644 --- a/tests/components/fan/test_dyson.py +++ b/tests/components/fan/test_dyson.py @@ -130,14 +130,14 @@ class DysonTest(unittest.TestCase): } }) self.hass.block_till_done() - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 1) + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1 assert mocked_devices.return_value[0].add_message_listener.called def test_dyson_set_speed(self): """Test set fan speed.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.set_speed("1") set_config = device.set_configuration set_config.assert_called_with(fan_mode=FanMode.FAN, @@ -151,7 +151,7 @@ class DysonTest(unittest.TestCase): """Test turn on fan.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.turn_on() set_config = device.set_configuration set_config.assert_called_with(fan_mode=FanMode.FAN) @@ -160,7 +160,7 @@ class DysonTest(unittest.TestCase): """Test turn on fan with night mode.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.night_mode(True) set_config = device.set_configuration set_config.assert_called_with(night_mode=NightMode.NIGHT_MODE_ON) @@ -173,17 +173,17 @@ class DysonTest(unittest.TestCase): """Test night mode.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.is_night_mode) + assert not component.is_night_mode device = _get_device_off() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertTrue(component.is_night_mode) + assert component.is_night_mode def test_dyson_turn_auto_mode(self): """Test turn on/off fan with auto mode.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.auto_mode(True) set_config = device.set_configuration set_config.assert_called_with(fan_mode=FanMode.AUTO) @@ -196,17 +196,17 @@ class DysonTest(unittest.TestCase): """Test auto mode.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.is_auto_mode) + assert not component.is_auto_mode device = _get_device_auto() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertTrue(component.is_auto_mode) + assert component.is_auto_mode def test_dyson_turn_on_speed(self): """Test turn on fan with specified speed.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.turn_on("1") set_config = device.set_configuration set_config.assert_called_with(fan_mode=FanMode.FAN, @@ -220,7 +220,7 @@ class DysonTest(unittest.TestCase): """Test turn off fan.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.should_poll) + assert not component.should_poll component.turn_off() set_config = device.set_configuration set_config.assert_called_with(fan_mode=FanMode.OFF) @@ -245,65 +245,65 @@ class DysonTest(unittest.TestCase): """Test get oscillation value on.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertTrue(component.oscillating) + assert component.oscillating def test_dyson_oscillate_value_off(self): """Test get oscillation value off.""" device = _get_device_off() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.oscillating) + assert not component.oscillating def test_dyson_on(self): """Test device is on.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertTrue(component.is_on) + assert component.is_on def test_dyson_off(self): """Test device is off.""" device = _get_device_off() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.is_on) + assert not component.is_on device = _get_device_with_no_state() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertFalse(component.is_on) + assert not component.is_on def test_dyson_get_speed(self): """Test get device speed.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertEqual(component.speed, 1) + assert component.speed == 1 device = _get_device_off() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertEqual(component.speed, 4) + assert component.speed == 4 device = _get_device_with_no_state() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertIsNone(component.speed) + assert component.speed is None device = _get_device_auto() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertEqual(component.speed, "AUTO") + assert component.speed == "AUTO" def test_dyson_get_direction(self): """Test get device direction.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertIsNone(component.current_direction) + assert component.current_direction is None def test_dyson_get_speed_list(self): """Test get speeds list.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertEqual(len(component.speed_list), 11) + assert len(component.speed_list) == 11 def test_dyson_supported_features(self): """Test supported features.""" device = _get_device_on() component = dyson.DysonPureCoolLinkDevice(self.hass, device) - self.assertEqual(component.supported_features, 3) + assert component.supported_features == 3 def test_on_message(self): """Test when message is received.""" diff --git a/tests/components/fan/test_init.py b/tests/components/fan/test_init.py index 15f9a79d2d2..920a2b81016 100644 --- a/tests/components/fan/test_init.py +++ b/tests/components/fan/test_init.py @@ -3,6 +3,7 @@ import unittest from homeassistant.components.fan import FanEntity +import pytest class BaseFan(FanEntity): @@ -26,15 +27,15 @@ class TestFanEntity(unittest.TestCase): def test_fanentity(self): """Test fan entity methods.""" - self.assertEqual('on', self.fan.state) - self.assertEqual(0, len(self.fan.speed_list)) - self.assertEqual(0, self.fan.supported_features) - self.assertEqual({'speed_list': []}, self.fan.state_attributes) + assert 'on' == self.fan.state + assert 0 == len(self.fan.speed_list) + assert 0 == self.fan.supported_features + assert {'speed_list': []} == self.fan.state_attributes # Test set_speed not required self.fan.oscillate(True) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): self.fan.set_speed('slow') - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): self.fan.turn_on() - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): self.fan.turn_off() diff --git a/tests/components/fan/test_mqtt.py b/tests/components/fan/test_mqtt.py index e2742eeba7d..7bbb8467ed1 100644 --- a/tests/components/fan/test_mqtt.py +++ b/tests/components/fan/test_mqtt.py @@ -37,32 +37,32 @@ class TestMqttFan(unittest.TestCase): }) state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'online') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNAVAILABLE != state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'availability_topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'online') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state def test_custom_availability_payload(self): """Test the availability payload.""" @@ -79,32 +79,32 @@ class TestMqttFan(unittest.TestCase): }) state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'good') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNAVAILABLE != state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'availability_topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'good') self.hass.block_till_done() state = self.hass.states.get('fan.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state async def test_discovery_removal_fan(hass, mqtt_mock, caplog): diff --git a/tests/components/geo_location/test_demo.py b/tests/components/geo_location/test_demo.py index 158e5d61968..a35a8e11af6 100644 --- a/tests/components/geo_location/test_demo.py +++ b/tests/components/geo_location/test_demo.py @@ -37,8 +37,7 @@ class TestDemoPlatform(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, geo_location.DOMAIN): - self.assertTrue(setup_component(self.hass, geo_location.DOMAIN, - CONFIG)) + assert setup_component(self.hass, geo_location.DOMAIN, CONFIG) # In this test, only entities of the geo location domain have been # generated. @@ -47,10 +46,14 @@ class TestDemoPlatform(unittest.TestCase): # Check a single device's attributes. state_first_entry = all_states[0] - self.assertAlmostEqual(state_first_entry.attributes['latitude'], - self.hass.config.latitude, delta=1.0) - self.assertAlmostEqual(state_first_entry.attributes['longitude'], - self.hass.config.longitude, delta=1.0) + assert abs( + state_first_entry.attributes['latitude'] - + self.hass.config.latitude + ) < 1.0 + assert abs( + state_first_entry.attributes['longitude'] - + self.hass.config.longitude + ) < 1.0 assert state_first_entry.attributes['unit_of_measurement'] == \ DEFAULT_UNIT_OF_MEASUREMENT # Update (replaces 1 device). @@ -60,4 +63,4 @@ class TestDemoPlatform(unittest.TestCase): # the same, but the lists are different. all_states_updated = self.hass.states.all() assert len(all_states_updated) == NUMBER_OF_DEMO_DEVICES - self.assertNotEqual(all_states, all_states_updated) + assert all_states != all_states_updated diff --git a/tests/components/geo_location/test_geo_json_events.py b/tests/components/geo_location/test_geo_json_events.py index 00fc9f8c996..f476598adc9 100644 --- a/tests/components/geo_location/test_geo_json_events.py +++ b/tests/components/geo_location/test_geo_json_events.py @@ -70,8 +70,7 @@ class TestGeoJsonPlatform(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, geo_location.DOMAIN): - self.assertTrue(setup_component(self.hass, geo_location.DOMAIN, - CONFIG)) + assert setup_component(self.hass, geo_location.DOMAIN, CONFIG) # Artificially trigger update. self.hass.bus.fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -81,34 +80,34 @@ class TestGeoJsonPlatform(unittest.TestCase): assert len(all_states) == 3 state = self.hass.states.get("geo_location.title_1") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 1" assert state.attributes == { ATTR_EXTERNAL_ID: "1234", ATTR_LATITUDE: -31.0, ATTR_LONGITUDE: 150.0, ATTR_FRIENDLY_NAME: "Title 1", ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'geo_json_events'} - self.assertAlmostEqual(float(state.state), 15.5) + assert round(abs(float(state.state)-15.5), 7) == 0 state = self.hass.states.get("geo_location.title_2") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 2" assert state.attributes == { ATTR_EXTERNAL_ID: "2345", ATTR_LATITUDE: -31.1, ATTR_LONGITUDE: 150.1, ATTR_FRIENDLY_NAME: "Title 2", ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'geo_json_events'} - self.assertAlmostEqual(float(state.state), 20.5) + assert round(abs(float(state.state)-20.5), 7) == 0 state = self.hass.states.get("geo_location.title_3") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 3" assert state.attributes == { ATTR_EXTERNAL_ID: "3456", ATTR_LATITUDE: -31.2, ATTR_LONGITUDE: 150.2, ATTR_FRIENDLY_NAME: "Title 3", ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'geo_json_events'} - self.assertAlmostEqual(float(state.state), 25.5) + assert round(abs(float(state.state)-25.5), 7) == 0 # Simulate an update - one existing, one new entry, # one outdated entry @@ -162,8 +161,7 @@ class TestGeoJsonPlatform(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, geo_location.DOMAIN): - self.assertTrue(setup_component(self.hass, geo_location.DOMAIN, - CONFIG)) + assert setup_component(self.hass, geo_location.DOMAIN, CONFIG) # This gives us the ability to assert the '_delete_callback' # has been called while still executing it. diff --git a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py b/tests/components/geo_location/test_nsw_rural_fire_service_feed.py index 92c4bae1931..665f6017907 100644 --- a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py +++ b/tests/components/geo_location/test_nsw_rural_fire_service_feed.py @@ -93,8 +93,7 @@ class TestGeoJsonPlatform(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, geo_location.DOMAIN): - self.assertTrue(setup_component(self.hass, geo_location.DOMAIN, - CONFIG)) + assert setup_component(self.hass, geo_location.DOMAIN, CONFIG) # Artificially trigger update. self.hass.bus.fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -104,7 +103,7 @@ class TestGeoJsonPlatform(unittest.TestCase): assert len(all_states) == 3 state = self.hass.states.get("geo_location.title_1") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 1" assert state.attributes == { ATTR_EXTERNAL_ID: "1234", ATTR_LATITUDE: -31.0, @@ -120,10 +119,10 @@ class TestGeoJsonPlatform(unittest.TestCase): ATTR_SIZE: 'Size 1', ATTR_RESPONSIBLE_AGENCY: 'Agency 1', ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'nsw_rural_fire_service_feed'} - self.assertAlmostEqual(float(state.state), 15.5) + assert round(abs(float(state.state)-15.5), 7) == 0 state = self.hass.states.get("geo_location.title_2") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 2" assert state.attributes == { ATTR_EXTERNAL_ID: "2345", ATTR_LATITUDE: -31.1, @@ -131,10 +130,10 @@ class TestGeoJsonPlatform(unittest.TestCase): ATTR_FIRE: False, ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'nsw_rural_fire_service_feed'} - self.assertAlmostEqual(float(state.state), 20.5) + assert round(abs(float(state.state)-20.5), 7) == 0 state = self.hass.states.get("geo_location.title_3") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Title 3" assert state.attributes == { ATTR_EXTERNAL_ID: "3456", ATTR_LATITUDE: -31.2, @@ -142,7 +141,7 @@ class TestGeoJsonPlatform(unittest.TestCase): ATTR_FIRE: True, ATTR_UNIT_OF_MEASUREMENT: "km", ATTR_SOURCE: 'nsw_rural_fire_service_feed'} - self.assertAlmostEqual(float(state.state), 25.5) + assert round(abs(float(state.state)-25.5), 7) == 0 # Simulate an update - one existing, one new entry, # one outdated entry diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 104d1427dc9..65558aeb25d 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -36,10 +36,9 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'person_and_light', ['light.Bowl', 'device_tracker.Paulus']) - self.assertEqual( - STATE_ON, + assert STATE_ON == \ self.hass.states.get( - group.ENTITY_ID_FORMAT.format('person_and_light')).state) + group.ENTITY_ID_FORMAT.format('person_and_light')).state def test_setup_group_with_a_non_existing_state(self): """Try to set up a group with a non existing state.""" @@ -49,7 +48,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'light_and_nothing', ['light.Bowl', 'non.existing']) - self.assertEqual(STATE_ON, grp.state) + assert STATE_ON == grp.state def test_setup_group_with_non_groupable_states(self): """Test setup with groups which are not groupable.""" @@ -60,13 +59,13 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'chromecasts', ['cast.living_room', 'cast.bedroom']) - self.assertEqual(STATE_UNKNOWN, grp.state) + assert STATE_UNKNOWN == grp.state def test_setup_empty_group(self): """Try to set up an empty group.""" grp = group.Group.create_group(self.hass, 'nothing', []) - self.assertEqual(STATE_UNKNOWN, grp.state) + assert STATE_UNKNOWN == grp.state def test_monitor_group(self): """Test if the group keeps track of states.""" @@ -76,11 +75,11 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) # Test if group setup in our init mode is ok - self.assertIn(test_group.entity_id, self.hass.states.entity_ids()) + assert test_group.entity_id in self.hass.states.entity_ids() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_ON, group_state.state) - self.assertTrue(group_state.attributes.get(group.ATTR_AUTO)) + assert STATE_ON == group_state.state + assert group_state.attributes.get(group.ATTR_AUTO) def test_group_turns_off_if_all_off(self): """Test if turn off if the last device that was on turns off.""" @@ -92,7 +91,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_OFF, group_state.state) + assert STATE_OFF == group_state.state def test_group_turns_on_if_all_are_off_and_one_turns_on(self): """Test if turn on if all devices were turned off and one turns on.""" @@ -106,7 +105,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_ON, group_state.state) + assert STATE_ON == group_state.state def test_allgroup_stays_off_if_all_are_off_and_one_turns_on(self): """Group with all: true, stay off if one device turns on.""" @@ -121,7 +120,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_OFF, group_state.state) + assert STATE_OFF == group_state.state def test_allgroup_turn_on_if_last_turns_on(self): """Group with all: true, turn on if all devices are on.""" @@ -136,7 +135,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_ON, group_state.state) + assert STATE_ON == group_state.state def test_is_on(self): """Test is_on method.""" @@ -145,13 +144,13 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - self.assertTrue(group.is_on(self.hass, test_group.entity_id)) + assert group.is_on(self.hass, test_group.entity_id) self.hass.states.set('light.Bowl', STATE_OFF) self.hass.block_till_done() - self.assertFalse(group.is_on(self.hass, test_group.entity_id)) + assert not group.is_on(self.hass, test_group.entity_id) # Try on non existing state - self.assertFalse(group.is_on(self.hass, 'non.existing')) + assert not group.is_on(self.hass, 'non.existing') def test_expand_entity_ids(self): """Test expand_entity_ids method.""" @@ -160,9 +159,9 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - self.assertEqual(sorted(['light.ceiling', 'light.bowl']), - sorted(group.expand_entity_ids( - self.hass, [test_group.entity_id]))) + assert sorted(['light.ceiling', 'light.bowl']) == \ + sorted(group.expand_entity_ids( + self.hass, [test_group.entity_id])) def test_expand_entity_ids_does_not_return_duplicates(self): """Test that expand_entity_ids does not return duplicates.""" @@ -171,15 +170,13 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - self.assertEqual( - ['light.bowl', 'light.ceiling'], + assert ['light.bowl', 'light.ceiling'] == \ sorted(group.expand_entity_ids( - self.hass, [test_group.entity_id, 'light.Ceiling']))) + self.hass, [test_group.entity_id, 'light.Ceiling'])) - self.assertEqual( - ['light.bowl', 'light.ceiling'], + assert ['light.bowl', 'light.ceiling'] == \ sorted(group.expand_entity_ids( - self.hass, ['light.bowl', test_group.entity_id]))) + self.hass, ['light.bowl', test_group.entity_id])) def test_expand_entity_ids_recursive(self): """Test expand_entity_ids method with a group that contains itself.""" @@ -191,13 +188,13 @@ class TestComponentsGroup(unittest.TestCase): ['light.Bowl', 'light.Ceiling', 'group.init_group'], False) - self.assertEqual(sorted(['light.ceiling', 'light.bowl']), - sorted(group.expand_entity_ids( - self.hass, [test_group.entity_id]))) + assert sorted(['light.ceiling', 'light.bowl']) == \ + sorted(group.expand_entity_ids( + self.hass, [test_group.entity_id])) def test_expand_entity_ids_ignores_non_strings(self): """Test that non string elements in lists are ignored.""" - self.assertEqual([], group.expand_entity_ids(self.hass, [5, True])) + assert [] == group.expand_entity_ids(self.hass, [5, True]) def test_get_entity_ids(self): """Test get_entity_ids method.""" @@ -206,9 +203,8 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group.create_group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - self.assertEqual( - ['light.bowl', 'light.ceiling'], - sorted(group.get_entity_ids(self.hass, test_group.entity_id))) + assert ['light.bowl', 'light.ceiling'] == \ + sorted(group.get_entity_ids(self.hass, test_group.entity_id)) def test_get_entity_ids_with_domain_filter(self): """Test if get_entity_ids works with a domain_filter.""" @@ -217,18 +213,17 @@ class TestComponentsGroup(unittest.TestCase): mixed_group = group.Group.create_group( self.hass, 'mixed_group', ['light.Bowl', 'switch.AC'], False) - self.assertEqual( - ['switch.ac'], + assert ['switch.ac'] == \ group.get_entity_ids( - self.hass, mixed_group.entity_id, domain_filter="switch")) + self.hass, mixed_group.entity_id, domain_filter="switch") def test_get_entity_ids_with_non_existing_group_name(self): """Test get_entity_ids with a non existing group.""" - self.assertEqual([], group.get_entity_ids(self.hass, 'non_existing')) + assert [] == group.get_entity_ids(self.hass, 'non_existing') def test_get_entity_ids_with_non_group_state(self): """Test get_entity_ids with a non group state.""" - self.assertEqual([], group.get_entity_ids(self.hass, 'switch.AC')) + assert [] == group.get_entity_ids(self.hass, 'switch.AC') def test_group_being_init_before_first_tracked_state_is_set_to_on(self): """Test if the groups turn on. @@ -244,7 +239,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_ON, group_state.state) + assert STATE_ON == group_state.state def test_group_being_init_before_first_tracked_state_is_set_to_off(self): """Test if the group turns off. @@ -260,7 +255,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) - self.assertEqual(STATE_OFF, group_state.state) + assert STATE_OFF == group_state.state def test_setup(self): """Test setup method.""" @@ -283,36 +278,36 @@ class TestComponentsGroup(unittest.TestCase): group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) - self.assertEqual(STATE_ON, group_state.state) - self.assertEqual(set((test_group.entity_id, 'light.bowl')), - set(group_state.attributes['entity_id'])) - self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) - self.assertEqual('mdi:work', - group_state.attributes.get(ATTR_ICON)) - self.assertTrue(group_state.attributes.get(group.ATTR_VIEW)) - self.assertEqual('hidden', - group_state.attributes.get(group.ATTR_CONTROL)) - self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) - self.assertEqual(1, group_state.attributes.get(group.ATTR_ORDER)) + assert STATE_ON == group_state.state + assert set((test_group.entity_id, 'light.bowl')) == \ + set(group_state.attributes['entity_id']) + assert group_state.attributes.get(group.ATTR_AUTO) is None + assert 'mdi:work' == \ + group_state.attributes.get(ATTR_ICON) + assert group_state.attributes.get(group.ATTR_VIEW) + assert 'hidden' == \ + group_state.attributes.get(group.ATTR_CONTROL) + assert group_state.attributes.get(ATTR_HIDDEN) + assert 1 == group_state.attributes.get(group.ATTR_ORDER) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('test_group')) - self.assertEqual(STATE_UNKNOWN, group_state.state) - self.assertEqual(set(('sensor.happy', 'hello.world')), - set(group_state.attributes['entity_id'])) - self.assertIsNone(group_state.attributes.get(group.ATTR_AUTO)) - self.assertIsNone(group_state.attributes.get(ATTR_ICON)) - self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW)) - self.assertIsNone(group_state.attributes.get(group.ATTR_CONTROL)) - self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN)) - self.assertEqual(2, group_state.attributes.get(group.ATTR_ORDER)) + assert STATE_UNKNOWN == group_state.state + assert set(('sensor.happy', 'hello.world')) == \ + set(group_state.attributes['entity_id']) + assert group_state.attributes.get(group.ATTR_AUTO) is None + assert group_state.attributes.get(ATTR_ICON) is None + assert group_state.attributes.get(group.ATTR_VIEW) is None + assert group_state.attributes.get(group.ATTR_CONTROL) is None + assert group_state.attributes.get(ATTR_HIDDEN) is None + assert 2 == group_state.attributes.get(group.ATTR_ORDER) def test_groups_get_unique_names(self): """Two groups with same name should both have a unique entity id.""" grp1 = group.Group.create_group(self.hass, 'Je suis Charlie') grp2 = group.Group.create_group(self.hass, 'Je suis Charlie') - self.assertNotEqual(grp1.entity_id, grp2.entity_id) + assert grp1.entity_id != grp2.entity_id def test_expand_entity_ids_expands_nested_groups(self): """Test if entity ids epands to nested groups.""" @@ -323,10 +318,10 @@ class TestComponentsGroup(unittest.TestCase): group.Group.create_group( self.hass, 'group_of_groups', ['group.light', 'group.switch']) - self.assertEqual( - ['light.test_1', 'light.test_2', 'switch.test_1', 'switch.test_2'], + assert ['light.test_1', 'light.test_2', + 'switch.test_1', 'switch.test_2'] == \ sorted(group.expand_entity_ids(self.hass, - ['group.group_of_groups']))) + ['group.group_of_groups'])) def test_set_assumed_state_based_on_tracked(self): """Test assumed state.""" @@ -337,7 +332,7 @@ class TestComponentsGroup(unittest.TestCase): ['light.Bowl', 'light.Ceiling', 'sensor.no_exist']) state = self.hass.states.get(test_group.entity_id) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert not state.attributes.get(ATTR_ASSUMED_STATE) self.hass.states.set('light.Bowl', STATE_ON, { ATTR_ASSUMED_STATE: True @@ -345,13 +340,13 @@ class TestComponentsGroup(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(test_group.entity_id) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert state.attributes.get(ATTR_ASSUMED_STATE) self.hass.states.set('light.Bowl', STATE_ON) self.hass.block_till_done() state = self.hass.states.get(test_group.entity_id) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert not state.attributes.get(ATTR_ASSUMED_STATE) def test_group_updated_after_device_tracker_zone_change(self): """Test group state when device tracker in group changes zone.""" @@ -363,9 +358,9 @@ class TestComponentsGroup(unittest.TestCase): ['device_tracker.Adam', 'device_tracker.Eve']) self.hass.states.set('device_tracker.Adam', 'cool_state_not_home') self.hass.block_till_done() - self.assertEqual(STATE_NOT_HOME, - self.hass.states.get( - group.ENTITY_ID_FORMAT.format('peeps')).state) + assert STATE_NOT_HOME == \ + self.hass.states.get( + group.ENTITY_ID_FORMAT.format('peeps')).state def test_reloading_groups(self): """Test reloading the group config.""" @@ -419,13 +414,13 @@ class TestComponentsGroup(unittest.TestCase): common.set_visibility(self.hass, group_entity_id, False) self.hass.block_till_done() group_state = self.hass.states.get(group_entity_id) - self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) + assert group_state.attributes.get(ATTR_HIDDEN) # Show it again common.set_visibility(self.hass, group_entity_id, True) self.hass.block_till_done() group_state = self.hass.states.get(group_entity_id) - self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN)) + assert group_state.attributes.get(ATTR_HIDDEN) is None def test_modify_group(self): """Test modifying a group.""" diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 6253de8cbae..a04fb853996 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -40,16 +40,16 @@ class TestLight(unittest.TestCase): """Test if methods call the services as expected.""" # Test is_on self.hass.states.set('light.test', STATE_ON) - self.assertTrue(light.is_on(self.hass, 'light.test')) + assert light.is_on(self.hass, 'light.test') self.hass.states.set('light.test', STATE_OFF) - self.assertFalse(light.is_on(self.hass, 'light.test')) + assert not light.is_on(self.hass, 'light.test') self.hass.states.set(light.ENTITY_ID_ALL_LIGHTS, STATE_ON) - self.assertTrue(light.is_on(self.hass)) + assert light.is_on(self.hass) self.hass.states.set(light.ENTITY_ID_ALL_LIGHTS, STATE_OFF) - self.assertFalse(light.is_on(self.hass)) + assert not light.is_on(self.hass) # Test turn_on turn_on_calls = mock_service( @@ -68,22 +68,19 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(turn_on_calls)) + assert 1 == len(turn_on_calls) call = turn_on_calls[-1] - self.assertEqual(light.DOMAIN, call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual('entity_id_val', call.data.get(ATTR_ENTITY_ID)) - self.assertEqual( - 'transition_val', call.data.get(light.ATTR_TRANSITION)) - self.assertEqual( - 'brightness_val', call.data.get(light.ATTR_BRIGHTNESS)) - self.assertEqual('rgb_color_val', call.data.get(light.ATTR_RGB_COLOR)) - self.assertEqual('xy_color_val', call.data.get(light.ATTR_XY_COLOR)) - self.assertEqual('profile_val', call.data.get(light.ATTR_PROFILE)) - self.assertEqual( - 'color_name_val', call.data.get(light.ATTR_COLOR_NAME)) - self.assertEqual('white_val', call.data.get(light.ATTR_WHITE_VALUE)) + assert light.DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert 'entity_id_val' == call.data.get(ATTR_ENTITY_ID) + assert 'transition_val' == call.data.get(light.ATTR_TRANSITION) + assert 'brightness_val' == call.data.get(light.ATTR_BRIGHTNESS) + assert 'rgb_color_val' == call.data.get(light.ATTR_RGB_COLOR) + assert 'xy_color_val' == call.data.get(light.ATTR_XY_COLOR) + assert 'profile_val' == call.data.get(light.ATTR_PROFILE) + assert 'color_name_val' == call.data.get(light.ATTR_COLOR_NAME) + assert 'white_val' == call.data.get(light.ATTR_WHITE_VALUE) # Test turn_off turn_off_calls = mock_service( @@ -94,13 +91,13 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(turn_off_calls)) + assert 1 == len(turn_off_calls) call = turn_off_calls[-1] - self.assertEqual(light.DOMAIN, call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) - self.assertEqual('transition_val', call.data[light.ATTR_TRANSITION]) + assert light.DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert 'entity_id_val' == call.data[ATTR_ENTITY_ID] + assert 'transition_val' == call.data[light.ATTR_TRANSITION] # Test toggle toggle_calls = mock_service( @@ -111,29 +108,28 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(toggle_calls)) + assert 1 == len(toggle_calls) call = toggle_calls[-1] - self.assertEqual(light.DOMAIN, call.domain) - self.assertEqual(SERVICE_TOGGLE, call.service) - self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) - self.assertEqual('transition_val', call.data[light.ATTR_TRANSITION]) + assert light.DOMAIN == call.domain + assert SERVICE_TOGGLE == call.service + assert 'entity_id_val' == call.data[ATTR_ENTITY_ID] + assert 'transition_val' == call.data[light.ATTR_TRANSITION] def test_services(self): """Test the provided services.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1, dev2, dev3 = platform.DEVICES # Test init - self.assertTrue(light.is_on(self.hass, dev1.entity_id)) - self.assertFalse(light.is_on(self.hass, dev2.entity_id)) - self.assertFalse(light.is_on(self.hass, dev3.entity_id)) + assert light.is_on(self.hass, dev1.entity_id) + assert not light.is_on(self.hass, dev2.entity_id) + assert not light.is_on(self.hass, dev3.entity_id) # Test basic turn_on, turn_off, toggle services common.turn_off(self.hass, entity_id=dev1.entity_id) @@ -141,44 +137,44 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() - self.assertFalse(light.is_on(self.hass, dev1.entity_id)) - self.assertTrue(light.is_on(self.hass, dev2.entity_id)) + assert not light.is_on(self.hass, dev1.entity_id) + assert light.is_on(self.hass, dev2.entity_id) # turn on all lights common.turn_on(self.hass) self.hass.block_till_done() - self.assertTrue(light.is_on(self.hass, dev1.entity_id)) - self.assertTrue(light.is_on(self.hass, dev2.entity_id)) - self.assertTrue(light.is_on(self.hass, dev3.entity_id)) + assert light.is_on(self.hass, dev1.entity_id) + assert light.is_on(self.hass, dev2.entity_id) + assert light.is_on(self.hass, dev3.entity_id) # turn off all lights common.turn_off(self.hass) self.hass.block_till_done() - self.assertFalse(light.is_on(self.hass, dev1.entity_id)) - self.assertFalse(light.is_on(self.hass, dev2.entity_id)) - self.assertFalse(light.is_on(self.hass, dev3.entity_id)) + assert not light.is_on(self.hass, dev1.entity_id) + assert not light.is_on(self.hass, dev2.entity_id) + assert not light.is_on(self.hass, dev3.entity_id) # toggle all lights common.toggle(self.hass) self.hass.block_till_done() - self.assertTrue(light.is_on(self.hass, dev1.entity_id)) - self.assertTrue(light.is_on(self.hass, dev2.entity_id)) - self.assertTrue(light.is_on(self.hass, dev3.entity_id)) + assert light.is_on(self.hass, dev1.entity_id) + assert light.is_on(self.hass, dev2.entity_id) + assert light.is_on(self.hass, dev3.entity_id) # toggle all lights common.toggle(self.hass) self.hass.block_till_done() - self.assertFalse(light.is_on(self.hass, dev1.entity_id)) - self.assertFalse(light.is_on(self.hass, dev2.entity_id)) - self.assertFalse(light.is_on(self.hass, dev3.entity_id)) + assert not light.is_on(self.hass, dev1.entity_id) + assert not light.is_on(self.hass, dev2.entity_id) + assert not light.is_on(self.hass, dev3.entity_id) # Ensure all attributes process correctly common.turn_on(self.hass, dev1.entity_id, @@ -191,22 +187,22 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() _, data = dev1.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_TRANSITION: 10, light.ATTR_BRIGHTNESS: 20, light.ATTR_HS_COLOR: (240, 100), - }, data) + } == data _, data = dev2.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_HS_COLOR: (0, 0), light.ATTR_WHITE_VALUE: 255, - }, data) + } == data _, data = dev3.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_HS_COLOR: (71.059, 100), - }, data) + } == data # One of the light profiles prof_name, prof_h, prof_s, prof_bri = 'relax', 35.932, 69.412, 144 @@ -221,16 +217,16 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() _, data = dev1.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_BRIGHTNESS: prof_bri, light.ATTR_HS_COLOR: (prof_h, prof_s), - }, data) + } == data _, data = dev2.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_BRIGHTNESS: 100, light.ATTR_HS_COLOR: (prof_h, prof_s), - }, data) + } == data # Test bad data common.turn_on(self.hass) @@ -241,13 +237,13 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() _, data = dev1.last_call('turn_on') - self.assertEqual({}, data) + assert {} == data _, data = dev2.last_call('turn_on') - self.assertEqual({}, data) + assert {} == data _, data = dev3.last_call('turn_on') - self.assertEqual({}, data) + assert {} == data # faulty attributes will not trigger a service call common.turn_on( @@ -263,10 +259,10 @@ class TestLight(unittest.TestCase): self.hass.block_till_done() _, data = dev1.last_call('turn_on') - self.assertEqual({}, data) + assert {} == data _, data = dev2.last_call('turn_on') - self.assertEqual({}, data) + assert {} == data def test_broken_light_profiles(self): """Test light profiles.""" @@ -280,8 +276,8 @@ class TestLight(unittest.TestCase): user_file.write('id,x,y,brightness\n') user_file.write('I,WILL,NOT,WORK\n') - self.assertFalse(setup_component( - self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert not setup_component( + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}}) def test_light_profiles(self): """Test light profiles.""" @@ -294,9 +290,9 @@ class TestLight(unittest.TestCase): user_file.write('id,x,y,brightness\n') user_file.write('test,.4,.6,100\n') - self.assertTrue(setup_component( + assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} - )) + ) dev1, _, _ = platform.DEVICES @@ -306,10 +302,10 @@ class TestLight(unittest.TestCase): _, data = dev1.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_HS_COLOR: (71.059, 100), light.ATTR_BRIGHTNESS: 100 - }, data) + } == data def test_default_profiles_group(self): """Test default turn-on light profile for all lights.""" @@ -335,19 +331,19 @@ class TestLight(unittest.TestCase): with mock.patch('os.path.isfile', side_effect=_mock_isfile): with mock.patch('builtins.open', side_effect=_mock_open): with mock_storage(): - self.assertTrue(setup_component( + assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} - )) + ) dev, _, _ = platform.DEVICES common.turn_on(self.hass, dev.entity_id) self.hass.block_till_done() _, data = dev.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_HS_COLOR: (71.059, 100), light.ATTR_BRIGHTNESS: 99 - }, data) + } == data def test_default_profiles_light(self): """Test default turn-on light profile for a specific light.""" @@ -374,20 +370,20 @@ class TestLight(unittest.TestCase): with mock.patch('os.path.isfile', side_effect=_mock_isfile): with mock.patch('builtins.open', side_effect=_mock_open): with mock_storage(): - self.assertTrue(setup_component( + assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} - )) + ) dev = next(filter(lambda x: x.entity_id == 'light.ceiling_2', platform.DEVICES)) common.turn_on(self.hass, dev.entity_id) self.hass.block_till_done() _, data = dev.last_call('turn_on') - self.assertEqual({ + assert { light.ATTR_HS_COLOR: (50.353, 100), light.ATTR_BRIGHTNESS: 100 - }, data) + } == data async def test_intent_set_color(hass): diff --git a/tests/components/light/test_mochad.py b/tests/components/light/test_mochad.py index fa122777ca4..d96bf8f5abb 100644 --- a/tests/components/light/test_mochad.py +++ b/tests/components/light/test_mochad.py @@ -50,7 +50,7 @@ class TestMochadSwitchSetup(unittest.TestCase): ], } } - self.assertTrue(setup_component(self.hass, light.DOMAIN, good_config)) + assert setup_component(self.hass, light.DOMAIN, good_config) class TestMochadLight(unittest.TestCase): @@ -71,7 +71,7 @@ class TestMochadLight(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual('fake_light', self.light.name) + assert 'fake_light' == self.light.name def test_turn_on_with_no_brightness(self): """Test turn_on.""" diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 5a768820e18..84c863d8621 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -193,7 +193,7 @@ class TestLightMQTT(unittest.TestCase): 'name': 'test', } }) - self.assertIsNone(self.hass.states.get('light.test')) + assert self.hass.states.get('light.test') is None def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(self): """Test if there is no color and brightness if no topic.""" @@ -208,25 +208,25 @@ class TestLightMQTT(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('hs_color')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('hs_color')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None def test_controlling_state_via_topic(self): """Test the controlling of the state via topic.""" @@ -258,34 +258,34 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('hs_color')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) - self.assertEqual(255, state.attributes.get('brightness')) - self.assertEqual(150, state.attributes.get('color_temp')) - self.assertEqual('none', state.attributes.get('effect')) - self.assertEqual((0, 0), state.attributes.get('hs_color')) - self.assertEqual(255, state.attributes.get('white_value')) - self.assertEqual((0.323, 0.329), state.attributes.get('xy_color')) + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 150 == state.attributes.get('color_temp') + assert 'none' == state.attributes.get('effect') + assert (0, 0) == state.attributes.get('hs_color') + assert 255 == state.attributes.get('white_value') + assert (0.323, 0.329) == state.attributes.get('xy_color') fire_mqtt_message(self.hass, 'test_light_rgb/status', '0') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') self.hass.block_till_done() @@ -295,20 +295,20 @@ class TestLightMQTT(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(100, - light_state.attributes['brightness']) + assert 100 == \ + light_state.attributes['brightness'] fire_mqtt_message(self.hass, 'test_light_rgb/color_temp/status', '300') self.hass.block_till_done() light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(300, light_state.attributes['color_temp']) + assert 300 == light_state.attributes['color_temp'] fire_mqtt_message(self.hass, 'test_light_rgb/effect/status', 'rainbow') self.hass.block_till_done() light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual('rainbow', light_state.attributes['effect']) + assert 'rainbow' == light_state.attributes['effect'] fire_mqtt_message(self.hass, 'test_light_rgb/white_value/status', '100') @@ -316,8 +316,8 @@ class TestLightMQTT(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(100, - light_state.attributes['white_value']) + assert 100 == \ + light_state.attributes['white_value'] fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') self.hass.block_till_done() @@ -327,24 +327,24 @@ class TestLightMQTT(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((255, 255, 255), - light_state.attributes.get('rgb_color')) + assert (255, 255, 255) == \ + light_state.attributes.get('rgb_color') fire_mqtt_message(self.hass, 'test_light_rgb/hs/status', '200,50') self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((200, 50), - light_state.attributes.get('hs_color')) + assert (200, 50) == \ + light_state.attributes.get('hs_color') fire_mqtt_message(self.hass, 'test_light_rgb/xy/status', '0.675,0.322') self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((0.672, 0.324), - light_state.attributes.get('xy_color')) + assert (0.672, 0.324) == \ + light_state.attributes.get('xy_color') def test_brightness_controlling_scale(self): """Test the brightness controlling scale.""" @@ -365,22 +365,22 @@ class TestLightMQTT(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'test_scale/status', 'on') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') fire_mqtt_message(self.hass, 'test_scale/status', 'off') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'test_scale/status', 'on') self.hass.block_till_done() @@ -390,8 +390,8 @@ class TestLightMQTT(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(255, - light_state.attributes['brightness']) + assert 255 == \ + light_state.attributes['brightness'] def test_brightness_from_rgb_controlling_scale(self): """Test the brightness controlling scale.""" @@ -411,22 +411,22 @@ class TestLightMQTT(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'test_scale_rgb/status', 'on') fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '255,0,0') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(255, state.attributes.get('brightness')) + assert 255 == state.attributes.get('brightness') fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '127,0,0') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(127, state.attributes.get('brightness')) + assert 127 == state.attributes.get('brightness') def test_white_value_controlling_scale(self): """Test the white_value controlling scale.""" @@ -447,22 +447,22 @@ class TestLightMQTT(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'test_scale/status', 'on') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('white_value')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') fire_mqtt_message(self.hass, 'test_scale/status', 'off') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'test_scale/status', 'on') self.hass.block_till_done() @@ -472,8 +472,8 @@ class TestLightMQTT(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(255, - light_state.attributes['white_value']) + assert 255 == \ + light_state.attributes['white_value'] def test_controlling_state_via_topic_with_templates(self): """Test the setting of the state with a template.""" @@ -510,9 +510,9 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('rgb_color')) + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert state.attributes.get('rgb_color') is None fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '{"hello": [1, 2, 3]}') @@ -529,26 +529,26 @@ class TestLightMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(50, state.attributes.get('brightness')) - self.assertEqual((84, 169, 255), state.attributes.get('rgb_color')) - self.assertEqual(300, state.attributes.get('color_temp')) - self.assertEqual('rainbow', state.attributes.get('effect')) - self.assertEqual(75, state.attributes.get('white_value')) + assert STATE_ON == state.state + assert 50 == state.attributes.get('brightness') + assert (84, 169, 255) == state.attributes.get('rgb_color') + assert 300 == state.attributes.get('color_temp') + assert 'rainbow' == state.attributes.get('effect') + assert 75 == state.attributes.get('white_value') fire_mqtt_message(self.hass, 'test_light_rgb/hs/status', '{"hello": [100,50]}') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual((100, 50), state.attributes.get('hs_color')) + assert (100, 50) == state.attributes.get('hs_color') fire_mqtt_message(self.hass, 'test_light_rgb/xy/status', '{"hello": [0.123,0.123]}') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual((0.14, 0.131), state.attributes.get('xy_color')) + assert (0.14, 0.131) == state.attributes.get('xy_color') def test_sending_mqtt_commands_and_optimistic(self): """Test the sending of command in optimistic mode.""" @@ -579,13 +579,13 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(95, state.attributes.get('brightness')) - self.assertEqual((100, 100), state.attributes.get('hs_color')) - self.assertEqual('random', state.attributes.get('effect')) - self.assertEqual(100, state.attributes.get('color_temp')) - self.assertEqual(50, state.attributes.get('white_value')) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert state.attributes.get(ATTR_ASSUMED_STATE) common.turn_on(self.hass, 'light.test') self.hass.block_till_done() @@ -594,7 +594,7 @@ class TestLightMQTT(unittest.TestCase): 'test_light_rgb/set', 'on', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'light.test') self.hass.block_till_done() @@ -603,7 +603,7 @@ class TestLightMQTT(unittest.TestCase): 'test_light_rgb/set', 'off', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state self.mock_publish.reset_mock() common.turn_on(self.hass, 'light.test', @@ -624,12 +624,12 @@ class TestLightMQTT(unittest.TestCase): ], any_order=True) state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 128, 0), state.attributes['rgb_color']) - self.assertEqual(50, state.attributes['brightness']) - self.assertEqual((30.118, 100), state.attributes['hs_color']) - self.assertEqual(80, state.attributes['white_value']) - self.assertEqual((0.611, 0.375), state.attributes['xy_color']) + assert STATE_ON == state.state + assert (255, 128, 0) == state.attributes['rgb_color'] + assert 50 == state.attributes['brightness'] + assert (30.118, 100) == state.attributes['hs_color'] + assert 80 == state.attributes['white_value'] + assert (0.611, 0.375) == state.attributes['xy_color'] def test_sending_mqtt_rgb_command_with_template(self): """Test the sending of RGB command with template.""" @@ -649,7 +649,7 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 64]) self.hass.block_till_done() @@ -660,8 +660,8 @@ class TestLightMQTT(unittest.TestCase): ], any_order=True) state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 128, 63), state.attributes['rgb_color']) + assert STATE_ON == state.state + assert (255, 128, 63) == state.attributes['rgb_color'] def test_show_brightness_if_only_command_topic(self): """Test the brightness if only a command topic is present.""" @@ -677,15 +677,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') def test_show_color_temp_only_if_command_topic(self): """Test the color temp only if a command topic is present.""" @@ -701,15 +701,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('color_temp')) + assert STATE_OFF == state.state + assert state.attributes.get('color_temp') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(150, state.attributes.get('color_temp')) + assert STATE_ON == state.state + assert 150 == state.attributes.get('color_temp') def test_show_effect_only_if_command_topic(self): """Test the color temp only if a command topic is present.""" @@ -725,15 +725,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('effect')) + assert STATE_OFF == state.state + assert state.attributes.get('effect') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual('none', state.attributes.get('effect')) + assert STATE_ON == state.state + assert 'none' == state.attributes.get('effect') def test_show_hs_if_only_command_topic(self): """Test the hs if only a command topic is present.""" @@ -749,15 +749,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('hs_color')) + assert STATE_OFF == state.state + assert state.attributes.get('hs_color') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((0, 0), state.attributes.get('hs_color')) + assert STATE_ON == state.state + assert (0, 0) == state.attributes.get('hs_color') def test_show_white_value_if_only_command_topic(self): """Test the white_value if only a command topic is present.""" @@ -773,15 +773,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('white_value')) + assert STATE_OFF == state.state + assert state.attributes.get('white_value') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('white_value')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') def test_show_xy_if_only_command_topic(self): """Test the xy if only a command topic is present.""" @@ -797,15 +797,15 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('xy_color')) + assert STATE_OFF == state.state + assert state.attributes.get('xy_color') is None fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((0.323, 0.329), state.attributes.get('xy_color')) + assert STATE_ON == state.state + assert (0.323, 0.329) == state.attributes.get('xy_color') def test_on_command_first(self): """Test on command being sent before brightness.""" @@ -821,7 +821,7 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'light.test', brightness=50) self.hass.block_till_done() @@ -854,7 +854,7 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'light.test', brightness=50) self.hass.block_till_done() @@ -889,7 +889,7 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state # Turn on w/ no brightness - should set to max common.turn_on(self.hass, 'light.test') @@ -942,7 +942,7 @@ class TestLightMQTT(unittest.TestCase): assert setup_component(self.hass, light.DOMAIN, config) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'light.test', brightness=127) self.hass.block_till_done() @@ -964,7 +964,7 @@ class TestLightMQTT(unittest.TestCase): def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -973,26 +973,26 @@ class TestLightMQTT(unittest.TestCase): 'rgb_command_topic': "test_light/rgb", 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -1003,22 +1003,22 @@ class TestLightMQTT(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal_light(hass, mqtt_mock, caplog): diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 46db2f61fb3..9a8d4ce73fb 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -127,7 +127,7 @@ class TestLightMQTTJSON(unittest.TestCase): 'name': 'test', } }) - self.assertIsNone(self.hass.states.get('light.test')) + assert self.hass.states.get('light.test') is None def test_no_color_brightness_color_temp_white_val_if_no_topics(self): """Test for no RGB, brightness, color temp, effect, white val or XY.""" @@ -141,28 +141,28 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(40, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('hs_color')) + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON"}') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('hs_color')) + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None def test_controlling_state_via_topic(self): """Test the controlling of the state via topic.""" @@ -184,16 +184,16 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(191, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('hs_color')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) # Turn on the light, full white fire_mqtt_message(self.hass, 'test_light_rgb', @@ -206,21 +206,21 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) - self.assertEqual(255, state.attributes.get('brightness')) - self.assertEqual(155, state.attributes.get('color_temp')) - self.assertEqual('colorloop', state.attributes.get('effect')) - self.assertEqual(150, state.attributes.get('white_value')) - self.assertEqual((0.323, 0.329), state.attributes.get('xy_color')) - self.assertEqual((0.0, 0.0), state.attributes.get('hs_color')) + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 155 == state.attributes.get('color_temp') + assert 'colorloop' == state.attributes.get('effect') + assert 150 == state.attributes.get('white_value') + assert (0.323, 0.329) == state.attributes.get('xy_color') + assert (0.0, 0.0) == state.attributes.get('hs_color') # Turn the light off fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"OFF"}') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -229,8 +229,8 @@ class TestLightMQTTJSON(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(100, - light_state.attributes['brightness']) + assert 100 == \ + light_state.attributes['brightness'] fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -238,8 +238,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((255, 255, 255), - light_state.attributes.get('rgb_color')) + assert (255, 255, 255) == \ + light_state.attributes.get('rgb_color') fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -247,8 +247,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((0.141, 0.14), - light_state.attributes.get('xy_color')) + assert (0.141, 0.14) == \ + light_state.attributes.get('xy_color') fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -256,8 +256,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((180.0, 50.0), - light_state.attributes.get('hs_color')) + assert (180.0, 50.0) == \ + light_state.attributes.get('hs_color') fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -265,7 +265,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual(155, light_state.attributes.get('color_temp')) + assert 155 == light_state.attributes.get('color_temp') fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -273,7 +273,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual('colorloop', light_state.attributes.get('effect')) + assert 'colorloop' == light_state.attributes.get('effect') fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON",' @@ -281,7 +281,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual(155, light_state.attributes.get('white_value')) + assert 155 == light_state.attributes.get('white_value') def test_sending_mqtt_commands_and_optimistic(self): """Test the sending of command in optimistic mode.""" @@ -309,14 +309,14 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(95, state.attributes.get('brightness')) - self.assertEqual((100, 100), state.attributes.get('hs_color')) - self.assertEqual('random', state.attributes.get('effect')) - self.assertEqual(100, state.attributes.get('color_temp')) - self.assertEqual(50, state.attributes.get('white_value')) - self.assertEqual(191, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get(ATTR_ASSUMED_STATE) common.turn_on(self.hass, 'light.test') self.hass.block_till_done() @@ -325,7 +325,7 @@ class TestLightMQTTJSON(unittest.TestCase): 'test_light_rgb/set', '{"state": "ON"}', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'light.test') self.hass.block_till_done() @@ -334,61 +334,59 @@ class TestLightMQTTJSON(unittest.TestCase): 'test_light_rgb/set', '{"state": "OFF"}', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'light.test', brightness=50, color_temp=155, effect='colorloop', white_value=170) self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[0][1][0]) - self.assertEqual(2, - self.mock_publish.async_publish.mock_calls[0][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[0][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[0][1][0] + assert 2 == \ + self.mock_publish.async_publish.mock_calls[0][1][2] + assert self.mock_publish.async_publish.mock_calls[0][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[0][1][1]) - self.assertEqual(50, message_json["brightness"]) - self.assertEqual(155, message_json["color_temp"]) - self.assertEqual('colorloop', message_json["effect"]) - self.assertEqual(170, message_json["white_value"]) - self.assertEqual("ON", message_json["state"]) + assert 50 == message_json["brightness"] + assert 155 == message_json["color_temp"] + assert 'colorloop' == message_json["effect"] + assert 170 == message_json["white_value"] + assert "ON" == message_json["state"] state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(50, state.attributes['brightness']) - self.assertEqual(155, state.attributes['color_temp']) - self.assertEqual('colorloop', state.attributes['effect']) - self.assertEqual(170, state.attributes['white_value']) + assert STATE_ON == state.state + assert 50 == state.attributes['brightness'] + assert 155 == state.attributes['color_temp'] + assert 'colorloop' == state.attributes['effect'] + assert 170 == state.attributes['white_value'] # Test a color command common.turn_on(self.hass, 'light.test', brightness=50, hs_color=(125, 100)) self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[0][1][0]) - self.assertEqual(2, - self.mock_publish.async_publish.mock_calls[0][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[0][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[0][1][0] + assert 2 == \ + self.mock_publish.async_publish.mock_calls[0][1][2] + assert self.mock_publish.async_publish.mock_calls[0][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[1][1][1]) - self.assertEqual(50, message_json["brightness"]) - self.assertEqual({ + assert 50 == message_json["brightness"] + assert { 'r': 0, 'g': 255, 'b': 21, - }, message_json["color"]) - self.assertEqual("ON", message_json["state"]) + } == message_json["color"] + assert "ON" == message_json["state"] state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(50, state.attributes['brightness']) - self.assertEqual((125, 100), state.attributes['hs_color']) + assert STATE_ON == state.state + assert 50 == state.attributes['brightness'] + assert (125, 100) == state.attributes['hs_color'] def test_sending_hs_color(self): """Test light.turn_on with hs color sends hs color parameters.""" @@ -406,11 +404,11 @@ class TestLightMQTTJSON(unittest.TestCase): message_json = json.loads( self.mock_publish.async_publish.mock_calls[0][1][1]) - self.assertEqual("ON", message_json["state"]) - self.assertEqual({ + assert "ON" == message_json["state"] + assert { 'h': 180.0, 's': 50.0, - }, message_json["color"]) + } == message_json["color"] def test_flash_short_and_long(self): """Test for flash length being sent when included.""" @@ -427,39 +425,37 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(40, state.attributes.get(ATTR_SUPPORTED_FEATURES)) + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) common.turn_on(self.hass, 'light.test', flash="short") self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[0][1][0]) - self.assertEqual(0, - self.mock_publish.async_publish.mock_calls[0][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[0][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[0][1][0] + assert 0 == \ + self.mock_publish.async_publish.mock_calls[0][1][2] + assert self.mock_publish.async_publish.mock_calls[0][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[0][1][1]) - self.assertEqual(5, message_json["flash"]) - self.assertEqual("ON", message_json["state"]) + assert 5 == message_json["flash"] + assert "ON" == message_json["state"] self.mock_publish.async_publish.reset_mock() common.turn_on(self.hass, 'light.test', flash="long") self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[0][1][0]) - self.assertEqual(0, - self.mock_publish.async_publish.mock_calls[0][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[0][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[0][1][0] + assert 0 == \ + self.mock_publish.async_publish.mock_calls[0][1][2] + assert self.mock_publish.async_publish.mock_calls[0][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[0][1][1]) - self.assertEqual(15, message_json["flash"]) - self.assertEqual("ON", message_json["state"]) + assert 15 == message_json["flash"] + assert "ON" == message_json["state"] def test_transition(self): """Test for transition time being sent when included.""" @@ -474,39 +470,37 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(40, state.attributes.get(ATTR_SUPPORTED_FEATURES)) + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) common.turn_on(self.hass, 'light.test', transition=10) self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[0][1][0]) - self.assertEqual(0, - self.mock_publish.async_publish.mock_calls[0][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[0][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[0][1][0] + assert 0 == \ + self.mock_publish.async_publish.mock_calls[0][1][2] + assert self.mock_publish.async_publish.mock_calls[0][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[0][1][1]) - self.assertEqual(10, message_json["transition"]) - self.assertEqual("ON", message_json["state"]) + assert 10 == message_json["transition"] + assert "ON" == message_json["state"] # Transition back off common.turn_off(self.hass, 'light.test', transition=10) self.hass.block_till_done() - self.assertEqual('test_light_rgb/set', - self.mock_publish.async_publish.mock_calls[1][1][0]) - self.assertEqual(0, - self.mock_publish.async_publish.mock_calls[1][1][2]) - self.assertEqual(False, - self.mock_publish.async_publish.mock_calls[1][1][3]) + assert 'test_light_rgb/set' == \ + self.mock_publish.async_publish.mock_calls[1][1][0] + assert 0 == \ + self.mock_publish.async_publish.mock_calls[1][1][2] + assert self.mock_publish.async_publish.mock_calls[1][1][3] is False # Get the sent message message_json = json.loads( self.mock_publish.async_publish.mock_calls[1][1][1]) - self.assertEqual(10, message_json["transition"]) - self.assertEqual("OFF", message_json["state"]) + assert 10 == message_json["transition"] + assert "OFF" == message_json["state"] def test_brightness_scale(self): """Test for brightness scaling.""" @@ -522,9 +516,9 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) # Turn on the light fire_mqtt_message(self.hass, 'test_light_bright_scale', @@ -532,8 +526,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') # Turn on the light with brightness fire_mqtt_message(self.hass, 'test_light_bright_scale', @@ -542,8 +536,8 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') def test_invalid_color_brightness_and_white_values(self): """Test that invalid color/brightness/white values are ignored.""" @@ -561,12 +555,12 @@ class TestLightMQTTJSON(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(185, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert 185 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) # Turn on the light fire_mqtt_message(self.hass, 'test_light_rgb', @@ -577,10 +571,10 @@ class TestLightMQTTJSON(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) - self.assertEqual(255, state.attributes.get('brightness')) - self.assertEqual(255, state.attributes.get('white_value')) + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 255 == state.attributes.get('white_value') # Bad color values fire_mqtt_message(self.hass, 'test_light_rgb', @@ -590,8 +584,8 @@ class TestLightMQTTJSON(unittest.TestCase): # Color should not have changed state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') # Bad brightness values fire_mqtt_message(self.hass, 'test_light_rgb', @@ -601,8 +595,8 @@ class TestLightMQTTJSON(unittest.TestCase): # Brightness should not have changed state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') # Bad white value fire_mqtt_message(self.hass, 'test_light_rgb', @@ -612,12 +606,12 @@ class TestLightMQTTJSON(unittest.TestCase): # White value should not have changed state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('white_value')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', 'name': 'test', @@ -625,26 +619,26 @@ class TestLightMQTTJSON(unittest.TestCase): 'command_topic': 'test_light_rgb/set', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', 'name': 'test', @@ -654,22 +648,22 @@ class TestLightMQTTJSON(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal(hass, mqtt_mock, caplog): diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index 731e7cd4e45..2d702fcb2ef 100644 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -62,7 +62,7 @@ class TestLightMQTTTemplate(unittest.TestCase): 'name': 'test', } }) - self.assertIsNone(self.hass.states.get('light.test')) + assert self.hass.states.get('light.test') is None def test_state_change_via_topic(self): """Test state change via topic.""" @@ -86,22 +86,22 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'test_light_rgb', 'on') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('white_value')) + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None def test_state_brightness_color_effect_temp_white_change_via_topic(self): """Test state, bri, color, effect, color temp, white val change.""" @@ -137,13 +137,13 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) # turn on the light, full white fire_mqtt_message(self.hass, 'test_light_rgb', @@ -151,19 +151,19 @@ class TestLightMQTTTemplate(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 128, 63), state.attributes.get('rgb_color')) - self.assertEqual(255, state.attributes.get('brightness')) - self.assertEqual(145, state.attributes.get('color_temp')) - self.assertEqual(123, state.attributes.get('white_value')) - self.assertIsNone(state.attributes.get('effect')) + assert STATE_ON == state.state + assert (255, 128, 63) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 145 == state.attributes.get('color_temp') + assert 123 == state.attributes.get('white_value') + assert state.attributes.get('effect') is None # turn the light off fire_mqtt_message(self.hass, 'test_light_rgb', 'off') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state # lower the brightness fire_mqtt_message(self.hass, 'test_light_rgb', 'on,100') @@ -171,7 +171,7 @@ class TestLightMQTTTemplate(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(100, light_state.attributes['brightness']) + assert 100 == light_state.attributes['brightness'] # change the color temp fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,195') @@ -179,15 +179,15 @@ class TestLightMQTTTemplate(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(195, light_state.attributes['color_temp']) + assert 195 == light_state.attributes['color_temp'] # change the color fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,,41-42-43') self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual((243, 249, 255), - light_state.attributes.get('rgb_color')) + assert (243, 249, 255) == \ + light_state.attributes.get('rgb_color') # change the white value fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,134') @@ -195,7 +195,7 @@ class TestLightMQTTTemplate(unittest.TestCase): light_state = self.hass.states.get('light.test') self.hass.block_till_done() - self.assertEqual(134, light_state.attributes['white_value']) + assert 134 == light_state.attributes['white_value'] # change the effect fire_mqtt_message(self.hass, 'test_light_rgb', @@ -203,7 +203,7 @@ class TestLightMQTTTemplate(unittest.TestCase): self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.assertEqual('rainbow', light_state.attributes.get('effect')) + assert 'rainbow' == light_state.attributes.get('effect') def test_optimistic(self): """Test optimistic mode.""" @@ -237,13 +237,13 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(95, state.attributes.get('brightness')) - self.assertEqual((100, 100), state.attributes.get('hs_color')) - self.assertEqual('random', state.attributes.get('effect')) - self.assertEqual(100, state.attributes.get('color_temp')) - self.assertEqual(50, state.attributes.get('white_value')) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert state.attributes.get(ATTR_ASSUMED_STATE) # turn on the light common.turn_on(self.hass, 'light.test') @@ -253,7 +253,7 @@ class TestLightMQTTTemplate(unittest.TestCase): 'test_light_rgb/set', 'on,,,,--', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state # turn the light off common.turn_off(self.hass, 'light.test') @@ -263,7 +263,7 @@ class TestLightMQTTTemplate(unittest.TestCase): 'test_light_rgb/set', 'off', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state # turn on the light with brightness, color common.turn_on(self.hass, 'light.test', brightness=50, @@ -284,11 +284,11 @@ class TestLightMQTTTemplate(unittest.TestCase): # check the state state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((255, 255, 255), state.attributes['rgb_color']) - self.assertEqual(50, state.attributes['brightness']) - self.assertEqual(200, state.attributes['color_temp']) - self.assertEqual(139, state.attributes['white_value']) + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes['rgb_color'] + assert 50 == state.attributes['brightness'] + assert 200 == state.attributes['color_temp'] + assert 139 == state.attributes['white_value'] def test_flash(self): """Test flash.""" @@ -305,7 +305,7 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state # short flash common.turn_on(self.hass, 'light.test', flash='short') @@ -336,7 +336,7 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state # transition on common.turn_on(self.hass, 'light.test', transition=10) @@ -386,13 +386,13 @@ class TestLightMQTTTemplate(unittest.TestCase): }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('rgb_color')) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('color_temp')) - self.assertIsNone(state.attributes.get('effect')) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) # turn on the light, full white fire_mqtt_message(self.hass, 'test_light_rgb', @@ -400,12 +400,12 @@ class TestLightMQTTTemplate(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(255, state.attributes.get('brightness')) - self.assertEqual(215, state.attributes.get('color_temp')) - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) - self.assertEqual(222, state.attributes.get('white_value')) - self.assertEqual('rainbow', state.attributes.get('effect')) + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + assert 215 == state.attributes.get('color_temp') + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 222 == state.attributes.get('white_value') + assert 'rainbow' == state.attributes.get('effect') # bad state value fire_mqtt_message(self.hass, 'test_light_rgb', 'offf') @@ -413,7 +413,7 @@ class TestLightMQTTTemplate(unittest.TestCase): # state should not have changed state = self.hass.states.get('light.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state # bad brightness values fire_mqtt_message(self.hass, 'test_light_rgb', 'on,off,255-255-255') @@ -421,7 +421,7 @@ class TestLightMQTTTemplate(unittest.TestCase): # brightness should not have changed state = self.hass.states.get('light.test') - self.assertEqual(255, state.attributes.get('brightness')) + assert 255 == state.attributes.get('brightness') # bad color temp values fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,off,255-255-255') @@ -429,7 +429,7 @@ class TestLightMQTTTemplate(unittest.TestCase): # color temp should not have changed state = self.hass.states.get('light.test') - self.assertEqual(215, state.attributes.get('color_temp')) + assert 215 == state.attributes.get('color_temp') # bad color values fire_mqtt_message(self.hass, 'test_light_rgb', 'on,255,a-b-c') @@ -437,7 +437,7 @@ class TestLightMQTTTemplate(unittest.TestCase): # color should not have changed state = self.hass.states.get('light.test') - self.assertEqual((255, 255, 255), state.attributes.get('rgb_color')) + assert (255, 255, 255) == state.attributes.get('rgb_color') # bad white value values fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,off,255-255-255') @@ -445,7 +445,7 @@ class TestLightMQTTTemplate(unittest.TestCase): # white value should not have changed state = self.hass.states.get('light.test') - self.assertEqual(222, state.attributes.get('white_value')) + assert 222 == state.attributes.get('white_value') # bad effect value fire_mqtt_message(self.hass, 'test_light_rgb', 'on,255,a-b-c,white') @@ -453,11 +453,11 @@ class TestLightMQTTTemplate(unittest.TestCase): # effect should not have changed state = self.hass.states.get('light.test') - self.assertEqual('rainbow', state.attributes.get('effect')) + assert 'rainbow' == state.attributes.get('effect') def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_template', 'name': 'test', @@ -466,26 +466,26 @@ class TestLightMQTTTemplate(unittest.TestCase): 'command_off_template': 'off,{{ transition|d }}', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_template', 'name': 'test', @@ -496,19 +496,19 @@ class TestLightMQTTTemplate(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('light.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state diff --git a/tests/components/light/test_rfxtrx.py b/tests/components/light/test_rfxtrx.py index 8a8e94ec179..d7f9feee830 100644 --- a/tests/components/light/test_rfxtrx.py +++ b/tests/components/light/test_rfxtrx.py @@ -28,26 +28,26 @@ class TestLightRfxtrx(unittest.TestCase): def test_valid_config(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'0b1100cd0213c7f210010f51': { 'name': 'Test', - rfxtrx_core.ATTR_FIREEVENT: True}}}})) + rfxtrx_core.ATTR_FIREEVENT: True}}}}) - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'213c7f216': { 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', - 'signal_repetitions': 3}}}})) + 'signal_repetitions': 3}}}}) def test_invalid_config(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'light', { + assert not setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'automatic_add': True, 'invalid_key': 'afda', @@ -55,182 +55,183 @@ class TestLightRfxtrx(unittest.TestCase): {'213c7f216': { 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', - rfxtrx_core.ATTR_FIREEVENT: True}}}})) + rfxtrx_core.ATTR_FIREEVENT: True}}}}) def test_default_config(self): """Test with 0 switches.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', - 'devices': {}}})) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + 'devices': {}}}) + assert 0 == len(rfxtrx_core.RFX_DEVICES) def test_old_config(self): """Test with 1 light.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'devices': {'123efab1': { 'name': 'Test', - 'packetid': '0b1100cd0213c7f210010f51'}}}})) + 'packetid': '0b1100cd0213c7f210010f51'}}}}) import RFXtrx as rfxtrxmod rfxtrx_core.RFXOBJECT =\ rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['213c7f216'] - self.assertEqual('Test', entity.name) - self.assertEqual('off', entity.state) - self.assertTrue(entity.assumed_state) - self.assertEqual(entity.signal_repetitions, 1) - self.assertFalse(entity.should_fire_event) - self.assertFalse(entity.should_poll) + assert 'Test' == entity.name + assert 'off' == entity.state + assert entity.assumed_state + assert entity.signal_repetitions == 1 + assert not entity.should_fire_event + assert not entity.should_poll - self.assertFalse(entity.is_on) + assert not entity.is_on entity.turn_on() - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 255) + assert entity.is_on + assert entity.brightness == 255 entity.turn_off() - self.assertFalse(entity.is_on) - self.assertEqual(entity.brightness, 0) + assert not entity.is_on + assert entity.brightness == 0 entity.turn_on(brightness=100) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 100) + assert entity.is_on + assert entity.brightness == 100 entity.turn_on(brightness=10) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 10) + assert entity.is_on + assert entity.brightness == 10 entity.turn_on(brightness=255) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 255) + assert entity.is_on + assert entity.brightness == 255 def test_one_light(self): """Test with 1 light.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'devices': {'0b1100cd0213c7f210010f51': { - 'name': 'Test'}}}})) + 'name': 'Test'}}}}) import RFXtrx as rfxtrxmod rfxtrx_core.RFXOBJECT =\ rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['213c7f216'] - self.assertEqual('Test', entity.name) - self.assertEqual('off', entity.state) - self.assertTrue(entity.assumed_state) - self.assertEqual(entity.signal_repetitions, 1) - self.assertFalse(entity.should_fire_event) - self.assertFalse(entity.should_poll) + assert 'Test' == entity.name + assert 'off' == entity.state + assert entity.assumed_state + assert entity.signal_repetitions == 1 + assert not entity.should_fire_event + assert not entity.should_poll - self.assertFalse(entity.is_on) + assert not entity.is_on entity.turn_on() - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 255) + assert entity.is_on + assert entity.brightness == 255 entity.turn_off() - self.assertFalse(entity.is_on) - self.assertEqual(entity.brightness, 0) + assert not entity.is_on + assert entity.brightness == 0 entity.turn_on(brightness=100) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 100) + assert entity.is_on + assert entity.brightness == 100 entity.turn_on(brightness=10) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 10) + assert entity.is_on + assert entity.brightness == 10 entity.turn_on(brightness=255) - self.assertTrue(entity.is_on) - self.assertEqual(entity.brightness, 255) + assert entity.is_on + assert entity.brightness == 255 entity.turn_off() entity_id = rfxtrx_core.RFX_DEVICES['213c7f216'].entity_id entity_hass = self.hass.states.get(entity_id) - self.assertEqual('Test', entity_hass.name) - self.assertEqual('off', entity_hass.state) + assert 'Test' == entity_hass.name + assert 'off' == entity_hass.state entity.turn_on() entity_hass = self.hass.states.get(entity_id) - self.assertEqual('on', entity_hass.state) + assert 'on' == entity_hass.state entity.turn_off() entity_hass = self.hass.states.get(entity_id) - self.assertEqual('off', entity_hass.state) + assert 'off' == entity_hass.state entity.turn_on(brightness=100) entity_hass = self.hass.states.get(entity_id) - self.assertEqual('on', entity_hass.state) + assert 'on' == entity_hass.state entity.turn_on(brightness=10) entity_hass = self.hass.states.get(entity_id) - self.assertEqual('on', entity_hass.state) + assert 'on' == entity_hass.state entity.turn_on(brightness=255) entity_hass = self.hass.states.get(entity_id) - self.assertEqual('on', entity_hass.state) + assert 'on' == entity_hass.state def test_several_lights(self): """Test with 3 lights.""" - self.assertTrue(setup_component(self.hass, 'light', { - 'light': {'platform': 'rfxtrx', - 'signal_repetitions': 3, - 'devices': - {'0b1100cd0213c7f230010f71': { - 'name': 'Test'}, - '0b1100100118cdea02010f70': { - 'name': 'Bath'}, - '0b1100101118cdea02010f70': { - 'name': 'Living'}}}})) + assert setup_component(self.hass, 'light', { + 'light': { + 'platform': 'rfxtrx', + 'signal_repetitions': 3, + 'devices': { + '0b1100cd0213c7f230010f71': { + 'name': 'Test'}, + '0b1100100118cdea02010f70': { + 'name': 'Bath'}, + '0b1100101118cdea02010f70': { + 'name': 'Living'}}}}) - self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES)) + assert 3 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: entity = rfxtrx_core.RFX_DEVICES[id] - self.assertEqual(entity.signal_repetitions, 3) + assert entity.signal_repetitions == 3 if entity.name == 'Living': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() elif entity.name == 'Bath': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() elif entity.name == 'Test': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() - self.assertEqual(3, device_num) + assert 3 == device_num def test_discover_light(self): """Test with discovery of lights.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'automatic_add': True, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0b11009e00e6116202020070') event.data = bytearray(b'\x0b\x11\x00\x9e\x00\xe6\x11b\x02\x02\x00p') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['0e611622'] - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual('', - entity.__str__()) + assert 1 == len(rfxtrx_core.RFX_DEVICES) + assert '' == \ + entity.__str__() event = rfxtrx_core.get_rfx_object('0b11009e00e6116201010070') event.data = bytearray(b'\x0b\x11\x00\x9e\x00\xe6\x11b\x01\x01\x00p') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, @@ -238,15 +239,15 @@ class TestLightRfxtrx(unittest.TestCase): rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['118cdea2'] - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual('', - entity.__str__()) + assert 2 == len(rfxtrx_core.RFX_DEVICES) + assert '' == \ + entity.__str__() # trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # trying to add a switch event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') @@ -254,59 +255,59 @@ class TestLightRfxtrx(unittest.TestCase): 0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a rollershutter event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, 0xAB, 0x02, 0x0E, 0x00, 0x60]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) def test_discover_light_noautoadd(self): """Test with discover of light when auto add is False.""" - self.assertTrue(setup_component(self.hass, 'light', { + assert setup_component(self.hass, 'light', { 'light': {'platform': 'rfxtrx', 'automatic_add': False, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, 0xcd, 0xea, 0x02, 0x02, 0x00, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0b1100120118cdea02010070') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, 0xcd, 0xea, 0x02, 0x01, 0x00, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0b1100120118cdea02020070') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, 0xcd, 0xea, 0x02, 0x02, 0x00, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a switch event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18, 0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a rollershutter event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, 0xAB, 0x02, 0x0E, 0x00, 0x60]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py index 255e5307f7a..509b348691d 100644 --- a/tests/components/lock/test_demo.py +++ b/tests/components/lock/test_demo.py @@ -18,11 +18,11 @@ class TestLockDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(setup_component(self.hass, lock.DOMAIN, { + assert setup_component(self.hass, lock.DOMAIN, { 'lock': { 'platform': 'demo' } - })) + }) def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -30,10 +30,10 @@ class TestLockDemo(unittest.TestCase): def test_is_locked(self): """Test if lock is locked.""" - self.assertTrue(lock.is_locked(self.hass, FRONT)) + assert lock.is_locked(self.hass, FRONT) self.hass.states.is_state(FRONT, 'locked') - self.assertFalse(lock.is_locked(self.hass, KITCHEN)) + assert not lock.is_locked(self.hass, KITCHEN) self.hass.states.is_state(KITCHEN, 'unlocked') def test_locking(self): @@ -41,18 +41,18 @@ class TestLockDemo(unittest.TestCase): common.lock(self.hass, KITCHEN) self.hass.block_till_done() - self.assertTrue(lock.is_locked(self.hass, KITCHEN)) + assert lock.is_locked(self.hass, KITCHEN) def test_unlocking(self): """Test the unlocking of a lock.""" common.unlock(self.hass, FRONT) self.hass.block_till_done() - self.assertFalse(lock.is_locked(self.hass, FRONT)) + assert not lock.is_locked(self.hass, FRONT) def test_opening(self): """Test the opening of a lock.""" calls = mock_service(self.hass, lock.DOMAIN, lock.SERVICE_OPEN) common.open_lock(self.hass, OPENABLE_LOCK) self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index 4d2378ff9fa..a7bb06fc223 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -39,20 +39,20 @@ class TestLockMQTT(unittest.TestCase): }) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNLOCKED == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'state-topic', 'LOCK') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_LOCKED, state.state) + assert STATE_LOCKED == state.state fire_mqtt_message(self.hass, 'state-topic', 'UNLOCK') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) + assert STATE_UNLOCKED == state.state def test_sending_mqtt_commands_and_optimistic(self): """Test the sending MQTT commands in optimistic mode.""" @@ -68,8 +68,8 @@ class TestLockMQTT(unittest.TestCase): }) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_UNLOCKED == state.state + assert state.attributes.get(ATTR_ASSUMED_STATE) common.lock(self.hass, 'lock.test') self.hass.block_till_done() @@ -78,7 +78,7 @@ class TestLockMQTT(unittest.TestCase): 'command-topic', 'LOCK', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_LOCKED, state.state) + assert STATE_LOCKED == state.state common.unlock(self.hass, 'lock.test') self.hass.block_till_done() @@ -86,7 +86,7 @@ class TestLockMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'UNLOCK', 2, False) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) + assert STATE_UNLOCKED == state.state def test_controlling_state_via_topic_and_json_message(self): """Test the controlling state via topic and JSON message.""" @@ -103,23 +103,23 @@ class TestLockMQTT(unittest.TestCase): }) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) + assert STATE_UNLOCKED == state.state fire_mqtt_message(self.hass, 'state-topic', '{"val":"LOCK"}') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_LOCKED, state.state) + assert STATE_LOCKED == state.state fire_mqtt_message(self.hass, 'state-topic', '{"val":"UNLOCK"}') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNLOCKED, state.state) + assert STATE_UNLOCKED == state.state def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" - self.assertTrue(setup_component(self.hass, lock.DOMAIN, { + assert setup_component(self.hass, lock.DOMAIN, { lock.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -129,26 +129,26 @@ class TestLockMQTT(unittest.TestCase): 'payload_unlock': 'UNLOCK', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, lock.DOMAIN, { + assert setup_component(self.hass, lock.DOMAIN, { lock.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -160,22 +160,22 @@ class TestLockMQTT(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('lock.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal_lock(hass, mqtt_mock, caplog): diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 1ce0f9ff602..d9465d7a752 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -11,6 +11,7 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.lovelace import (load_yaml, save_yaml, load_config, UnsupportedYamlError) +import pytest TEST_YAML_A = """\ title: My Awesome Home @@ -138,7 +139,7 @@ class TestYAML(unittest.TestCase): fname = self._path_for("test1") save_yaml(fname, self.yaml.load(TEST_YAML_A)) data = load_yaml(fname) - self.assertEqual(data, self.yaml.load(TEST_YAML_A)) + assert data == self.yaml.load(TEST_YAML_A) def test_overwrite_and_reload(self): """Test that we can overwrite an existing file and read back.""" @@ -146,14 +147,14 @@ class TestYAML(unittest.TestCase): save_yaml(fname, self.yaml.load(TEST_YAML_A)) save_yaml(fname, self.yaml.load(TEST_YAML_B)) data = load_yaml(fname) - self.assertEqual(data, self.yaml.load(TEST_YAML_B)) + assert data == self.yaml.load(TEST_YAML_B) def test_load_bad_data(self): """Test error from trying to load unserialisable data.""" fname = self._path_for("test5") with open(fname, "w") as fh: fh.write(TEST_BAD_YAML) - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): load_yaml(fname) def test_add_id(self): @@ -172,7 +173,7 @@ class TestYAML(unittest.TestCase): with patch('homeassistant.components.lovelace.load_yaml', return_value=self.yaml.load(TEST_YAML_B)): data = load_config(fname) - self.assertEqual(data, self.yaml.load(TEST_YAML_B)) + assert data == self.yaml.load(TEST_YAML_B) async def test_deprecated_lovelace_ui(hass, hass_ws_client): diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index 5908ea02a37..1c4a2fa84a2 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -133,43 +133,43 @@ class TestAsyncMediaPlayer(unittest.TestCase): def test_volume_up(self): """Test the volume_up helper function.""" - self.assertEqual(self.player.volume_level, 0) + assert self.player.volume_level == 0 run_coroutine_threadsafe( self.player.async_set_volume_level(0.5), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.5) + assert self.player.volume_level == 0.5 run_coroutine_threadsafe( self.player.async_volume_up(), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.6) + assert self.player.volume_level == 0.6 def test_volume_down(self): """Test the volume_down helper function.""" - self.assertEqual(self.player.volume_level, 0) + assert self.player.volume_level == 0 run_coroutine_threadsafe( self.player.async_set_volume_level(0.5), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.5) + assert self.player.volume_level == 0.5 run_coroutine_threadsafe( self.player.async_volume_down(), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.4) + assert self.player.volume_level == 0.4 def test_media_play_pause(self): """Test the media_play_pause helper function.""" - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF run_coroutine_threadsafe( self.player.async_media_play_pause(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_PLAYING) + assert self.player.state == STATE_PLAYING run_coroutine_threadsafe( self.player.async_media_play_pause(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_PAUSED) + assert self.player.state == STATE_PAUSED def test_toggle(self): """Test the toggle helper function.""" - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF run_coroutine_threadsafe( self.player.async_toggle(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_ON) + assert self.player.state == STATE_ON run_coroutine_threadsafe( self.player.async_toggle(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF class TestSyncMediaPlayer(unittest.TestCase): @@ -186,38 +186,38 @@ class TestSyncMediaPlayer(unittest.TestCase): def test_volume_up(self): """Test the volume_up helper function.""" - self.assertEqual(self.player.volume_level, 0) + assert self.player.volume_level == 0 self.player.set_volume_level(0.5) - self.assertEqual(self.player.volume_level, 0.5) + assert self.player.volume_level == 0.5 run_coroutine_threadsafe( self.player.async_volume_up(), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.7) + assert self.player.volume_level == 0.7 def test_volume_down(self): """Test the volume_down helper function.""" - self.assertEqual(self.player.volume_level, 0) + assert self.player.volume_level == 0 self.player.set_volume_level(0.5) - self.assertEqual(self.player.volume_level, 0.5) + assert self.player.volume_level == 0.5 run_coroutine_threadsafe( self.player.async_volume_down(), self.hass.loop).result() - self.assertEqual(self.player.volume_level, 0.3) + assert self.player.volume_level == 0.3 def test_media_play_pause(self): """Test the media_play_pause helper function.""" - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF run_coroutine_threadsafe( self.player.async_media_play_pause(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_PLAYING) + assert self.player.state == STATE_PLAYING run_coroutine_threadsafe( self.player.async_media_play_pause(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_PAUSED) + assert self.player.state == STATE_PAUSED def test_toggle(self): """Test the toggle helper function.""" - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF run_coroutine_threadsafe( self.player.async_toggle(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_ON) + assert self.player.state == STATE_ON run_coroutine_threadsafe( self.player.async_toggle(), self.hass.loop).result() - self.assertEqual(self.player.state, STATE_OFF) + assert self.player.state == STATE_OFF diff --git a/tests/components/media_player/test_blackbird.py b/tests/components/media_player/test_blackbird.py index 550bfe88a61..38a63f294f9 100644 --- a/tests/components/media_player/test_blackbird.py +++ b/tests/components/media_player/test_blackbird.py @@ -11,6 +11,7 @@ from homeassistant.const import STATE_ON, STATE_OFF import tests.common from homeassistant.components.media_player.blackbird import ( DATA_BLACKBIRD, PLATFORM_SCHEMA, SERVICE_SETALLZONES, setup_platform) +import pytest class AttrDict(dict): @@ -157,7 +158,7 @@ class TestBlackbirdSchema(unittest.TestCase): }, ) for value in schemas: - with self.assertRaises(vol.MultipleInvalid): + with pytest.raises(vol.MultipleInvalid): PLATFORM_SCHEMA(value) @@ -192,18 +193,17 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): def test_setup_platform(self, *args): """Test setting up platform.""" # One service must be registered - self.assertTrue(self.hass.services.has_service(DOMAIN, - SERVICE_SETALLZONES)) - self.assertEqual(len(self.hass.data[DATA_BLACKBIRD]), 1) - self.assertEqual(self.hass.data[DATA_BLACKBIRD]['/dev/ttyUSB0-3'].name, - 'Zone name') + assert self.hass.services.has_service(DOMAIN, SERVICE_SETALLZONES) + assert len(self.hass.data[DATA_BLACKBIRD]) == 1 + assert self.hass.data[DATA_BLACKBIRD]['/dev/ttyUSB0-3'].name == \ + 'Zone name' def test_setallzones_service_call_with_entity_id(self): """Test set all zone source service call with entity id.""" self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 'one' == self.media_player.source # Call set all zones service self.hass.services.call(DOMAIN, SERVICE_SETALLZONES, @@ -212,110 +212,110 @@ class TestBlackbirdMediaPlayer(unittest.TestCase): blocking=True) # Check that source was changed - self.assertEqual(3, self.blackbird.zones[3].av) + assert 3 == self.blackbird.zones[3].av self.media_player.update() - self.assertEqual('three', self.media_player.source) + assert 'three' == self.media_player.source def test_setallzones_service_call_without_entity_id(self): """Test set all zone source service call without entity id.""" self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 'one' == self.media_player.source # Call set all zones service self.hass.services.call(DOMAIN, SERVICE_SETALLZONES, {'source': 'three'}, blocking=True) # Check that source was changed - self.assertEqual(3, self.blackbird.zones[3].av) + assert 3 == self.blackbird.zones[3].av self.media_player.update() - self.assertEqual('three', self.media_player.source) + assert 'three' == self.media_player.source def test_update(self): """Test updating values from blackbird.""" - self.assertIsNone(self.media_player.state) - self.assertIsNone(self.media_player.source) + assert self.media_player.state is None + assert self.media_player.source is None self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual('one', self.media_player.source) + assert STATE_ON == self.media_player.state + assert 'one' == self.media_player.source def test_name(self): """Test name property.""" - self.assertEqual('Zone name', self.media_player.name) + assert 'Zone name' == self.media_player.name def test_state(self): """Test state property.""" - self.assertIsNone(self.media_player.state) + assert self.media_player.state is None self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state self.blackbird.zones[3].power = False self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state def test_supported_features(self): """Test supported features property.""" - self.assertEqual(SUPPORT_TURN_ON | SUPPORT_TURN_OFF | - SUPPORT_SELECT_SOURCE, - self.media_player.supported_features) + assert SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ + SUPPORT_SELECT_SOURCE == \ + self.media_player.supported_features def test_source(self): """Test source property.""" - self.assertIsNone(self.media_player.source) + assert self.media_player.source is None self.media_player.update() - self.assertEqual('one', self.media_player.source) + assert 'one' == self.media_player.source def test_media_title(self): """Test media title property.""" - self.assertIsNone(self.media_player.media_title) + assert self.media_player.media_title is None self.media_player.update() - self.assertEqual('one', self.media_player.media_title) + assert 'one' == self.media_player.media_title def test_source_list(self): """Test source list property.""" # Note, the list is sorted! - self.assertEqual(['one', 'two', 'three'], - self.media_player.source_list) + assert ['one', 'two', 'three'] == \ + self.media_player.source_list def test_select_source(self): """Test source selection methods.""" self.media_player.update() - self.assertEqual('one', self.media_player.source) + assert 'one' == self.media_player.source self.media_player.select_source('two') - self.assertEqual(2, self.blackbird.zones[3].av) + assert 2 == self.blackbird.zones[3].av self.media_player.update() - self.assertEqual('two', self.media_player.source) + assert 'two' == self.media_player.source # Trying to set unknown source. self.media_player.select_source('no name') - self.assertEqual(2, self.blackbird.zones[3].av) + assert 2 == self.blackbird.zones[3].av self.media_player.update() - self.assertEqual('two', self.media_player.source) + assert 'two' == self.media_player.source def test_turn_on(self): """Testing turning on the zone.""" self.blackbird.zones[3].power = False self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state self.media_player.turn_on() - self.assertTrue(self.blackbird.zones[3].power) + assert self.blackbird.zones[3].power self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state def test_turn_off(self): """Testing turning off the zone.""" self.blackbird.zones[3].power = True self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state self.media_player.turn_off() - self.assertFalse(self.blackbird.zones[3].power) + assert not self.blackbird.zones[3].power self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/media_player/test_monoprice.py index 14e1769047a..417cd42187f 100644 --- a/tests/components/media_player/test_monoprice.py +++ b/tests/components/media_player/test_monoprice.py @@ -13,6 +13,7 @@ import tests.common from homeassistant.components.media_player.monoprice import ( DATA_MONOPRICE, PLATFORM_SCHEMA, SERVICE_SNAPSHOT, SERVICE_RESTORE, setup_platform) +import pytest class AttrDict(dict): @@ -149,7 +150,7 @@ class TestMonopriceSchema(unittest.TestCase): ) for value in schemas: - with self.assertRaises(vol.MultipleInvalid): + with pytest.raises(vol.MultipleInvalid): PLATFORM_SCHEMA(value) @@ -185,21 +186,19 @@ class TestMonopriceMediaPlayer(unittest.TestCase): def test_setup_platform(self, *args): """Test setting up platform.""" # Two services must be registered - self.assertTrue(self.hass.services.has_service(DOMAIN, - SERVICE_RESTORE)) - self.assertTrue(self.hass.services.has_service(DOMAIN, - SERVICE_SNAPSHOT)) - self.assertEqual(len(self.hass.data[DATA_MONOPRICE]), 1) - self.assertEqual(self.hass.data[DATA_MONOPRICE][0].name, 'Zone name') + assert self.hass.services.has_service(DOMAIN, SERVICE_RESTORE) + assert self.hass.services.has_service(DOMAIN, SERVICE_SNAPSHOT) + assert len(self.hass.data[DATA_MONOPRICE]) == 1 + assert self.hass.data[DATA_MONOPRICE][0].name == 'Zone name' def test_service_calls_with_entity_id(self): """Test snapshot save/restore service calls.""" self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source # Saving default values self.hass.services.call(DOMAIN, SERVICE_SNAPSHOT, @@ -215,11 +214,11 @@ class TestMonopriceMediaPlayer(unittest.TestCase): # Checking that values were indeed changed self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_OFF, self.media_player.state) - self.assertEqual(1.0, self.media_player.volume_level, 0.0001) - self.assertFalse(self.media_player.is_volume_muted) - self.assertEqual('two', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_OFF == self.media_player.state + assert 1.0 == self.media_player.volume_level, 0.0001 + assert not self.media_player.is_volume_muted + assert 'two' == self.media_player.source # Restoring wrong media player to its previous state # Nothing should be done @@ -230,11 +229,11 @@ class TestMonopriceMediaPlayer(unittest.TestCase): # Checking that values were not (!) restored self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_OFF, self.media_player.state) - self.assertEqual(1.0, self.media_player.volume_level, 0.0001) - self.assertFalse(self.media_player.is_volume_muted) - self.assertEqual('two', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_OFF == self.media_player.state + assert 1.0 == self.media_player.volume_level, 0.0001 + assert not self.media_player.is_volume_muted + assert 'two' == self.media_player.source # Restoring media player to its previous state self.hass.services.call(DOMAIN, SERVICE_RESTORE, @@ -243,31 +242,31 @@ class TestMonopriceMediaPlayer(unittest.TestCase): self.hass.block_till_done() # Checking that values were restored - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source def test_service_calls_without_entity_id(self): """Test snapshot save/restore service calls.""" self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source # Restoring media player # since there is no snapshot, nothing should be done self.hass.services.call(DOMAIN, SERVICE_RESTORE, blocking=True) self.hass.block_till_done() self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source # Saving default values self.hass.services.call(DOMAIN, SERVICE_SNAPSHOT, blocking=True) @@ -281,195 +280,195 @@ class TestMonopriceMediaPlayer(unittest.TestCase): # Checking that values were indeed changed self.media_player.update() - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_OFF, self.media_player.state) - self.assertEqual(1.0, self.media_player.volume_level, 0.0001) - self.assertFalse(self.media_player.is_volume_muted) - self.assertEqual('two', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_OFF == self.media_player.state + assert 1.0 == self.media_player.volume_level, 0.0001 + assert not self.media_player.is_volume_muted + assert 'two' == self.media_player.source # Restoring media player to its previous state self.hass.services.call(DOMAIN, SERVICE_RESTORE, blocking=True) self.hass.block_till_done() # Checking that values were restored - self.assertEqual('Zone name', self.media_player.name) - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert 'Zone name' == self.media_player.name + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source def test_update(self): """Test updating values from monoprice.""" - self.assertIsNone(self.media_player.state) - self.assertIsNone(self.media_player.volume_level) - self.assertIsNone(self.media_player.is_volume_muted) - self.assertIsNone(self.media_player.source) + assert self.media_player.state is None + assert self.media_player.volume_level is None + assert self.media_player.is_volume_muted is None + assert self.media_player.source is None self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) - self.assertTrue(self.media_player.is_volume_muted) - self.assertEqual('one', self.media_player.source) + assert STATE_ON == self.media_player.state + assert 0.0 == self.media_player.volume_level, 0.0001 + assert self.media_player.is_volume_muted + assert 'one' == self.media_player.source def test_name(self): """Test name property.""" - self.assertEqual('Zone name', self.media_player.name) + assert 'Zone name' == self.media_player.name def test_state(self): """Test state property.""" - self.assertIsNone(self.media_player.state) + assert self.media_player.state is None self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state self.monoprice.zones[12].power = False self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state def test_volume_level(self): """Test volume level property.""" - self.assertIsNone(self.media_player.volume_level) + assert self.media_player.volume_level is None self.media_player.update() - self.assertEqual(0.0, self.media_player.volume_level, 0.0001) + assert 0.0 == self.media_player.volume_level, 0.0001 self.monoprice.zones[12].volume = 38 self.media_player.update() - self.assertEqual(1.0, self.media_player.volume_level, 0.0001) + assert 1.0 == self.media_player.volume_level, 0.0001 self.monoprice.zones[12].volume = 19 self.media_player.update() - self.assertEqual(.5, self.media_player.volume_level, 0.0001) + assert .5 == self.media_player.volume_level, 0.0001 def test_is_volume_muted(self): """Test volume muted property.""" - self.assertIsNone(self.media_player.is_volume_muted) + assert self.media_player.is_volume_muted is None self.media_player.update() - self.assertTrue(self.media_player.is_volume_muted) + assert self.media_player.is_volume_muted self.monoprice.zones[12].mute = False self.media_player.update() - self.assertFalse(self.media_player.is_volume_muted) + assert not self.media_player.is_volume_muted def test_supported_features(self): """Test supported features property.""" - self.assertEqual(SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | - SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON | - SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE, - self.media_player.supported_features) + assert SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ + SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON | \ + SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE == \ + self.media_player.supported_features def test_source(self): """Test source property.""" - self.assertIsNone(self.media_player.source) + assert self.media_player.source is None self.media_player.update() - self.assertEqual('one', self.media_player.source) + assert 'one' == self.media_player.source def test_media_title(self): """Test media title property.""" - self.assertIsNone(self.media_player.media_title) + assert self.media_player.media_title is None self.media_player.update() - self.assertEqual('one', self.media_player.media_title) + assert 'one' == self.media_player.media_title def test_source_list(self): """Test source list property.""" # Note, the list is sorted! - self.assertEqual(['one', 'two', 'three'], - self.media_player.source_list) + assert ['one', 'two', 'three'] == \ + self.media_player.source_list def test_select_source(self): """Test source selection methods.""" self.media_player.update() - self.assertEqual('one', self.media_player.source) + assert 'one' == self.media_player.source self.media_player.select_source('two') - self.assertEqual(2, self.monoprice.zones[12].source) + assert 2 == self.monoprice.zones[12].source self.media_player.update() - self.assertEqual('two', self.media_player.source) + assert 'two' == self.media_player.source # Trying to set unknown source self.media_player.select_source('no name') - self.assertEqual(2, self.monoprice.zones[12].source) + assert 2 == self.monoprice.zones[12].source self.media_player.update() - self.assertEqual('two', self.media_player.source) + assert 'two' == self.media_player.source def test_turn_on(self): """Test turning on the zone.""" self.monoprice.zones[12].power = False self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state self.media_player.turn_on() - self.assertTrue(self.monoprice.zones[12].power) + assert self.monoprice.zones[12].power self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state def test_turn_off(self): """Test turning off the zone.""" self.monoprice.zones[12].power = True self.media_player.update() - self.assertEqual(STATE_ON, self.media_player.state) + assert STATE_ON == self.media_player.state self.media_player.turn_off() - self.assertFalse(self.monoprice.zones[12].power) + assert not self.monoprice.zones[12].power self.media_player.update() - self.assertEqual(STATE_OFF, self.media_player.state) + assert STATE_OFF == self.media_player.state def test_mute_volume(self): """Test mute functionality.""" self.monoprice.zones[12].mute = True self.media_player.update() - self.assertTrue(self.media_player.is_volume_muted) + assert self.media_player.is_volume_muted self.media_player.mute_volume(False) - self.assertFalse(self.monoprice.zones[12].mute) + assert not self.monoprice.zones[12].mute self.media_player.update() - self.assertFalse(self.media_player.is_volume_muted) + assert not self.media_player.is_volume_muted self.media_player.mute_volume(True) - self.assertTrue(self.monoprice.zones[12].mute) + assert self.monoprice.zones[12].mute self.media_player.update() - self.assertTrue(self.media_player.is_volume_muted) + assert self.media_player.is_volume_muted def test_set_volume_level(self): """Test set volume level.""" self.media_player.set_volume_level(1.0) - self.assertEqual(38, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 38 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) self.media_player.set_volume_level(0.0) - self.assertEqual(0, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 0 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) self.media_player.set_volume_level(0.5) - self.assertEqual(19, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 19 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) def test_volume_up(self): """Test increasing volume by one.""" self.monoprice.zones[12].volume = 37 self.media_player.update() self.media_player.volume_up() - self.assertEqual(38, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 38 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) # Try to raise value beyond max self.media_player.update() self.media_player.volume_up() - self.assertEqual(38, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 38 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) def test_volume_down(self): """Test decreasing volume by one.""" self.monoprice.zones[12].volume = 1 self.media_player.update() self.media_player.volume_down() - self.assertEqual(0, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 0 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) # Try to lower value beyond minimum self.media_player.update() self.media_player.volume_down() - self.assertEqual(0, self.monoprice.zones[12].volume) - self.assertTrue(isinstance(self.monoprice.zones[12].volume, int)) + assert 0 == self.monoprice.zones[12].volume + assert isinstance(self.monoprice.zones[12].volume, int) diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/media_player/test_samsungtv.py index dda6562af77..31ad449254b 100644 --- a/tests/components/media_player/test_samsungtv.py +++ b/tests/components/media_player/test_samsungtv.py @@ -102,7 +102,7 @@ class TestSamsungTv(unittest.TestCase): def test_update_on(self): """Testing update tv on.""" self.device.update() - self.assertEqual(None, self.device._state) + assert self.device._state is None def test_update_off(self): """Testing update tv off.""" @@ -111,12 +111,12 @@ class TestSamsungTv(unittest.TestCase): side_effect=OSError('Boom')) self.device.get_remote = mock.Mock(return_value=_remote) self.device.update() - self.assertEqual(STATE_OFF, self.device._state) + assert STATE_OFF == self.device._state def test_send_key(self): """Test for send key.""" self.device.send_key('KEY_POWER') - self.assertEqual(None, self.device._state) + assert self.device._state is None def test_send_key_broken_pipe(self): """Testing broken pipe Exception.""" @@ -125,8 +125,8 @@ class TestSamsungTv(unittest.TestCase): side_effect=BrokenPipeError('Boom')) self.device.get_remote = mock.Mock(return_value=_remote) self.device.send_key('HELLO') - self.assertIsNone(self.device._remote) - self.assertEqual(None, self.device._state) + assert self.device._remote is None + assert self.device._state is None def test_send_key_connection_closed_retry_succeed(self): """Test retry on connection closed.""" @@ -137,11 +137,11 @@ class TestSamsungTv(unittest.TestCase): self.device.get_remote = mock.Mock(return_value=_remote) command = 'HELLO' self.device.send_key(command) - self.assertEqual(None, self.device._state) + assert self.device._state is None # verify that _remote.control() get called twice because of retry logic expected = [mock.call(command), mock.call(command)] - self.assertEqual(expected, _remote.control.call_args_list) + assert expected == _remote.control.call_args_list def test_send_key_unhandled_response(self): """Testing unhandled response exception.""" @@ -151,8 +151,8 @@ class TestSamsungTv(unittest.TestCase): ) self.device.get_remote = mock.Mock(return_value=_remote) self.device.send_key('HELLO') - self.assertIsNone(self.device._remote) - self.assertEqual(None, self.device._state) + assert self.device._remote is None + assert self.device._state is None def test_send_key_os_error(self): """Testing broken pipe Exception.""" @@ -161,42 +161,41 @@ class TestSamsungTv(unittest.TestCase): side_effect=OSError('Boom')) self.device.get_remote = mock.Mock(return_value=_remote) self.device.send_key('HELLO') - self.assertIsNone(self.device._remote) - self.assertEqual(STATE_OFF, self.device._state) + assert self.device._remote is None + assert STATE_OFF == self.device._state def test_power_off_in_progress(self): """Test for power_off_in_progress.""" - self.assertFalse(self.device._power_off_in_progress()) + assert not self.device._power_off_in_progress() self.device._end_of_power_off = dt_util.utcnow() + timedelta( seconds=15) - self.assertTrue(self.device._power_off_in_progress()) + assert self.device._power_off_in_progress() def test_name(self): """Test for name property.""" - self.assertEqual('fake', self.device.name) + assert 'fake' == self.device.name def test_state(self): """Test for state property.""" self.device._state = None - self.assertEqual(None, self.device.state) + assert self.device.state is None self.device._state = STATE_OFF - self.assertEqual(STATE_OFF, self.device.state) + assert STATE_OFF == self.device.state def test_is_volume_muted(self): """Test for is_volume_muted property.""" self.device._muted = False - self.assertFalse(self.device.is_volume_muted) + assert not self.device.is_volume_muted self.device._muted = True - self.assertTrue(self.device.is_volume_muted) + assert self.device.is_volume_muted def test_supported_features(self): """Test for supported_features property.""" self.device._mac = None - self.assertEqual(SUPPORT_SAMSUNGTV, self.device.supported_features) + assert SUPPORT_SAMSUNGTV == self.device.supported_features self.device._mac = "fake" - self.assertEqual( - SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON, - self.device.supported_features) + assert SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON == \ + self.device.supported_features def test_turn_off(self): """Test for turn_off.""" @@ -206,7 +205,7 @@ class TestSamsungTv(unittest.TestCase): self.get_remote = mock.Mock(return_value=_remote) self.device._end_of_power_off = None self.device.turn_off() - self.assertIsNotNone(self.device._end_of_power_off) + assert self.device._end_of_power_off is not None self.device.send_key.assert_called_once_with('KEY_POWER') self.device.send_key = mock.Mock() self.device._config['method'] = 'legacy' @@ -247,11 +246,11 @@ class TestSamsungTv(unittest.TestCase): self.device._playing = False self.device.media_play_pause() self.device.send_key.assert_called_once_with("KEY_PLAY") - self.assertTrue(self.device._playing) + assert self.device._playing self.device.send_key = mock.Mock() self.device.media_play_pause() self.device.send_key.assert_called_once_with("KEY_PAUSE") - self.assertFalse(self.device._playing) + assert not self.device._playing def test_media_play(self): """Test for media_play.""" @@ -259,7 +258,7 @@ class TestSamsungTv(unittest.TestCase): self.device._playing = False self.device.media_play() self.device.send_key.assert_called_once_with("KEY_PLAY") - self.assertTrue(self.device._playing) + assert self.device._playing def test_media_pause(self): """Test for media_pause.""" @@ -267,7 +266,7 @@ class TestSamsungTv(unittest.TestCase): self.device._playing = True self.device.media_pause() self.device.send_key.assert_called_once_with("KEY_PAUSE") - self.assertFalse(self.device._playing) + assert not self.device._playing def test_media_next_track(self): """Test for media_next_track.""" diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py index cfe969a25c4..bf81aee5982 100644 --- a/tests/components/media_player/test_sonos.py +++ b/tests/components/media_player/test_sonos.py @@ -162,8 +162,8 @@ class TestSonosMediaPlayer(unittest.TestCase): }) devices = list(self.hass.data[sonos.DATA_SONOS].devices) - self.assertEqual(len(devices), 1) - self.assertEqual(devices[0].name, 'Kitchen') + assert len(devices) == 1 + assert devices[0].name == 'Kitchen' @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -181,8 +181,8 @@ class TestSonosMediaPlayer(unittest.TestCase): assert setup_component(self.hass, DOMAIN, config) - self.assertEqual(len(self.hass.data[sonos.DATA_SONOS].devices), 1) - self.assertEqual(discover_mock.call_count, 1) + assert len(self.hass.data[sonos.DATA_SONOS].devices) == 1 + assert discover_mock.call_count == 1 @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -198,8 +198,8 @@ class TestSonosMediaPlayer(unittest.TestCase): assert setup_component(self.hass, DOMAIN, config) devices = self.hass.data[sonos.DATA_SONOS].devices - self.assertEqual(len(devices), 1) - self.assertEqual(devices[0].name, 'Kitchen') + assert len(devices) == 1 + assert devices[0].name == 'Kitchen' @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -215,8 +215,8 @@ class TestSonosMediaPlayer(unittest.TestCase): assert setup_component(self.hass, DOMAIN, config) devices = self.hass.data[sonos.DATA_SONOS].devices - self.assertEqual(len(devices), 2) - self.assertEqual(devices[0].name, 'Kitchen') + assert len(devices) == 2 + assert devices[0].name == 'Kitchen' @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -232,8 +232,8 @@ class TestSonosMediaPlayer(unittest.TestCase): assert setup_component(self.hass, DOMAIN, config) devices = self.hass.data[sonos.DATA_SONOS].devices - self.assertEqual(len(devices), 2) - self.assertEqual(devices[0].name, 'Kitchen') + assert len(devices) == 2 + assert devices[0].name == 'Kitchen' @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch.object(pysonos, 'discover', new=pysonosDiscoverMock.discover) @@ -242,8 +242,8 @@ class TestSonosMediaPlayer(unittest.TestCase): """Test a single device using the autodiscovery provided by Sonos.""" sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass)) devices = list(self.hass.data[sonos.DATA_SONOS].devices) - self.assertEqual(len(devices), 1) - self.assertEqual(devices[0].name, 'Kitchen') + assert len(devices) == 1 + assert devices[0].name == 'Kitchen' @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -296,11 +296,11 @@ class TestSonosMediaPlayer(unittest.TestCase): device.set_alarm(alarm_id=2) alarm1.save.assert_not_called() device.set_alarm(alarm_id=1, **attrs) - self.assertEqual(alarm1.enabled, attrs['enabled']) - self.assertEqual(alarm1.start_time, attrs['time']) - self.assertEqual(alarm1.include_linked_zones, - attrs['include_linked_zones']) - self.assertEqual(alarm1.volume, 30) + assert alarm1.enabled == attrs['enabled'] + assert alarm1.start_time == attrs['time'] + assert alarm1.include_linked_zones == \ + attrs['include_linked_zones'] + assert alarm1.volume == 30 alarm1.save.assert_called_once_with() @mock.patch('pysonos.SoCo', new=SoCoMock) @@ -316,8 +316,8 @@ class TestSonosMediaPlayer(unittest.TestCase): snapshotMock.return_value = True device.snapshot() - self.assertEqual(snapshotMock.call_count, 1) - self.assertEqual(snapshotMock.call_args, mock.call()) + assert snapshotMock.call_count == 1 + assert snapshotMock.call_args == mock.call() @mock.patch('pysonos.SoCo', new=SoCoMock) @mock.patch('socket.create_connection', side_effect=socket.error()) @@ -337,5 +337,5 @@ class TestSonosMediaPlayer(unittest.TestCase): device._snapshot_coordinator.soco_device = SoCoMock('192.0.2.17') device._soco_snapshot = Snapshot(device._player) device.restore() - self.assertEqual(restoreMock.call_count, 1) - self.assertEqual(restoreMock.call_args, mock.call(False)) + assert restoreMock.call_count == 1 + assert restoreMock.call_args == mock.call(False) diff --git a/tests/components/media_player/test_soundtouch.py b/tests/components/media_player/test_soundtouch.py index 62356e6afca..2a763bfc706 100644 --- a/tests/components/media_player/test_soundtouch.py +++ b/tests/components/media_player/test_soundtouch.py @@ -164,10 +164,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): default_component(), mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(len(all_devices), 1) - self.assertEqual(all_devices[0].name, 'soundtouch') - self.assertEqual(all_devices[0].config['port'], 8090) - self.assertEqual(mocked_soundtouch_device.call_count, 1) + assert len(all_devices) == 1 + assert all_devices[0].name == 'soundtouch' + assert all_devices[0].config['port'] == 8090 + assert mocked_soundtouch_device.call_count == 1 @mock.patch('libsoundtouch.soundtouch_device', side_effect=None) def test_ensure_setup_discovery(self, mocked_soundtouch_device): @@ -181,10 +181,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock(), new_device) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(len(all_devices), 1) - self.assertEqual(all_devices[0].config['port'], 8090) - self.assertEqual(all_devices[0].config['host'], '192.168.1.1') - self.assertEqual(mocked_soundtouch_device.call_count, 1) + assert len(all_devices) == 1 + assert all_devices[0].config['port'] == 8090 + assert all_devices[0].config['host'] == '192.168.1.1' + assert mocked_soundtouch_device.call_count == 1 @mock.patch('libsoundtouch.soundtouch_device', side_effect=None) def test_ensure_setup_discovery_no_duplicate(self, @@ -193,7 +193,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 1) + assert len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]) == 1 new_device = {"port": "8090", "host": "192.168.1.1", "properties": {}, @@ -203,7 +203,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock(), new_device # New device ) - self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 2) + assert len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]) == 2 existing_device = {"port": "8090", "host": "192.168.0.1", "properties": {}, @@ -213,8 +213,8 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock(), existing_device # Existing device ) - self.assertEqual(mocked_soundtouch_device.call_count, 2) - self.assertEqual(len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]), 2) + assert mocked_soundtouch_device.call_count == 2 + assert len(self.hass.data[soundtouch.DATA_SOUNDTOUCH]) == 2 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status') @@ -226,12 +226,12 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 self.hass.data[soundtouch.DATA_SOUNDTOUCH][0].update() - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 2 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status', @@ -244,17 +244,17 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].state, STATE_PLAYING) - self.assertEqual(all_devices[0].media_image_url, "image.url") - self.assertEqual(all_devices[0].media_title, "artist - track") - self.assertEqual(all_devices[0].media_track, "track") - self.assertEqual(all_devices[0].media_artist, "artist") - self.assertEqual(all_devices[0].media_album_name, "album") - self.assertEqual(all_devices[0].media_duration, 1) + assert all_devices[0].state == STATE_PLAYING + assert all_devices[0].media_image_url == "image.url" + assert all_devices[0].media_title == "artist - track" + assert all_devices[0].media_track == "track" + assert all_devices[0].media_artist == "artist" + assert all_devices[0].media_album_name == "album" + assert all_devices[0].media_duration == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status', @@ -267,11 +267,11 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].media_title, None) + assert all_devices[0].media_title is None @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status', @@ -284,17 +284,17 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].state, STATE_PLAYING) - self.assertEqual(all_devices[0].media_image_url, "image.url") - self.assertEqual(all_devices[0].media_title, "station") - self.assertEqual(all_devices[0].media_track, None) - self.assertEqual(all_devices[0].media_artist, None) - self.assertEqual(all_devices[0].media_album_name, None) - self.assertEqual(all_devices[0].media_duration, None) + assert all_devices[0].state == STATE_PLAYING + assert all_devices[0].media_image_url == "image.url" + assert all_devices[0].media_title == "station" + assert all_devices[0].media_track is None + assert all_devices[0].media_artist is None + assert all_devices[0].media_album_name is None + assert all_devices[0].media_duration is None @mock.patch('libsoundtouch.device.SoundTouchDevice.volume', side_effect=MockVolume) @@ -307,11 +307,11 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].volume_level, 0.12) + assert all_devices[0].volume_level == 0.12 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status', @@ -324,11 +324,11 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].state, STATE_OFF) + assert all_devices[0].state == STATE_OFF @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.status', @@ -341,11 +341,11 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].state, STATE_PAUSED) + assert all_devices[0].state == STATE_PAUSED @mock.patch('libsoundtouch.device.SoundTouchDevice.volume', side_effect=MockVolumeMuted) @@ -358,11 +358,11 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].is_volume_muted, True) + assert all_devices[0].is_volume_muted is True @mock.patch('libsoundtouch.soundtouch_device') def test_media_commands(self, mocked_soundtouch_device): @@ -370,9 +370,9 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): soundtouch.setup_platform(self.hass, default_component(), mock.MagicMock()) - self.assertEqual(mocked_soundtouch_device.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(all_devices[0].supported_features, 17853) + assert all_devices[0].supported_features == 17853 @mock.patch('libsoundtouch.device.SoundTouchDevice.power_off') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -387,10 +387,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].turn_off() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 1) - self.assertEqual(mocked_power_off.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 1 + assert mocked_power_off.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.power_on') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -405,10 +405,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].turn_on() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 1) - self.assertEqual(mocked_power_on.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 1 + assert mocked_power_on.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume_up') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -423,10 +423,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].volume_up() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 2) - self.assertEqual(mocked_volume_up.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 2 + assert mocked_volume_up.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.volume_down') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -441,10 +441,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].volume_down() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 2) - self.assertEqual(mocked_volume_down.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 2 + assert mocked_volume_down.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.set_volume') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -459,9 +459,9 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].set_volume_level(0.17) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 2 mocked_set_volume.assert_called_with(17) @mock.patch('libsoundtouch.device.SoundTouchDevice.mute') @@ -477,10 +477,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].mute_volume(None) - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 2) - self.assertEqual(mocked_mute.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 2 + assert mocked_mute.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.play') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -495,10 +495,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].media_play() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 1) - self.assertEqual(mocked_play.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 1 + assert mocked_play.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.pause') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -513,10 +513,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].media_pause() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 1) - self.assertEqual(mocked_pause.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 1 + assert mocked_pause.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.play_pause') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -531,10 +531,10 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].media_play_pause() - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 1) - self.assertEqual(mocked_play_pause.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 1 + assert mocked_play_pause.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.previous_track') @mock.patch('libsoundtouch.device.SoundTouchDevice.next_track') @@ -550,15 +550,15 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): default_component(), mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices[0].media_next_track() - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_next_track.call_count, 1) + assert mocked_status.call_count == 2 + assert mocked_next_track.call_count == 1 all_devices[0].media_previous_track() - self.assertEqual(mocked_status.call_count, 3) - self.assertEqual(mocked_previous_track.call_count, 1) + assert mocked_status.call_count == 3 + assert mocked_previous_track.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.select_preset') @mock.patch('libsoundtouch.device.SoundTouchDevice.presets', @@ -574,15 +574,15 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): default_component(), mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices[0].play_media('PLAYLIST', 1) - self.assertEqual(mocked_presets.call_count, 1) - self.assertEqual(mocked_select_preset.call_count, 1) + assert mocked_presets.call_count == 1 + assert mocked_select_preset.call_count == 1 all_devices[0].play_media('PLAYLIST', 2) - self.assertEqual(mocked_presets.call_count, 2) - self.assertEqual(mocked_select_preset.call_count, 1) + assert mocked_presets.call_count == 2 + assert mocked_select_preset.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.play_url') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -596,9 +596,9 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): default_component(), mock.MagicMock()) all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] - self.assertEqual(mocked_soundtouch_device.call_count, 1) - self.assertEqual(mocked_status.call_count, 1) - self.assertEqual(mocked_volume.call_count, 1) + assert mocked_soundtouch_device.call_count == 1 + assert mocked_status.call_count == 1 + assert mocked_volume.call_count == 1 all_devices[0].play_media('MUSIC', "http://fqdn/file.mp3") mocked_play_url.assert_called_with("http://fqdn/file.mp3") @@ -619,28 +619,28 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].entity_id = "media_player.entity_1" all_devices[1].entity_id = "media_player.entity_2" - self.assertEqual(mocked_soundtouch_device.call_count, 2) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_soundtouch_device.call_count == 2 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 2 # one master, one slave => create zone self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.entity_1"}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 # unknown master. create zone is must not be called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.entity_X"}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 # no slaves, create zone must not be called all_devices.pop(1) self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.entity_1"}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.create_zone') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -659,30 +659,30 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].entity_id = "media_player.entity_1" all_devices[1].entity_id = "media_player.entity_2" - self.assertEqual(mocked_soundtouch_device.call_count, 2) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_soundtouch_device.call_count == 2 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 2 # one master, one slave => create zone self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, {"master": "media_player.entity_1", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 # unknown master. create zone is must not be called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, {"master": "media_player.entity_X", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 # no slaves, create zone must not be called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, {"master": "media_player.entity_X", "slaves": []}, True) - self.assertEqual(mocked_create_zone.call_count, 1) + assert mocked_create_zone.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.remove_zone_slave') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -701,30 +701,30 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].entity_id = "media_player.entity_1" all_devices[1].entity_id = "media_player.entity_2" - self.assertEqual(mocked_soundtouch_device.call_count, 2) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_soundtouch_device.call_count == 2 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 2 # remove one slave self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, {"master": "media_player.entity_1", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_remove_zone_slave.call_count, 1) + assert mocked_remove_zone_slave.call_count == 1 # unknown master. add zone slave is not called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, {"master": "media_player.entity_X", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_remove_zone_slave.call_count, 1) + assert mocked_remove_zone_slave.call_count == 1 # no slave to add, add zone slave is not called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, {"master": "media_player.entity_1", "slaves": []}, True) - self.assertEqual(mocked_remove_zone_slave.call_count, 1) + assert mocked_remove_zone_slave.call_count == 1 @mock.patch('libsoundtouch.device.SoundTouchDevice.add_zone_slave') @mock.patch('libsoundtouch.device.SoundTouchDevice.volume') @@ -743,27 +743,27 @@ class TestSoundtouchMediaPlayer(unittest.TestCase): all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH] all_devices[0].entity_id = "media_player.entity_1" all_devices[1].entity_id = "media_player.entity_2" - self.assertEqual(mocked_soundtouch_device.call_count, 2) - self.assertEqual(mocked_status.call_count, 2) - self.assertEqual(mocked_volume.call_count, 2) + assert mocked_soundtouch_device.call_count == 2 + assert mocked_status.call_count == 2 + assert mocked_volume.call_count == 2 # add one slave self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, {"master": "media_player.entity_1", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_add_zone_slave.call_count, 1) + assert mocked_add_zone_slave.call_count == 1 # unknown master. add zone slave is not called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, {"master": "media_player.entity_X", "slaves": ["media_player.entity_2"]}, True) - self.assertEqual(mocked_add_zone_slave.call_count, 1) + assert mocked_add_zone_slave.call_count == 1 # no slave to add, add zone slave is not called self.hass.services.call(soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, {"master": "media_player.entity_1", "slaves": ["media_player.entity_X"]}, True) - self.assertEqual(mocked_add_zone_slave.call_count, 1) + assert mocked_add_zone_slave.call_count == 1 diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 279809a283f..9f0f6d0728b 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -220,7 +220,7 @@ class TestMediaPlayer(unittest.TestCase): config_start['attributes'] = {} config = validate_config(self.config_children_only) - self.assertEqual(config_start, config) + assert config_start == config def test_config_children_and_attr(self): """Check config with children and attributes.""" @@ -229,7 +229,7 @@ class TestMediaPlayer(unittest.TestCase): config_start['commands'] = {} config = validate_config(self.config_children_and_attr) - self.assertEqual(config_start, config) + assert config_start == config def test_config_no_name(self): """Check config with no Name entry.""" @@ -238,7 +238,7 @@ class TestMediaPlayer(unittest.TestCase): validate_config({'platform': 'universal'}) except MultipleInvalid: response = False - self.assertFalse(response) + assert not response def test_config_bad_children(self): """Check config with bad children entry.""" @@ -247,31 +247,31 @@ class TestMediaPlayer(unittest.TestCase): 'platform': 'universal'} config_no_children = validate_config(config_no_children) - self.assertEqual([], config_no_children['children']) + assert [] == config_no_children['children'] config_bad_children = validate_config(config_bad_children) - self.assertEqual([], config_bad_children['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) - self.assertEqual({}, config['commands']) + assert {} == config['commands'] def test_config_bad_attributes(self): """Check config with bad attributes.""" config = {'name': 'test', 'platform': 'universal'} config = validate_config(config) - self.assertEqual({}, config['attributes']) + 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) - self.assertFalse('asdf' in config) + assert not ('asdf' in config) def test_platform_setup(self): """Test platform setup.""" @@ -292,15 +292,15 @@ class TestMediaPlayer(unittest.TestCase): self.hass.loop).result() except MultipleInvalid: setup_ok = False - self.assertFalse(setup_ok) - self.assertEqual(0, len(entities)) + assert not setup_ok + assert 0 == len(entities) run_coroutine_threadsafe( universal.async_setup_platform( self.hass, validate_config(config), add_entities), self.hass.loop).result() - self.assertEqual(1, len(entities)) - self.assertEqual('test', entities[0].name) + assert 1 == len(entities) + assert 'test' == entities[0].name def test_master_state(self): """Test master state property.""" @@ -308,7 +308,7 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(None, ump.master_state) + assert ump.master_state is None def test_master_state_with_attrs(self): """Test master state property.""" @@ -316,9 +316,9 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(STATE_OFF, ump.master_state) + assert STATE_OFF == ump.master_state self.hass.states.set(self.mock_state_switch_id, STATE_ON) - self.assertEqual(STATE_ON, ump.master_state) + assert STATE_ON == ump.master_state def test_master_state_with_template(self): """Test the state_template option.""" @@ -331,9 +331,9 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(STATE_ON, ump.master_state) + assert STATE_ON == ump.master_state self.hass.states.set('input_boolean.test', STATE_ON) - self.assertEqual(STATE_OFF, ump.master_state) + assert STATE_OFF == ump.master_state def test_master_state_with_bad_attrs(self): """Test master state property.""" @@ -343,7 +343,7 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(STATE_OFF, ump.master_state) + assert STATE_OFF == ump.master_state def test_active_child_state(self): """Test active child state property.""" @@ -353,28 +353,28 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(None, ump._child_state) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(self.mock_mp_1.entity_id, - ump._child_state.entity_id) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(self.mock_mp_1.entity_id, - ump._child_state.entity_id) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(self.mock_mp_2.entity_id, - ump._child_state.entity_id) + assert self.mock_mp_2.entity_id == \ + ump._child_state.entity_id def test_name(self): """Test name property.""" @@ -382,7 +382,7 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(config['name'], ump.name) + assert config['name'] == ump.name def test_polling(self): """Test should_poll property.""" @@ -390,7 +390,7 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual(False, ump.should_poll) + assert ump.should_poll is False def test_state_children_only(self): """Test media player state with only children.""" @@ -400,13 +400,13 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertTrue(ump.state, STATE_OFF) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(STATE_PLAYING, ump.state) + assert STATE_PLAYING == ump.state def test_state_with_children_and_attrs(self): """Test media player with children and master state.""" @@ -416,21 +416,21 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(STATE_OFF, ump.state) + assert STATE_OFF == ump.state self.hass.states.set(self.mock_state_switch_id, STATE_ON) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(STATE_ON, ump.state) + assert STATE_ON == ump.state self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(STATE_PLAYING, ump.state) + assert STATE_PLAYING == ump.state self.hass.states.set(self.mock_state_switch_id, STATE_OFF) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(STATE_OFF, ump.state) + assert STATE_OFF == ump.state def test_volume_level(self): """Test volume level property.""" @@ -440,19 +440,19 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(None, ump.volume_level) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(0, ump.volume_level) + assert 0 == ump.volume_level self.mock_mp_1._volume_level = 1 self.mock_mp_1.schedule_update_ha_state() self.hass.block_till_done() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(1, ump.volume_level) + assert 1 == ump.volume_level def test_media_image_url(self): """Test media_image_url property.""" @@ -463,7 +463,7 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(None, ump.media_image_url) + assert ump.media_image_url is None self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1._media_image_url = test_url @@ -472,7 +472,7 @@ class TestMediaPlayer(unittest.TestCase): 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. - self.assertEqual(self.mock_mp_1.entity_picture, ump.entity_picture) + 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.""" @@ -482,19 +482,19 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertFalse(ump.is_volume_muted) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertFalse(ump.is_volume_muted) + 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertTrue(ump.is_volume_muted) + assert ump.is_volume_muted def test_source_list_children_and_attr(self): """Test source list property w/ children and attrs.""" @@ -502,10 +502,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual("['dvd', 'htpc']", ump.source_list) + assert "['dvd', 'htpc']" == ump.source_list self.hass.states.set(self.mock_source_list_id, ['dvd', 'htpc', 'game']) - self.assertEqual("['dvd', 'htpc', 'game']", ump.source_list) + assert "['dvd', 'htpc', 'game']" == ump.source_list def test_source_children_and_attr(self): """Test source property w/ children and attrs.""" @@ -513,10 +513,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual('dvd', ump.source) + assert 'dvd' == ump.source self.hass.states.set(self.mock_source_id, 'htpc') - self.assertEqual('htpc', ump.source) + assert 'htpc' == ump.source def test_volume_level_children_and_attr(self): """Test volume level property w/ children and attrs.""" @@ -524,10 +524,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertEqual('0', ump.volume_level) + assert '0' == ump.volume_level self.hass.states.set(self.mock_volume_id, 100) - self.assertEqual('100', ump.volume_level) + assert '100' == ump.volume_level def test_is_volume_muted_children_and_attr(self): """Test is volume muted property w/ children and attrs.""" @@ -535,10 +535,10 @@ class TestMediaPlayer(unittest.TestCase): ump = universal.UniversalMediaPlayer(self.hass, **config) - self.assertFalse(ump.is_volume_muted) + assert not ump.is_volume_muted self.hass.states.set(self.mock_mute_switch_id, STATE_ON) - self.assertTrue(ump.is_volume_muted) + assert ump.is_volume_muted def test_supported_features_children_only(self): """Test supported media commands with only children.""" @@ -548,14 +548,14 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(0, ump.supported_features) + assert 0 == ump.supported_features 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() run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() - self.assertEqual(512, ump.supported_features) + assert 512 == ump.supported_features def test_supported_features_children_and_cmds(self): """Test supported media commands with children and attrs.""" @@ -586,7 +586,7 @@ class TestMediaPlayer(unittest.TestCase): | universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE \ | universal.SUPPORT_SELECT_SOURCE | universal.SUPPORT_SHUFFLE_SET - self.assertEqual(check_flags, ump.supported_features) + assert check_flags == ump.supported_features def test_service_call_no_active_child(self): """Test a service call to children with no active child.""" @@ -606,8 +606,8 @@ class TestMediaPlayer(unittest.TestCase): run_coroutine_threadsafe( ump.async_turn_off(), self.hass.loop).result() - self.assertEqual(0, len(self.mock_mp_1.service_calls['turn_off'])) - self.assertEqual(0, len(self.mock_mp_2.service_calls['turn_off'])) + assert 0 == len(self.mock_mp_1.service_calls['turn_off']) + assert 0 == len(self.mock_mp_2.service_calls['turn_off']) def test_service_call_to_child(self): """Test service calls that should be routed to a child.""" @@ -625,88 +625,82 @@ class TestMediaPlayer(unittest.TestCase): run_coroutine_threadsafe( ump.async_turn_off(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_off'])) + assert 1 == len(self.mock_mp_2.service_calls['turn_off']) run_coroutine_threadsafe( ump.async_turn_on(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_on'])) + assert 1 == len(self.mock_mp_2.service_calls['turn_on']) run_coroutine_threadsafe( ump.async_mute_volume(True), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['mute_volume'])) + assert 1 == len(self.mock_mp_2.service_calls['mute_volume']) run_coroutine_threadsafe( ump.async_set_volume_level(0.5), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['set_volume_level'])) + assert 1 == len(self.mock_mp_2.service_calls['set_volume_level']) run_coroutine_threadsafe( ump.async_media_play(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['media_play'])) + assert 1 == len(self.mock_mp_2.service_calls['media_play']) run_coroutine_threadsafe( ump.async_media_pause(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['media_pause'])) + assert 1 == len(self.mock_mp_2.service_calls['media_pause']) run_coroutine_threadsafe( ump.async_media_previous_track(), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['media_previous_track'])) + assert 1 == len(self.mock_mp_2.service_calls['media_previous_track']) run_coroutine_threadsafe( ump.async_media_next_track(), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['media_next_track'])) + assert 1 == len(self.mock_mp_2.service_calls['media_next_track']) run_coroutine_threadsafe( ump.async_media_seek(100), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['media_seek'])) + assert 1 == len(self.mock_mp_2.service_calls['media_seek']) run_coroutine_threadsafe( ump.async_play_media('movie', 'batman'), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['play_media'])) + assert 1 == len(self.mock_mp_2.service_calls['play_media']) run_coroutine_threadsafe( ump.async_volume_up(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_up'])) + assert 1 == len(self.mock_mp_2.service_calls['volume_up']) run_coroutine_threadsafe( ump.async_volume_down(), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_down'])) + assert 1 == len(self.mock_mp_2.service_calls['volume_down']) run_coroutine_threadsafe( ump.async_media_play_pause(), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['media_play_pause'])) + assert 1 == len(self.mock_mp_2.service_calls['media_play_pause']) run_coroutine_threadsafe( ump.async_select_source('dvd'), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['select_source'])) + assert 1 == len(self.mock_mp_2.service_calls['select_source']) run_coroutine_threadsafe( ump.async_clear_playlist(), self.hass.loop).result() - self.assertEqual( - 1, len(self.mock_mp_2.service_calls['clear_playlist'])) + assert 1 == len(self.mock_mp_2.service_calls['clear_playlist']) run_coroutine_threadsafe( ump.async_set_shuffle(True), self.hass.loop).result() - self.assertEqual(1, len(self.mock_mp_2.service_calls['shuffle_set'])) + assert 1 == len(self.mock_mp_2.service_calls['shuffle_set']) def test_service_call_to_command(self): """Test service call to command.""" @@ -727,4 +721,4 @@ class TestMediaPlayer(unittest.TestCase): run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result() run_coroutine_threadsafe(ump.async_turn_off(), self.hass.loop).result() - self.assertEqual(1, len(service)) + assert 1 == len(service) diff --git a/tests/components/media_player/test_yamaha.py b/tests/components/media_player/test_yamaha.py index a55429c0c7b..13d5a785e7f 100644 --- a/tests/components/media_player/test_yamaha.py +++ b/tests/components/media_player/test_yamaha.py @@ -67,7 +67,7 @@ class TestYamahaMediaPlayer(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, mp.DOMAIN, config)) + assert setup_component(self.hass, mp.DOMAIN, config) @patch('rxv.RXV') def test_enable_output(self, mock_rxv): diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index dd3ab2e6f7a..b075b8db063 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -4,7 +4,7 @@ from unittest.mock import patch from homeassistant.components import mqtt from homeassistant.components.mqtt.discovery import async_start, \ - ALREADY_DISCOVERED + ALREADY_DISCOVERED from homeassistant.const import STATE_ON, STATE_OFF from tests.common import async_fire_mqtt_message, mock_coro, MockConfigEntry diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 045a411a271..5d7afbde843 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -71,7 +71,7 @@ class TestMQTTComponent(unittest.TestCase): """Test if client stops on HA stop.""" self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) self.hass.block_till_done() - self.assertTrue(self.hass.data['mqtt'].async_disconnect.called) + assert self.hass.data['mqtt'].async_disconnect.called def test_publish_calls_service(self): """Test the publishing of call to services.""" @@ -81,13 +81,11 @@ class TestMQTTComponent(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - 'test-topic', - self.calls[0][0].data['service_data'][mqtt.ATTR_TOPIC]) - self.assertEqual( - 'test-payload', - self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD]) + assert 1 == len(self.calls) + assert 'test-topic' == \ + self.calls[0][0].data['service_data'][mqtt.ATTR_TOPIC] + assert 'test-payload' == \ + self.calls[0][0].data['service_data'][mqtt.ATTR_PAYLOAD] def test_service_call_without_topic_does_not_publish(self): """Test the service call if topic is missing.""" @@ -96,7 +94,7 @@ class TestMQTTComponent(unittest.TestCase): ATTR_SERVICE: mqtt.SERVICE_PUBLISH }) self.hass.block_till_done() - self.assertTrue(not self.hass.data['mqtt'].async_publish.called) + assert not self.hass.data['mqtt'].async_publish.called def test_service_call_with_template_payload_renders_template(self): """Test the service call with rendered template. @@ -105,9 +103,8 @@ class TestMQTTComponent(unittest.TestCase): """ mqtt.publish_template(self.hass, "test/topic", "{{ 1+1 }}") self.hass.block_till_done() - self.assertTrue(self.hass.data['mqtt'].async_publish.called) - self.assertEqual( - self.hass.data['mqtt'].async_publish.call_args[0][1], "2") + assert self.hass.data['mqtt'].async_publish.called + assert self.hass.data['mqtt'].async_publish.call_args[0][1] == "2" def test_service_call_with_payload_doesnt_render_template(self): """Test the service call with unrendered template. @@ -121,7 +118,7 @@ class TestMQTTComponent(unittest.TestCase): mqtt.ATTR_PAYLOAD: payload, mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template }, blocking=True) - self.assertFalse(self.hass.data['mqtt'].async_publish.called) + assert not self.hass.data['mqtt'].async_publish.called def test_service_call_with_ascii_qos_retain_flags(self): """Test the service call with args that can be misinterpreted. @@ -134,22 +131,26 @@ class TestMQTTComponent(unittest.TestCase): mqtt.ATTR_QOS: '2', mqtt.ATTR_RETAIN: 'no' }, blocking=True) - self.assertTrue(self.hass.data['mqtt'].async_publish.called) - self.assertEqual( - self.hass.data['mqtt'].async_publish.call_args[0][2], 2) - self.assertFalse(self.hass.data['mqtt'].async_publish.call_args[0][3]) + assert self.hass.data['mqtt'].async_publish.called + assert self.hass.data['mqtt'].async_publish.call_args[0][2] == 2 + assert not self.hass.data['mqtt'].async_publish.call_args[0][3] def test_validate_topic(self): """Test topic name/filter validation.""" # Invalid UTF-8, must not contain U+D800 to U+DFFF. - self.assertRaises(vol.Invalid, mqtt.valid_topic, '\ud800') - self.assertRaises(vol.Invalid, mqtt.valid_topic, '\udfff') + with pytest.raises(vol.Invalid): + mqtt.valid_topic('\ud800') + with pytest.raises(vol.Invalid): + mqtt.valid_topic('\udfff') # Topic MUST NOT be empty - self.assertRaises(vol.Invalid, mqtt.valid_topic, '') + with pytest.raises(vol.Invalid): + mqtt.valid_topic('') # Topic MUST NOT be longer than 65535 encoded bytes. - self.assertRaises(vol.Invalid, mqtt.valid_topic, 'ü' * 32768) + with pytest.raises(vol.Invalid): + mqtt.valid_topic('ü' * 32768) # UTF-8 MUST NOT include null character - self.assertRaises(vol.Invalid, mqtt.valid_topic, 'bad\0one') + with pytest.raises(vol.Invalid): + mqtt.valid_topic('bad\0one') # Topics "SHOULD NOT" include these special characters # (not MUST NOT, RFC2119). The receiver MAY close the connection. @@ -163,17 +164,25 @@ class TestMQTTComponent(unittest.TestCase): """Test invalid subscribe topics.""" mqtt.valid_subscribe_topic('#') mqtt.valid_subscribe_topic('sport/#') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'sport/#/') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'foo/bar#') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'foo/#/bar') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('sport/#/') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('foo/bar#') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('foo/#/bar') mqtt.valid_subscribe_topic('+') mqtt.valid_subscribe_topic('+/tennis/#') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'sport+') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'sport+/') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'sport/+1') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'sport/+#') - self.assertRaises(vol.Invalid, mqtt.valid_subscribe_topic, 'bad+topic') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('sport+') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('sport+/') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('sport/+1') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('sport/+#') + with pytest.raises(vol.Invalid): + mqtt.valid_subscribe_topic('bad+topic') mqtt.valid_subscribe_topic('sport/+/player1') mqtt.valid_subscribe_topic('/finance') mqtt.valid_subscribe_topic('+/+') @@ -181,10 +190,14 @@ class TestMQTTComponent(unittest.TestCase): def test_validate_publish_topic(self): """Test invalid publish topics.""" - self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'pub+') - self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'pub/+') - self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, '1#') - self.assertRaises(vol.Invalid, mqtt.valid_publish_topic, 'bad+topic') + with pytest.raises(vol.Invalid): + mqtt.valid_publish_topic('pub+') + with pytest.raises(vol.Invalid): + mqtt.valid_publish_topic('pub/+') + with pytest.raises(vol.Invalid): + mqtt.valid_publish_topic('1#') + with pytest.raises(vol.Invalid): + mqtt.valid_publish_topic('bad+topic') mqtt.valid_publish_topic('//') # Topic names beginning with $ SHOULD NOT be used, but can @@ -218,18 +231,20 @@ class TestMQTTComponent(unittest.TestCase): 'sw_version': '0.1-beta', }) # no identifiers - self.assertRaises(vol.Invalid, mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, { - 'manufacturer': 'Whatever', - 'name': 'Beer', - 'model': 'Glass', - 'sw_version': '0.1-beta', - }) + with pytest.raises(vol.Invalid): + mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA({ + 'manufacturer': 'Whatever', + 'name': 'Beer', + 'model': 'Glass', + 'sw_version': '0.1-beta', + }) # empty identifiers - self.assertRaises(vol.Invalid, mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, { - 'identifiers': [], - 'connections': [], - 'name': 'Beer', - }) + with pytest.raises(vol.Invalid): + mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA({ + 'identifiers': [], + 'connections': [], + 'name': 'Beer', + }) # pylint: disable=invalid-name @@ -253,7 +268,7 @@ class TestMQTTCallbacks(unittest.TestCase): def aiohttp_client_starts_on_home_assistant_mqtt_setup(self): """Test if client is connected after mqtt init on bootstrap.""" - self.assertEqual(self.hass.data['mqtt']._mqttc.connect.call_count, 1) + assert self.hass.data['mqtt']._mqttc.connect.call_count == 1 def test_receiving_non_utf8_message_gets_logged(self): """Test receiving a non utf8 encoded message.""" @@ -263,10 +278,10 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', b'\x9a') self.hass.block_till_done() - self.assertIn( - "WARNING:homeassistant.components.mqtt:Can't decode payload " - "b'\\x9a' on test-topic with encoding utf-8", - test_handle.output[0]) + assert \ + "WARNING:homeassistant.components.mqtt:Can't decode payload " \ + "b'\\x9a' on test-topic with encoding utf-8" in \ + test_handle.output[0] def test_all_subscriptions_run_when_decode_fails(self): """Test all other subscriptions still run when decode fails for one.""" @@ -277,7 +292,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', '°C') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_subscribe_topic(self): """Test the subscription of a topic.""" @@ -286,16 +301,16 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'test-topic' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] unsub() fire_mqtt_message(self.hass, 'test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) + assert 1 == len(self.calls) def test_subscribe_topic_not_match(self): """Test if subscribed topic is not a match.""" @@ -304,7 +319,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_level_wildcard(self): """Test the subscription of wildcard topics.""" @@ -313,9 +328,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic/bier/on', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'test-topic/bier/on' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_level_wildcard_no_subtree_match(self): """Test the subscription of wildcard topics.""" @@ -324,7 +339,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_level_wildcard_root_topic_no_subtree_match(self): """Test the subscription of wildcard topics.""" @@ -333,7 +348,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic-123', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_subtree_wildcard_subtree_topic(self): """Test the subscription of wildcard topics.""" @@ -342,9 +357,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic/bier/on', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'test-topic/bier/on' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_subtree_wildcard_root_topic(self): """Test the subscription of wildcard topics.""" @@ -353,9 +368,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('test-topic', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'test-topic' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_subtree_wildcard_no_match(self): """Test the subscription of wildcard topics.""" @@ -364,7 +379,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_level_wildcard_and_wildcard_root_topic(self): """Test the subscription of wildcard topics.""" @@ -373,9 +388,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'hi/test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('hi/test-topic', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'hi/test-topic' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_level_wildcard_and_wildcard_subtree_topic(self): """Test the subscription of wildcard topics.""" @@ -384,9 +399,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'hi/test-topic/here-iam', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('hi/test-topic/here-iam', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert 'hi/test-topic/here-iam' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_level_wildcard_and_wildcard_level_no_match(self): """Test the subscription of wildcard topics.""" @@ -395,7 +410,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'hi/here-iam/test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_level_wildcard_and_wildcard_no_match(self): """Test the subscription of wildcard topics.""" @@ -404,7 +419,7 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, 'hi/another-test-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) + assert 0 == len(self.calls) def test_subscribe_topic_sys_root(self): """Test the subscription of $ root topics.""" @@ -413,9 +428,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, '$test-topic/subtree/on', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('$test-topic/subtree/on', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert '$test-topic/subtree/on' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_sys_root_and_wildcard_topic(self): """Test the subscription of $ root and wildcard topics.""" @@ -424,9 +439,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, '$test-topic/some-topic', 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('$test-topic/some-topic', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert '$test-topic/some-topic' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_topic_sys_root_and_wildcard_subtree_topic(self): """Test the subscription of $ root and wildcard subtree topics.""" @@ -436,9 +451,9 @@ class TestMQTTCallbacks(unittest.TestCase): 'test-payload') self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('$test-topic/subtree/some-topic', self.calls[0][0]) - self.assertEqual('test-payload', self.calls[0][1]) + assert 1 == len(self.calls) + assert '$test-topic/subtree/some-topic' == self.calls[0][0] + assert 'test-payload' == self.calls[0][1] def test_subscribe_special_characters(self): """Test the subscription to topics with special characters.""" @@ -449,9 +464,9 @@ class TestMQTTCallbacks(unittest.TestCase): fire_mqtt_message(self.hass, topic, payload) self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual(topic, self.calls[0][0]) - self.assertEqual(payload, self.calls[0][1]) + assert 1 == len(self.calls) + assert topic == self.calls[0][0] + assert payload == self.calls[0][1] def test_mqtt_failed_connection_results_in_disconnect(self): """Test if connection failure leads to disconnect.""" @@ -459,12 +474,12 @@ class TestMQTTCallbacks(unittest.TestCase): self.hass.data['mqtt']._mqttc = mock.MagicMock() self.hass.data['mqtt']._mqtt_on_connect( None, {'topics': {}}, 0, result_code) - self.assertTrue(self.hass.data['mqtt']._mqttc.disconnect.called) + assert self.hass.data['mqtt']._mqttc.disconnect.called def test_mqtt_disconnect_tries_no_reconnect_on_stop(self): """Test the disconnect tries.""" self.hass.data['mqtt']._mqtt_on_disconnect(None, None, 0) - self.assertFalse(self.hass.data['mqtt']._mqttc.reconnect.called) + assert not self.hass.data['mqtt']._mqttc.reconnect.called @mock.patch('homeassistant.components.mqtt.time.sleep') def test_mqtt_disconnect_tries_reconnect(self, mock_sleep): @@ -476,11 +491,10 @@ class TestMQTTCallbacks(unittest.TestCase): ] self.hass.data['mqtt']._mqttc.reconnect.side_effect = [1, 1, 1, 0] self.hass.data['mqtt']._mqtt_on_disconnect(None, None, 1) - self.assertTrue(self.hass.data['mqtt']._mqttc.reconnect.called) - self.assertEqual( - 4, len(self.hass.data['mqtt']._mqttc.reconnect.mock_calls)) - self.assertEqual([1, 2, 4], - [call[1][0] for call in mock_sleep.mock_calls]) + assert self.hass.data['mqtt']._mqttc.reconnect.called + assert 4 == len(self.hass.data['mqtt']._mqttc.reconnect.mock_calls) + assert [1, 2, 4] == \ + [call[1][0] for call in mock_sleep.mock_calls] def test_retained_message_on_subscribe_received(self): """Test every subscriber receives retained message on subscribe.""" @@ -493,34 +507,34 @@ class TestMQTTCallbacks(unittest.TestCase): calls_a = mock.MagicMock() mqtt.subscribe(self.hass, 'test/state', calls_a) self.hass.block_till_done() - self.assertTrue(calls_a.called) + assert calls_a.called calls_b = mock.MagicMock() mqtt.subscribe(self.hass, 'test/state', calls_b) self.hass.block_till_done() - self.assertTrue(calls_b.called) + assert calls_b.called def test_not_calling_unsubscribe_with_active_subscribers(self): """Test not calling unsubscribe() when other subscribers are active.""" unsub = mqtt.subscribe(self.hass, 'test/state', None) mqtt.subscribe(self.hass, 'test/state', None) self.hass.block_till_done() - self.assertTrue(self.hass.data['mqtt']._mqttc.subscribe.called) + assert self.hass.data['mqtt']._mqttc.subscribe.called unsub() self.hass.block_till_done() - self.assertFalse(self.hass.data['mqtt']._mqttc.unsubscribe.called) + assert not self.hass.data['mqtt']._mqttc.unsubscribe.called def test_restore_subscriptions_on_reconnect(self): """Test subscriptions are restored on reconnect.""" mqtt.subscribe(self.hass, 'test/state', None) self.hass.block_till_done() - self.assertEqual(self.hass.data['mqtt']._mqttc.subscribe.call_count, 1) + assert self.hass.data['mqtt']._mqttc.subscribe.call_count == 1 self.hass.data['mqtt']._mqtt_on_disconnect(None, None, 0) self.hass.data['mqtt']._mqtt_on_connect(None, None, None, 0) self.hass.block_till_done() - self.assertEqual(self.hass.data['mqtt']._mqttc.subscribe.call_count, 2) + assert self.hass.data['mqtt']._mqttc.subscribe.call_count == 2 def test_restore_all_active_subscriptions_on_reconnect(self): """Test active subscriptions are restored correctly on reconnect.""" @@ -538,21 +552,21 @@ class TestMQTTCallbacks(unittest.TestCase): mock.call('test/state', 0), mock.call('test/state', 1) ] - self.assertEqual(self.hass.data['mqtt']._mqttc.subscribe.mock_calls, - expected) + assert self.hass.data['mqtt']._mqttc.subscribe.mock_calls == \ + expected unsub() self.hass.block_till_done() - self.assertEqual(self.hass.data['mqtt']._mqttc.unsubscribe.call_count, - 0) + assert self.hass.data['mqtt']._mqttc.unsubscribe.call_count == \ + 0 self.hass.data['mqtt']._mqtt_on_disconnect(None, None, 0) self.hass.data['mqtt']._mqtt_on_connect(None, None, None, 0) self.hass.block_till_done() expected.append(mock.call('test/state', 1)) - self.assertEqual(self.hass.data['mqtt']._mqttc.subscribe.mock_calls, - expected) + assert self.hass.data['mqtt']._mqttc.subscribe.mock_calls == \ + expected @asyncio.coroutine diff --git a/tests/components/notify/test_apns.py b/tests/components/notify/test_apns.py index dc120dc4ff6..9964a58cd24 100644 --- a/tests/components/notify/test_apns.py +++ b/tests/components/notify/test_apns.py @@ -120,11 +120,10 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call(notify.DOMAIN, - 'apns_test_app', - {'push_id': '1234', - 'name': 'test device'}, - blocking=True)) + assert self.hass.services.call(notify.DOMAIN, 'apns_test_app', { + 'push_id': '1234', + 'name': 'test device' + }, blocking=True) assert len(written_devices) == 1 assert written_devices[0].name == 'test device' @@ -156,16 +155,16 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call(notify.DOMAIN, 'apns_test_app', - {'push_id': '1234'}, - blocking=True)) + assert self.hass.services.call(notify.DOMAIN, 'apns_test_app', { + 'push_id': '1234' + }, blocking=True) devices = {dev.push_id: dev for dev in written_devices} test_device = devices.get('1234') - self.assertIsNotNone(test_device) - self.assertIsNone(test_device.name) + assert test_device is not None + assert test_device.name is None @patch('homeassistant.components.notify.apns._write_device') def test_update_existing_device(self, mock_write): @@ -192,21 +191,20 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call(notify.DOMAIN, - 'apns_test_app', - {'push_id': '1234', - 'name': 'updated device 1'}, - blocking=True)) + assert self.hass.services.call(notify.DOMAIN, 'apns_test_app', { + 'push_id': '1234', + 'name': 'updated device 1' + }, blocking=True) devices = {dev.push_id: dev for dev in written_devices} test_device_1 = devices.get('1234') test_device_2 = devices.get('5678') - self.assertIsNotNone(test_device_1) - self.assertIsNotNone(test_device_2) + assert test_device_1 is not None + assert test_device_2 is not None - self.assertEqual('updated device 1', test_device_1.name) + assert 'updated device 1' == test_device_1.name @patch('homeassistant.components.notify.apns._write_device') def test_update_existing_device_with_tracking_id(self, mock_write): @@ -235,24 +233,23 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call(notify.DOMAIN, - 'apns_test_app', - {'push_id': '1234', - 'name': 'updated device 1'}, - blocking=True)) + assert self.hass.services.call(notify.DOMAIN, 'apns_test_app', { + 'push_id': '1234', + 'name': 'updated device 1' + }, blocking=True) devices = {dev.push_id: dev for dev in written_devices} test_device_1 = devices.get('1234') test_device_2 = devices.get('5678') - self.assertIsNotNone(test_device_1) - self.assertIsNotNone(test_device_2) + assert test_device_1 is not None + assert test_device_2 is not None - self.assertEqual('tracking123', - test_device_1.tracking_device_id) - self.assertEqual('tracking456', - test_device_2.tracking_device_id) + assert 'tracking123' == \ + test_device_1.tracking_device_id + assert 'tracking456' == \ + test_device_2.tracking_device_id @patch('apns2.client.APNsClient') def test_send(self, mock_client): @@ -266,25 +263,25 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call( + assert self.hass.services.call( 'notify', 'test_app', {'message': 'Hello', 'data': { 'badge': 1, 'sound': 'test.mp3', 'category': 'testing'}}, - blocking=True)) + blocking=True) - self.assertTrue(send.called) - self.assertEqual(1, len(send.mock_calls)) + assert send.called + assert 1 == len(send.mock_calls) target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - self.assertEqual('1234', target) - self.assertEqual('Hello', payload.alert) - self.assertEqual(1, payload.badge) - self.assertEqual('test.mp3', payload.sound) - self.assertEqual('testing', payload.category) + assert '1234' == target + assert 'Hello' == payload.alert + assert 1 == payload.badge + assert 'test.mp3' == payload.sound + assert 'testing' == payload.category @patch('apns2.client.APNsClient') def test_send_when_disabled(self, mock_client): @@ -301,15 +298,15 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call( + assert self.hass.services.call( 'notify', 'test_app', {'message': 'Hello', 'data': { 'badge': 1, 'sound': 'test.mp3', 'category': 'testing'}}, - blocking=True)) + blocking=True) - self.assertFalse(send.called) + assert not send.called @patch('apns2.client.APNsClient') def test_send_with_state(self, mock_client): @@ -346,14 +343,14 @@ class TestApns(unittest.TestCase): notify_service.send_message(message='Hello', target='home') - self.assertTrue(send.called) - self.assertEqual(1, len(send.mock_calls)) + assert send.called + assert 1 == len(send.mock_calls) target = send.mock_calls[0][1][0] payload = send.mock_calls[0][1][1] - self.assertEqual('5678', target) - self.assertEqual('Hello', payload.alert) + assert '5678' == target + assert 'Hello' == payload.alert @patch('apns2.client.APNsClient') @patch('homeassistant.components.notify.apns._write_device') @@ -386,15 +383,15 @@ class TestApns(unittest.TestCase): Mock(return_value=yaml_file)): self._setup_notify() - self.assertTrue(self.hass.services.call('notify', 'test_app', - {'message': 'Hello'}, - blocking=True)) + assert self.hass.services.call('notify', 'test_app', + {'message': 'Hello'}, + blocking=True) devices = {dev.push_id: dev for dev in written_devices} test_device_1 = devices.get('1234') - self.assertIsNotNone(test_device_1) - self.assertEqual(True, test_device_1.disabled) + assert test_device_1 is not None + assert test_device_1.disabled is True def test_write_device(): diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py index 57933063ba1..66aa451d389 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/notify/test_command_line.py @@ -49,39 +49,35 @@ class TestCommandLine(unittest.TestCase): filename = os.path.join(tempdirname, 'message.txt') message = 'one, two, testing, testing' with assert_setup_component(1) as handle_config: - self.assertTrue(setup_component(self.hass, notify.DOMAIN, { + assert setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'command_line', 'command': 'echo $(cat) > {}'.format(filename) } - })) + }) assert handle_config[notify.DOMAIN] - self.assertTrue( - self.hass.services.call('notify', 'test', {'message': message}, - blocking=True) - ) + assert self.hass.services.call( + 'notify', 'test', {'message': message}, blocking=True) with open(filename) as fil: # the echo command adds a line break - self.assertEqual(fil.read(), "{}\n".format(message)) + assert fil.read() == "{}\n".format(message) @patch('homeassistant.components.notify.command_line._LOGGER.error') def test_error_for_none_zero_exit_code(self, mock_error): """Test if an error is logged for non zero exit codes.""" with assert_setup_component(1) as handle_config: - self.assertTrue(setup_component(self.hass, notify.DOMAIN, { + assert setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'command_line', 'command': 'echo $(cat); exit 1' } - })) + }) assert handle_config[notify.DOMAIN] - self.assertTrue( - self.hass.services.call('notify', 'test', {'message': 'error'}, - blocking=True) - ) - self.assertEqual(1, mock_error.call_count) + assert self.hass.services.call('notify', 'test', {'message': 'error'}, + blocking=True) + assert 1 == mock_error.call_count diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 3be2ddcb86b..270736d1b86 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -56,10 +56,9 @@ class TestNotifyDemo(unittest.TestCase): self._setup_notify() self.hass.block_till_done() assert mock_demo_get_service.called - self.assertEqual( - log_handle.output, + assert log_handle.output == \ ['ERROR:homeassistant.components.notify:' - 'Failed to initialize notification service demo']) + 'Failed to initialize notification service demo'] @patch('homeassistant.components.notify.demo.get_service', autospec=True) def test_discover_notify(self, mock_demo_get_service): @@ -83,7 +82,7 @@ class TestNotifyDemo(unittest.TestCase): self._setup_notify() common.send_message(self.hass, None) self.hass.block_till_done() - self.assertTrue(len(self.events) == 0) + assert len(self.events) == 0 def test_sending_templated_message(self): """Send a templated message.""" @@ -93,8 +92,8 @@ class TestNotifyDemo(unittest.TestCase): '{{ states.sensor.temperature.name }}') self.hass.block_till_done() last_event = self.events[-1] - self.assertEqual(last_event.data[notify.ATTR_TITLE], 'temperature') - self.assertEqual(last_event.data[notify.ATTR_MESSAGE], '10') + assert last_event.data[notify.ATTR_TITLE] == 'temperature' + assert last_event.data[notify.ATTR_MESSAGE] == '10' def test_method_forwards_correct_data(self): """Test that all data from the service gets forwarded to service.""" @@ -102,7 +101,7 @@ class TestNotifyDemo(unittest.TestCase): common.send_message(self.hass, 'my message', 'my title', {'hello': 'world'}) self.hass.block_till_done() - self.assertTrue(len(self.events) == 1) + assert len(self.events) == 1 data = self.events[0].data assert { 'message': 'my message', @@ -128,7 +127,7 @@ class TestNotifyDemo(unittest.TestCase): script.call_from_config(self.hass, conf) self.hass.block_till_done() - self.assertTrue(len(self.events) == 1) + assert len(self.events) == 1 assert { 'message': 'Test 123 4', 'data': { @@ -158,7 +157,7 @@ class TestNotifyDemo(unittest.TestCase): script.call_from_config(self.hass, conf) self.hass.block_till_done() - self.assertTrue(len(self.events) == 1) + assert len(self.events) == 1 assert { 'message': 'Test 123 4', 'title': 'Test', @@ -171,9 +170,9 @@ class TestNotifyDemo(unittest.TestCase): def test_targets_are_services(self): """Test that all targets are exposed as individual services.""" self._setup_notify() - self.assertIsNotNone(self.hass.services.has_service("notify", "demo")) + assert self.hass.services.has_service("notify", "demo") is not None service = "demo_test_target_name" - self.assertIsNotNone(self.hass.services.has_service("notify", service)) + assert self.hass.services.has_service("notify", service) is not None def test_messages_to_targets_route(self): """Test message routing to specific target services.""" diff --git a/tests/components/notify/test_facebook.py b/tests/components/notify/test_facebook.py index b94a4c38a40..a74395d5a5e 100644 --- a/tests/components/notify/test_facebook.py +++ b/tests/components/notify/test_facebook.py @@ -26,17 +26,17 @@ class TestFacebook(unittest.TestCase): target = ["+15555551234"] self.facebook.send_message(message=message, target=target) - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) + assert mock.called + assert mock.call_count == 1 expected_body = { "recipient": {"phone_number": target[0]}, "message": {"text": message} } - self.assertEqual(mock.last_request.json(), expected_body) + assert mock.last_request.json() == expected_body expected_params = {"access_token": ["page-access-token"]} - self.assertEqual(mock.last_request.qs, expected_params) + assert mock.last_request.qs == expected_params @requests_mock.Mocker() def test_sending_multiple_messages(self, mock): @@ -51,8 +51,8 @@ class TestFacebook(unittest.TestCase): targets = ["+15555551234", "+15555551235"] self.facebook.send_message(message=message, target=targets) - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 2) + assert mock.called + assert mock.call_count == 2 for idx, target in enumerate(targets): request = mock.request_history[idx] @@ -60,10 +60,10 @@ class TestFacebook(unittest.TestCase): "recipient": {"phone_number": target}, "message": {"text": message} } - self.assertEqual(request.json(), expected_body) + assert request.json() == expected_body expected_params = {"access_token": ["page-access-token"]} - self.assertEqual(request.qs, expected_params) + assert request.qs == expected_params @requests_mock.Mocker() def test_send_message_attachment(self, mock): @@ -84,17 +84,17 @@ class TestFacebook(unittest.TestCase): target = ["+15555551234"] self.facebook.send_message(message=message, data=data, target=target) - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) + assert mock.called + assert mock.call_count == 1 expected_body = { "recipient": {"phone_number": target[0]}, "message": data } - self.assertEqual(mock.last_request.json(), expected_body) + assert mock.last_request.json() == expected_body expected_params = {"access_token": ["page-access-token"]} - self.assertEqual(mock.last_request.qs, expected_params) + assert mock.last_request.qs == expected_params @requests_mock.Mocker() def test_send_targetless_message(self, mock): @@ -106,7 +106,7 @@ class TestFacebook(unittest.TestCase): ) self.facebook.send_message(message="goin nowhere") - self.assertFalse(mock.called) + assert not mock.called @requests_mock.Mocker() def test_send_message_with_400(self, mock): @@ -125,5 +125,5 @@ class TestFacebook(unittest.TestCase): } ) self.facebook.send_message(message="nope!", target=["+15555551234"]) - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) + assert mock.called + assert mock.call_count == 1 diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py index fb4ab42e97b..e67ed532604 100644 --- a/tests/components/notify/test_file.py +++ b/tests/components/notify/test_file.py @@ -40,14 +40,14 @@ class TestNotifyFile(unittest.TestCase): filename = 'mock_file' message = 'one, two, testing, testing' with assert_setup_component(1) as handle_config: - self.assertTrue(setup_component(self.hass, notify.DOMAIN, { + assert setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'file', 'filename': filename, 'timestamp': timestamp, } - })) + }) assert handle_config[notify.DOMAIN] m_open = mock_open() @@ -68,21 +68,17 @@ class TestNotifyFile(unittest.TestCase): blocking=True) full_filename = os.path.join(self.hass.config.path(), filename) - self.assertEqual(m_open.call_count, 1) - self.assertEqual(m_open.call_args, call(full_filename, 'a')) + assert m_open.call_count == 1 + assert m_open.call_args == call(full_filename, 'a') - self.assertEqual(m_open.return_value.write.call_count, 2) + assert m_open.return_value.write.call_count == 2 if not timestamp: - self.assertEqual( - m_open.return_value.write.call_args_list, + assert m_open.return_value.write.call_args_list == \ [call(title), call('{}\n'.format(message))] - ) else: - self.assertEqual( - m_open.return_value.write.call_args_list, + assert m_open.return_value.write.call_args_list == \ [call(title), call('{} {}\n'.format( dt_util.utcnow().isoformat(), message))] - ) def test_notify_file(self): """Test the notify file output without timestamp.""" diff --git a/tests/components/notify/test_pushbullet.py b/tests/components/notify/test_pushbullet.py index 73b9aa4fbeb..a936a11aa9f 100644 --- a/tests/components/notify/test_pushbullet.py +++ b/tests/components/notify/test_pushbullet.py @@ -68,13 +68,13 @@ class TestPushBullet(unittest.TestCase): 'message': 'Test Message'} self.hass.services.call(notify.DOMAIN, 'test', data) self.hass.block_till_done() - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) + assert mock.called + assert mock.call_count == 1 expected_body = {'body': 'Test Message', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.last_request.json(), expected_body) + assert mock.last_request.json() == expected_body @requests_mock.Mocker() @patch.object(PushBullet, '_get_data', @@ -99,14 +99,14 @@ class TestPushBullet(unittest.TestCase): 'target': ['device/DESKTOP']} self.hass.services.call(notify.DOMAIN, 'test', data) self.hass.block_till_done() - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) + assert mock.called + assert mock.call_count == 1 expected_body = {'body': 'Test Message', 'device_iden': 'identity1', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.last_request.json(), expected_body) + assert mock.last_request.json() == expected_body @requests_mock.Mocker() @patch.object(PushBullet, '_get_data', @@ -131,20 +131,20 @@ class TestPushBullet(unittest.TestCase): 'target': ['device/DESKTOP', 'device/My iPhone']} self.hass.services.call(notify.DOMAIN, 'test', data) self.hass.block_till_done() - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 2) - self.assertEqual(len(mock.request_history), 2) + assert mock.called + assert mock.call_count == 2 + assert len(mock.request_history) == 2 expected_body = {'body': 'Test Message', 'device_iden': 'identity1', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.request_history[0].json(), expected_body) + assert mock.request_history[0].json() == expected_body expected_body = {'body': 'Test Message', 'device_iden': 'identity2', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.request_history[1].json(), expected_body) + assert mock.request_history[1].json() == expected_body @requests_mock.Mocker() @patch.object(PushBullet, '_get_data', @@ -169,15 +169,15 @@ class TestPushBullet(unittest.TestCase): 'target': ['email/user@host.net']} self.hass.services.call(notify.DOMAIN, 'test', data) self.hass.block_till_done() - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 1) - self.assertEqual(len(mock.request_history), 1) + assert mock.called + assert mock.call_count == 1 + assert len(mock.request_history) == 1 expected_body = {'body': 'Test Message', 'email': 'user@host.net', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.request_history[0].json(), expected_body) + assert mock.request_history[0].json() == expected_body @requests_mock.Mocker() @patch.object(PushBullet, '_get_data', @@ -202,20 +202,20 @@ class TestPushBullet(unittest.TestCase): 'target': ['device/DESKTOP', 'email/user@host.net']} self.hass.services.call(notify.DOMAIN, 'test', data) self.hass.block_till_done() - self.assertTrue(mock.called) - self.assertEqual(mock.call_count, 2) - self.assertEqual(len(mock.request_history), 2) + assert mock.called + assert mock.call_count == 2 + assert len(mock.request_history) == 2 expected_body = {'body': 'Test Message', 'device_iden': 'identity1', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.request_history[0].json(), expected_body) + assert mock.request_history[0].json() == expected_body expected_body = {'body': 'Test Message', 'email': 'user@host.net', 'title': 'Test Title', 'type': 'note'} - self.assertEqual(mock.request_history[1].json(), expected_body) + assert mock.request_history[1].json() == expected_body @requests_mock.Mocker() @patch.object(PushBullet, '_get_data', diff --git a/tests/components/notify/test_smtp.py b/tests/components/notify/test_smtp.py index fca0e3c79e3..fa6c5003288 100644 --- a/tests/components/notify/test_smtp.py +++ b/tests/components/notify/test_smtp.py @@ -5,6 +5,7 @@ from unittest.mock import patch from homeassistant.components.notify import smtp from tests.common import get_test_home_assistant +import re class MockSMTP(smtp.MailNotificationService): @@ -45,14 +46,14 @@ class TestNotifySmtp(unittest.TestCase): 'Message-Id: <[^@]+@[^>]+>\n' '\n' 'Test msg$') - self.assertRegex(msg, expected) + assert re.search(expected, msg) @patch('email.utils.make_msgid', return_value='') def test_mixed_email(self, mock_make_msgid): """Test build of mixed text email behavior.""" msg = self.mailer.send_message('Test msg', data={'images': ['test.jpg']}) - self.assertTrue('Content-Type: multipart/related' in msg) + assert 'Content-Type: multipart/related' in msg @patch('email.utils.make_msgid', return_value='') def test_html_email(self, mock_make_msgid): @@ -73,4 +74,4 @@ class TestNotifySmtp(unittest.TestCase): msg = self.mailer.send_message('Test msg', data={'html': html, 'images': ['test.jpg']}) - self.assertTrue('Content-Type: multipart/related' in msg) + assert 'Content-Type: multipart/related' in msg diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index f33236f0ceb..4249a8abfb9 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -124,13 +124,13 @@ class TestRecorderPurge(unittest.TestCase): # make sure we start with 7 states with session_scope(hass=self.hass) as session: states = session.query(States) - self.assertEqual(states.count(), 7) + assert states.count() == 7 # run purge_old_data() purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) # we should only have 3 states left after purging - self.assertEqual(states.count(), 3) + assert states.count() == 3 def test_purge_old_events(self): """Test deleting old events.""" @@ -139,13 +139,13 @@ class TestRecorderPurge(unittest.TestCase): with session_scope(hass=self.hass) as session: events = session.query(Events).filter( Events.event_type.like("EVENT_TEST%")) - self.assertEqual(events.count(), 7) + assert events.count() == 7 # run purge_old_data() purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) # no state to protect, now we should only have 2 events left - self.assertEqual(events.count(), 2) + assert events.count() == 2 def test_purge_method(self): """Test purge method.""" @@ -156,11 +156,11 @@ class TestRecorderPurge(unittest.TestCase): # make sure we start with 6 states with session_scope(hass=self.hass) as session: states = session.query(States) - self.assertEqual(states.count(), 7) + assert states.count() == 7 events = session.query(Events).filter( Events.event_type.like("EVENT_TEST%")) - self.assertEqual(events.count(), 7) + assert events.count() == 7 self.hass.data[DATA_INSTANCE].block_till_done() @@ -172,8 +172,8 @@ class TestRecorderPurge(unittest.TestCase): self.hass.data[DATA_INSTANCE].block_till_done() # only purged old events - self.assertEqual(states.count(), 5) - self.assertEqual(events.count(), 5) + assert states.count() == 5 + assert events.count() == 5 # run purge method - correct service data self.hass.services.call('recorder', 'purge', @@ -184,19 +184,19 @@ class TestRecorderPurge(unittest.TestCase): self.hass.data[DATA_INSTANCE].block_till_done() # we should only have 3 states left after purging - self.assertEqual(states.count(), 3) + assert states.count() == 3 # the protected state is among them - self.assertTrue('iamprotected' in ( - state.state for state in states)) + assert 'iamprotected' in ( + state.state for state in states) # now we should only have 3 events left - self.assertEqual(events.count(), 3) + assert events.count() == 3 # and the protected event is among them - self.assertTrue('EVENT_TEST_FOR_PROTECTED' in ( - event.event_type for event in events.all())) - self.assertFalse('EVENT_TEST_PURGE' in ( + assert 'EVENT_TEST_FOR_PROTECTED' in ( + event.event_type for event in events.all()) + assert not ('EVENT_TEST_PURGE' in ( event.event_type for event in events.all())) # run purge method - correct service data, with repack @@ -207,5 +207,5 @@ class TestRecorderPurge(unittest.TestCase): service_data=service_data) self.hass.block_till_done() self.hass.data[DATA_INSTANCE].block_till_done() - self.assertEqual(mock_logger.debug.mock_calls[4][1][0], - "Vacuuming SQLite to free space") + assert mock_logger.debug.mock_calls[4][1][0] == \ + "Vacuuming SQLite to free space" diff --git a/tests/components/remote/test_demo.py b/tests/components/remote/test_demo.py index fbf7230c237..c68e34ddc18 100644 --- a/tests/components/remote/test_demo.py +++ b/tests/components/remote/test_demo.py @@ -19,9 +19,9 @@ class TestDemoRemote(unittest.TestCase): def setUp(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(setup_component(self.hass, remote.DOMAIN, {'remote': { + assert setup_component(self.hass, remote.DOMAIN, {'remote': { 'platform': 'demo', - }})) + }}) # pylint: disable=invalid-name def tearDown(self): @@ -33,21 +33,20 @@ class TestDemoRemote(unittest.TestCase): common.turn_on(self.hass, entity_id=ENTITY_ID) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ID) - self.assertEqual(state.state, STATE_ON) + assert state.state == STATE_ON common.turn_off(self.hass, entity_id=ENTITY_ID) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ID) - self.assertEqual(state.state, STATE_OFF) + assert state.state == STATE_OFF common.turn_on(self.hass, entity_id=ENTITY_ID) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ID) - self.assertEqual(state.state, STATE_ON) + assert state.state == STATE_ON common.send_command(self.hass, 'test', entity_id=ENTITY_ID) self.hass.block_till_done() state = self.hass.states.get(ENTITY_ID) - self.assertEqual( - state.attributes, - {'friendly_name': 'Remote One', 'last_command_sent': 'test'}) + assert state.attributes == \ + {'friendly_name': 'Remote One', 'last_command_sent': 'test'} diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index 21a083e3b9a..2315dc1cf64 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -3,7 +3,6 @@ import unittest -from homeassistant.setup import setup_component from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, SERVICE_TURN_ON, SERVICE_TURN_OFF) @@ -32,16 +31,16 @@ class TestRemote(unittest.TestCase): def test_is_on(self): """Test is_on.""" self.hass.states.set('remote.test', STATE_ON) - self.assertTrue(remote.is_on(self.hass, 'remote.test')) + assert remote.is_on(self.hass, 'remote.test') self.hass.states.set('remote.test', STATE_OFF) - self.assertFalse(remote.is_on(self.hass, 'remote.test')) + assert not remote.is_on(self.hass, 'remote.test') self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_ON) - self.assertTrue(remote.is_on(self.hass)) + assert remote.is_on(self.hass) self.hass.states.set(remote.ENTITY_ID_ALL_REMOTES, STATE_OFF) - self.assertFalse(remote.is_on(self.hass)) + assert not remote.is_on(self.hass) def test_turn_on(self): """Test turn_on.""" @@ -54,10 +53,10 @@ class TestRemote(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(turn_on_calls)) + assert 1 == len(turn_on_calls) call = turn_on_calls[-1] - self.assertEqual(remote.DOMAIN, call.domain) + assert remote.DOMAIN == call.domain def test_turn_off(self): """Test turn_off.""" @@ -69,12 +68,12 @@ class TestRemote(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(turn_off_calls)) + assert 1 == len(turn_off_calls) call = turn_off_calls[-1] - self.assertEqual(remote.DOMAIN, call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) + assert remote.DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert 'entity_id_val' == call.data[ATTR_ENTITY_ID] def test_send_command(self): """Test send_command.""" @@ -88,14 +87,9 @@ class TestRemote(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(send_command_calls)) + assert 1 == len(send_command_calls) call = send_command_calls[-1] - self.assertEqual(remote.DOMAIN, call.domain) - self.assertEqual(SERVICE_SEND_COMMAND, call.service) - self.assertEqual('entity_id_val', call.data[ATTR_ENTITY_ID]) - - def test_services(self): - """Test the provided services.""" - self.assertTrue(setup_component(self.hass, remote.DOMAIN, - TEST_PLATFORM)) + assert remote.DOMAIN == call.domain + assert SERVICE_SEND_COMMAND == call.service + assert 'entity_id_val' == call.data[ATTR_ENTITY_ID] diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 81ab5f89c25..df96b19f351 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -21,9 +21,9 @@ class TestScene(unittest.TestCase): test_light = loader.get_component(self.hass, 'light.test') test_light.init() - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: {'platform': 'test'} - })) + }) self.light_1, self.light_2 = test_light.DEVICES[0:2] @@ -32,8 +32,8 @@ class TestScene(unittest.TestCase): self.hass.block_till_done() - self.assertFalse(self.light_1.is_on) - self.assertFalse(self.light_2.is_on) + assert not self.light_1.is_on + assert not self.light_2.is_on def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -60,7 +60,7 @@ class TestScene(unittest.TestCase): 'state': 'on', 'brightness': 100, } - self.assertTrue(setup_component(self.hass, scene.DOMAIN, { + assert setup_component(self.hass, scene.DOMAIN, { 'scene': [{ 'name': 'test', 'entities': { @@ -68,17 +68,15 @@ class TestScene(unittest.TestCase): self.light_2.entity_id: entity_state, } }] - })) + }) common.activate(self.hass, 'scene.test') self.hass.block_till_done() - self.assertTrue(self.light_1.is_on) - self.assertTrue(self.light_2.is_on) - self.assertEqual( - 100, self.light_1.last_call('turn_on')[1].get('brightness')) - self.assertEqual( - 100, self.light_2.last_call('turn_on')[1].get('brightness')) + assert self.light_1.is_on + assert self.light_2.is_on + assert 100 == self.light_1.last_call('turn_on')[1].get('brightness') + assert 100 == self.light_2.last_call('turn_on')[1].get('brightness') def test_config_yaml_bool(self): """Test parsing of booleans in yaml config.""" @@ -95,18 +93,17 @@ class TestScene(unittest.TestCase): with io.StringIO(config) as file: doc = yaml.yaml.safe_load(file) - self.assertTrue(setup_component(self.hass, scene.DOMAIN, doc)) + assert setup_component(self.hass, scene.DOMAIN, doc) common.activate(self.hass, 'scene.test') self.hass.block_till_done() - self.assertTrue(self.light_1.is_on) - self.assertTrue(self.light_2.is_on) - self.assertEqual( - 100, self.light_2.last_call('turn_on')[1].get('brightness')) + assert self.light_1.is_on + assert self.light_2.is_on + assert 100 == self.light_2.last_call('turn_on')[1].get('brightness') def test_activate_scene(self): """Test active scene.""" - self.assertTrue(setup_component(self.hass, scene.DOMAIN, { + assert setup_component(self.hass, scene.DOMAIN, { 'scene': [{ 'name': 'test', 'entities': { @@ -117,12 +114,11 @@ class TestScene(unittest.TestCase): } } }] - })) + }) common.activate(self.hass, 'scene.test') self.hass.block_till_done() - self.assertTrue(self.light_1.is_on) - self.assertTrue(self.light_2.is_on) - self.assertEqual( - 100, self.light_2.last_call('turn_on')[1].get('brightness')) + assert self.light_1.is_on + assert self.light_2.is_on + assert 100 == self.light_2.last_call('turn_on')[1].get('brightness') diff --git a/tests/components/sensor/test_bom.py b/tests/components/sensor/test_bom.py index 5e5a829662a..50669f5a77d 100644 --- a/tests/components/sensor/test_bom.py +++ b/tests/components/sensor/test_bom.py @@ -71,8 +71,8 @@ class TestBOMWeatherSensor(unittest.TestCase): def test_setup(self, mock_get): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { - 'sensor': VALID_CONFIG})) + assert setup_component(self.hass, sensor.DOMAIN, { + 'sensor': VALID_CONFIG}) fake_entities = [ 'bom_fake_feels_like_c', @@ -81,19 +81,19 @@ class TestBOMWeatherSensor(unittest.TestCase): for entity_id in fake_entities: state = self.hass.states.get('sensor.{}'.format(entity_id)) - self.assertIsNotNone(state) + assert state is not None @patch('requests.get', side_effect=mocked_requests) def test_sensor_values(self, mock_get): """Test retrieval of sensor values.""" - self.assertTrue(setup_component( - self.hass, sensor.DOMAIN, {'sensor': VALID_CONFIG})) + assert setup_component( + self.hass, sensor.DOMAIN, {'sensor': VALID_CONFIG}) weather = self.hass.states.get('sensor.bom_fake_weather').state - self.assertEqual('Fine', weather) + assert 'Fine' == weather pressure = self.hass.states.get('sensor.bom_fake_pressure_mb').state - self.assertEqual('1021.7', pressure) + assert '1021.7' == pressure feels_like = self.hass.states.get('sensor.bom_fake_feels_like_c').state - self.assertEqual('25.0', feels_like) + assert '25.0' == feels_like diff --git a/tests/components/sensor/test_canary.py b/tests/components/sensor/test_canary.py index 47a1f330d05..7908e22e579 100644 --- a/tests/components/sensor/test_canary.py +++ b/tests/components/sensor/test_canary.py @@ -53,7 +53,7 @@ class TestCanarySensorSetup(unittest.TestCase): canary.setup_platform(self.hass, self.config, self.add_entities, None) - self.assertEqual(6, len(self.DEVICES)) + assert 6 == len(self.DEVICES) def test_temperature_sensor(self): """Test temperature sensor with fahrenheit.""" @@ -66,10 +66,10 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[0], location, device) sensor.update() - self.assertEqual("Home Family Room Temperature", sensor.name) - self.assertEqual("°C", sensor.unit_of_measurement) - self.assertEqual(21.12, sensor.state) - self.assertEqual("mdi:thermometer", sensor.icon) + assert "Home Family Room Temperature" == sensor.name + assert "°C" == sensor.unit_of_measurement + assert 21.12 == sensor.state + assert "mdi:thermometer" == sensor.icon def test_temperature_sensor_with_none_sensor_value(self): """Test temperature sensor with fahrenheit.""" @@ -82,7 +82,7 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[0], location, device) sensor.update() - self.assertEqual(None, sensor.state) + assert sensor.state is None def test_humidity_sensor(self): """Test humidity sensor.""" @@ -95,10 +95,10 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[1], location, device) sensor.update() - self.assertEqual("Home Family Room Humidity", sensor.name) - self.assertEqual("%", sensor.unit_of_measurement) - self.assertEqual(50.46, sensor.state) - self.assertEqual("mdi:water-percent", sensor.icon) + assert "Home Family Room Humidity" == sensor.name + assert "%" == sensor.unit_of_measurement + assert 50.46 == sensor.state + assert "mdi:water-percent" == sensor.icon def test_air_quality_sensor_with_very_abnormal_reading(self): """Test air quality sensor.""" @@ -111,13 +111,13 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) sensor.update() - self.assertEqual("Home Family Room Air Quality", sensor.name) - self.assertEqual(None, sensor.unit_of_measurement) - self.assertEqual(0.4, sensor.state) - self.assertEqual("mdi:weather-windy", sensor.icon) + assert "Home Family Room Air Quality" == sensor.name + assert sensor.unit_of_measurement is None + assert 0.4 == sensor.state + assert "mdi:weather-windy" == sensor.icon air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - self.assertEqual(STATE_AIR_QUALITY_VERY_ABNORMAL, air_quality) + assert STATE_AIR_QUALITY_VERY_ABNORMAL == air_quality def test_air_quality_sensor_with_abnormal_reading(self): """Test air quality sensor.""" @@ -130,13 +130,13 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) sensor.update() - self.assertEqual("Home Family Room Air Quality", sensor.name) - self.assertEqual(None, sensor.unit_of_measurement) - self.assertEqual(0.59, sensor.state) - self.assertEqual("mdi:weather-windy", sensor.icon) + assert "Home Family Room Air Quality" == sensor.name + assert sensor.unit_of_measurement is None + assert 0.59 == sensor.state + assert "mdi:weather-windy" == sensor.icon air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - self.assertEqual(STATE_AIR_QUALITY_ABNORMAL, air_quality) + assert STATE_AIR_QUALITY_ABNORMAL == air_quality def test_air_quality_sensor_with_normal_reading(self): """Test air quality sensor.""" @@ -149,13 +149,13 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) sensor.update() - self.assertEqual("Home Family Room Air Quality", sensor.name) - self.assertEqual(None, sensor.unit_of_measurement) - self.assertEqual(1.0, sensor.state) - self.assertEqual("mdi:weather-windy", sensor.icon) + assert "Home Family Room Air Quality" == sensor.name + assert sensor.unit_of_measurement is None + assert 1.0 == sensor.state + assert "mdi:weather-windy" == sensor.icon air_quality = sensor.device_state_attributes[ATTR_AIR_QUALITY] - self.assertEqual(STATE_AIR_QUALITY_NORMAL, air_quality) + assert STATE_AIR_QUALITY_NORMAL == air_quality def test_air_quality_sensor_with_none_sensor_value(self): """Test air quality sensor.""" @@ -168,8 +168,8 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[2], location, device) sensor.update() - self.assertEqual(None, sensor.state) - self.assertEqual(None, sensor.device_state_attributes) + assert sensor.state is None + assert sensor.device_state_attributes is None def test_battery_sensor(self): """Test battery sensor.""" @@ -182,10 +182,10 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[4], location, device) sensor.update() - self.assertEqual("Home Family Room Battery", sensor.name) - self.assertEqual("%", sensor.unit_of_measurement) - self.assertEqual(70.46, sensor.state) - self.assertEqual("mdi:battery-70", sensor.icon) + assert "Home Family Room Battery" == sensor.name + assert "%" == sensor.unit_of_measurement + assert 70.46 == sensor.state + assert "mdi:battery-70" == sensor.icon def test_wifi_sensor(self): """Test battery sensor.""" @@ -198,7 +198,7 @@ class TestCanarySensorSetup(unittest.TestCase): sensor = CanarySensor(data, SENSOR_TYPES[3], location, device) sensor.update() - self.assertEqual("Home Family Room Wifi", sensor.name) - self.assertEqual("dBm", sensor.unit_of_measurement) - self.assertEqual(-57, sensor.state) - self.assertEqual("mdi:wifi", sensor.icon) + assert "Home Family Room Wifi" == sensor.name + assert "dBm" == sensor.unit_of_measurement + assert -57 == sensor.state + assert "mdi:wifi" == sensor.icon diff --git a/tests/components/sensor/test_command_line.py b/tests/components/sensor/test_command_line.py index 82cdef014b9..cacd7a4156d 100644 --- a/tests/components/sensor/test_command_line.py +++ b/tests/components/sensor/test_command_line.py @@ -38,12 +38,12 @@ class TestCommandSensorSensor(unittest.TestCase): command_line.setup_platform(self.hass, config, add_dev_callback) - self.assertEqual(1, len(devices)) + assert 1 == len(devices) entity = devices[0] entity.update() - self.assertEqual('Test', entity.name) - self.assertEqual('in', entity.unit_of_measurement) - self.assertEqual('5', entity.state) + assert 'Test' == entity.name + assert 'in' == entity.unit_of_measurement + assert '5' == entity.state def test_template(self): """Test command sensor with template.""" @@ -54,7 +54,7 @@ class TestCommandSensorSensor(unittest.TestCase): Template('{{ value | multiply(0.1) }}', self.hass), []) entity.update() - self.assertEqual(5, float(entity.state)) + assert 5 == float(entity.state) def test_template_render(self): """Ensure command with templates get rendered properly.""" @@ -65,14 +65,14 @@ class TestCommandSensorSensor(unittest.TestCase): ) data.update() - self.assertEqual("Works", data.value) + assert "Works" == data.value def test_bad_command(self): """Test bad command.""" data = command_line.CommandSensorData(self.hass, 'asdfasdf', 15) data.update() - self.assertEqual(None, data.value) + assert data.value is None def test_update_with_json_attrs(self): """Test attributes get extracted from a JSON result.""" @@ -88,12 +88,12 @@ class TestCommandSensorSensor(unittest.TestCase): 'another_key', 'key_three']) self.sensor.update() - self.assertEqual('some_json_value', - self.sensor.device_state_attributes['key']) - self.assertEqual('another_json_value', - self.sensor.device_state_attributes['another_key']) - self.assertEqual('value_three', - self.sensor.device_state_attributes['key_three']) + assert 'some_json_value' == \ + self.sensor.device_state_attributes['key'] + assert 'another_json_value' == \ + self.sensor.device_state_attributes['another_key'] + assert 'value_three' == \ + self.sensor.device_state_attributes['key_three'] @patch('homeassistant.components.sensor.command_line._LOGGER') def test_update_with_json_attrs_no_data(self, mock_logger): @@ -105,8 +105,8 @@ class TestCommandSensorSensor(unittest.TestCase): self.sensor = command_line.CommandSensor(self.hass, data, 'test', None, None, ['key']) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called @patch('homeassistant.components.sensor.command_line._LOGGER') def test_update_with_json_attrs_not_dict(self, mock_logger): @@ -118,8 +118,8 @@ class TestCommandSensorSensor(unittest.TestCase): self.sensor = command_line.CommandSensor(self.hass, data, 'test', None, None, ['key']) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called @patch('homeassistant.components.sensor.command_line._LOGGER') def test_update_with_json_attrs_bad_JSON(self, mock_logger): @@ -131,8 +131,8 @@ class TestCommandSensorSensor(unittest.TestCase): self.sensor = command_line.CommandSensor(self.hass, data, 'test', None, None, ['key']) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called def test_update_with_missing_json_attrs(self): """Test attributes get extracted from a JSON result.""" @@ -149,13 +149,13 @@ class TestCommandSensorSensor(unittest.TestCase): 'key_three', 'special_key']) self.sensor.update() - self.assertEqual('some_json_value', - self.sensor.device_state_attributes['key']) - self.assertEqual('another_json_value', - self.sensor.device_state_attributes['another_key']) - self.assertEqual('value_three', - self.sensor.device_state_attributes['key_three']) - self.assertFalse('special_key' in self.sensor.device_state_attributes) + assert 'some_json_value' == \ + self.sensor.device_state_attributes['key'] + assert 'another_json_value' == \ + self.sensor.device_state_attributes['another_key'] + assert 'value_three' == \ + self.sensor.device_state_attributes['key_three'] + assert not ('special_key' in self.sensor.device_state_attributes) def test_update_with_unnecessary_json_attrs(self): """Test attributes get extracted from a JSON result.""" @@ -170,8 +170,8 @@ class TestCommandSensorSensor(unittest.TestCase): None, None, ['key', 'another_key']) self.sensor.update() - self.assertEqual('some_json_value', - self.sensor.device_state_attributes['key']) - self.assertEqual('another_json_value', - self.sensor.device_state_attributes['another_key']) - self.assertFalse('key_three' in self.sensor.device_state_attributes) + assert 'some_json_value' == \ + self.sensor.device_state_attributes['key'] + assert 'another_json_value' == \ + self.sensor.device_state_attributes['another_key'] + assert not ('key_three' in self.sensor.device_state_attributes) diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index 9300ecef432..fb3c89db097 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -140,7 +140,7 @@ class TestDarkSkySetup(unittest.TestCase): response = darksky.setup_platform(self.hass, VALID_CONFIG_MINIMAL, MagicMock()) - self.assertFalse(response) + assert not response @requests_mock.Mocker() @patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast) @@ -152,12 +152,12 @@ class TestDarkSkySetup(unittest.TestCase): assert setup_component(self.hass, 'sensor', VALID_CONFIG_MINIMAL) - self.assertTrue(mock_get_forecast.called) - self.assertEqual(mock_get_forecast.call_count, 1) - self.assertEqual(len(self.hass.states.entity_ids()), 7) + assert mock_get_forecast.called + assert mock_get_forecast.call_count == 1 + assert len(self.hass.states.entity_ids()) == 7 state = self.hass.states.get('sensor.dark_sky_summary') assert state is not None - self.assertEqual(state.state, 'Clear') - self.assertEqual(state.attributes.get('friendly_name'), - 'Dark Sky Summary') + assert state.state == 'Clear' + assert state.attributes.get('friendly_name') == \ + 'Dark Sky Summary' diff --git a/tests/components/sensor/test_dte_energy_bridge.py b/tests/components/sensor/test_dte_energy_bridge.py index 2341c3f8350..335f5d67a0f 100644 --- a/tests/components/sensor/test_dte_energy_bridge.py +++ b/tests/components/sensor/test_dte_energy_bridge.py @@ -27,9 +27,8 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): def test_setup_with_config(self): """Test the platform setup with configuration.""" - self.assertTrue( - setup_component(self.hass, 'sensor', - {'dte_energy_bridge': DTE_ENERGY_BRIDGE_CONFIG})) + assert setup_component(self.hass, 'sensor', + {'dte_energy_bridge': DTE_ENERGY_BRIDGE_CONFIG}) @requests_mock.Mocker() def test_setup_correct_reading(self, mock_req): @@ -39,9 +38,9 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): text='.411 kW') assert setup_component(self.hass, 'sensor', { 'sensor': DTE_ENERGY_BRIDGE_CONFIG}) - self.assertEqual('0.411', - self.hass.states - .get('sensor.current_energy_usage').state) + assert '0.411' == \ + self.hass.states \ + .get('sensor.current_energy_usage').state @requests_mock.Mocker() def test_setup_incorrect_units_reading(self, mock_req): @@ -51,9 +50,9 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): text='411 kW') assert setup_component(self.hass, 'sensor', { 'sensor': DTE_ENERGY_BRIDGE_CONFIG}) - self.assertEqual('0.411', - self.hass.states - .get('sensor.current_energy_usage').state) + assert '0.411' == \ + self.hass.states \ + .get('sensor.current_energy_usage').state @requests_mock.Mocker() def test_setup_bad_format_reading(self, mock_req): @@ -63,6 +62,6 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): text='411') assert setup_component(self.hass, 'sensor', { 'sensor': DTE_ENERGY_BRIDGE_CONFIG}) - self.assertEqual('unknown', - self.hass.states - .get('sensor.current_energy_usage').state) + assert 'unknown' == \ + self.hass.states \ + .get('sensor.current_energy_usage').state diff --git a/tests/components/sensor/test_dyson.py b/tests/components/sensor/test_dyson.py index baab96a61f0..d4609c2b2c5 100644 --- a/tests/components/sensor/test_dyson.py +++ b/tests/components/sensor/test_dyson.py @@ -86,11 +86,11 @@ class DysonTest(unittest.TestCase): sensor = dyson.DysonFilterLifeSensor(_get_device_without_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertIsNone(sensor.state) - self.assertEqual(sensor.unit_of_measurement, "hours") - self.assertEqual(sensor.name, "Device_name Filter Life") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state is None + assert sensor.unit_of_measurement == "hours" + assert sensor.name == "Device_name Filter Life" + assert sensor.entity_id == "sensor.dyson_1" sensor.on_message('message') def test_dyson_filter_life_sensor_with_values(self): @@ -98,11 +98,11 @@ class DysonTest(unittest.TestCase): sensor = dyson.DysonFilterLifeSensor(_get_with_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 100) - self.assertEqual(sensor.unit_of_measurement, "hours") - self.assertEqual(sensor.name, "Device_name Filter Life") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 100 + assert sensor.unit_of_measurement == "hours" + assert sensor.name == "Device_name Filter Life" + assert sensor.entity_id == "sensor.dyson_1" sensor.on_message('message') def test_dyson_dust_sensor(self): @@ -110,55 +110,55 @@ class DysonTest(unittest.TestCase): sensor = dyson.DysonDustSensor(_get_device_without_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertIsNone(sensor.state) - self.assertEqual(sensor.unit_of_measurement, None) - self.assertEqual(sensor.name, "Device_name Dust") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state is None + assert sensor.unit_of_measurement is None + assert sensor.name == "Device_name Dust" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_dust_sensor_with_values(self): """Test dust sensor with values.""" sensor = dyson.DysonDustSensor(_get_with_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 5) - self.assertEqual(sensor.unit_of_measurement, None) - self.assertEqual(sensor.name, "Device_name Dust") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 5 + assert sensor.unit_of_measurement is None + assert sensor.name == "Device_name Dust" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_humidity_sensor(self): """Test humidity sensor with no value.""" sensor = dyson.DysonHumiditySensor(_get_device_without_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertIsNone(sensor.state) - self.assertEqual(sensor.unit_of_measurement, '%') - self.assertEqual(sensor.name, "Device_name Humidity") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state is None + assert sensor.unit_of_measurement == '%' + assert sensor.name == "Device_name Humidity" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_humidity_sensor_with_values(self): """Test humidity sensor with values.""" sensor = dyson.DysonHumiditySensor(_get_with_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 45) - self.assertEqual(sensor.unit_of_measurement, '%') - self.assertEqual(sensor.name, "Device_name Humidity") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 45 + assert sensor.unit_of_measurement == '%' + assert sensor.name == "Device_name Humidity" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_humidity_standby_monitoring(self): """Test humidity sensor while device is in standby monitoring.""" sensor = dyson.DysonHumiditySensor(_get_with_standby_monitoring()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, STATE_OFF) - self.assertEqual(sensor.unit_of_measurement, '%') - self.assertEqual(sensor.name, "Device_name Humidity") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == STATE_OFF + assert sensor.unit_of_measurement == '%' + assert sensor.name == "Device_name Humidity" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_temperature_sensor(self): """Test temperature sensor with no value.""" @@ -166,11 +166,11 @@ class DysonTest(unittest.TestCase): TEMP_CELSIUS) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertIsNone(sensor.state) - self.assertEqual(sensor.unit_of_measurement, '°C') - self.assertEqual(sensor.name, "Device_name Temperature") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state is None + assert sensor.unit_of_measurement == '°C' + assert sensor.name == "Device_name Temperature" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_temperature_sensor_with_values(self): """Test temperature sensor with values.""" @@ -178,21 +178,21 @@ class DysonTest(unittest.TestCase): TEMP_CELSIUS) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 21.9) - self.assertEqual(sensor.unit_of_measurement, '°C') - self.assertEqual(sensor.name, "Device_name Temperature") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 21.9 + assert sensor.unit_of_measurement == '°C' + assert sensor.name == "Device_name Temperature" + assert sensor.entity_id == "sensor.dyson_1" sensor = dyson.DysonTemperatureSensor(_get_with_state(), TEMP_FAHRENHEIT) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 71.3) - self.assertEqual(sensor.unit_of_measurement, '°F') - self.assertEqual(sensor.name, "Device_name Temperature") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 71.3 + assert sensor.unit_of_measurement == '°F' + assert sensor.name == "Device_name Temperature" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_temperature_standby_monitoring(self): """Test temperature sensor while device is in standby monitoring.""" @@ -200,30 +200,30 @@ class DysonTest(unittest.TestCase): TEMP_CELSIUS) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, STATE_OFF) - self.assertEqual(sensor.unit_of_measurement, '°C') - self.assertEqual(sensor.name, "Device_name Temperature") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == STATE_OFF + assert sensor.unit_of_measurement == '°C' + assert sensor.name == "Device_name Temperature" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_air_quality_sensor(self): """Test air quality sensor with no value.""" sensor = dyson.DysonAirQualitySensor(_get_device_without_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertIsNone(sensor.state) - self.assertEqual(sensor.unit_of_measurement, None) - self.assertEqual(sensor.name, "Device_name AQI") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state is None + assert sensor.unit_of_measurement is None + assert sensor.name == "Device_name AQI" + assert sensor.entity_id == "sensor.dyson_1" def test_dyson_air_quality_sensor_with_values(self): """Test air quality sensor with values.""" sensor = dyson.DysonAirQualitySensor(_get_with_state()) sensor.hass = self.hass sensor.entity_id = "sensor.dyson_1" - self.assertFalse(sensor.should_poll) - self.assertEqual(sensor.state, 2) - self.assertEqual(sensor.unit_of_measurement, None) - self.assertEqual(sensor.name, "Device_name AQI") - self.assertEqual(sensor.entity_id, "sensor.dyson_1") + assert not sensor.should_poll + assert sensor.state == 2 + assert sensor.unit_of_measurement is None + assert sensor.name == "Device_name AQI" + assert sensor.entity_id == "sensor.dyson_1" diff --git a/tests/components/sensor/test_efergy.py b/tests/components/sensor/test_efergy.py index fdcaa415483..f35c7b143e6 100644 --- a/tests/components/sensor/test_efergy.py +++ b/tests/components/sensor/test_efergy.py @@ -85,16 +85,11 @@ class TestEfergySensor(unittest.TestCase): 'sensor': ONE_SENSOR_CONFIG, }) - self.assertEqual( - '38.21', self.hass.states.get('sensor.energy_consumed').state) - self.assertEqual( - '1580', self.hass.states.get('sensor.energy_usage').state) - self.assertEqual( - 'ok', self.hass.states.get('sensor.energy_budget').state) - self.assertEqual( - '5.27', self.hass.states.get('sensor.energy_cost').state) - self.assertEqual( - '1628', self.hass.states.get('sensor.efergy_728386').state) + assert '38.21' == self.hass.states.get('sensor.energy_consumed').state + assert '1580' == self.hass.states.get('sensor.energy_usage').state + assert 'ok' == self.hass.states.get('sensor.energy_budget').state + assert '5.27' == self.hass.states.get('sensor.energy_cost').state + assert '1628' == self.hass.states.get('sensor.efergy_728386').state @requests_mock.Mocker() def test_multi_sensor_readings(self, mock): @@ -104,9 +99,6 @@ class TestEfergySensor(unittest.TestCase): 'sensor': MULTI_SENSOR_CONFIG, }) - self.assertEqual( - '218', self.hass.states.get('sensor.efergy_728386').state) - self.assertEqual( - '1808', self.hass.states.get('sensor.efergy_0').state) - self.assertEqual( - '312', self.hass.states.get('sensor.efergy_728387').state) + assert '218' == self.hass.states.get('sensor.efergy_728386').state + assert '1808' == self.hass.states.get('sensor.efergy_0').state + assert '312' == self.hass.states.get('sensor.efergy_728387').state diff --git a/tests/components/sensor/test_fail2ban.py b/tests/components/sensor/test_fail2ban.py index a6131e5dbc6..6ba959f80ff 100644 --- a/tests/components/sensor/test_fail2ban.py +++ b/tests/components/sensor/test_fail2ban.py @@ -101,120 +101,99 @@ class TestBanSensor(unittest.TestCase): """Test that log is parsed correctly for single ban.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor = BanSensor('fail2ban', 'jail_one', log_parser) - self.assertEqual(sensor.name, 'fail2ban jail_one') + assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('single_ban')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor.update() - self.assertEqual(sensor.state, '111.111.111.111') - self.assertEqual( - sensor.state_attributes[STATE_CURRENT_BANS], ['111.111.111.111'] - ) - self.assertEqual( - sensor.state_attributes[STATE_ALL_BANS], ['111.111.111.111'] - ) + assert sensor.state == '111.111.111.111' + assert \ + sensor.state_attributes[STATE_CURRENT_BANS] == ['111.111.111.111'] + assert \ + sensor.state_attributes[STATE_ALL_BANS] == ['111.111.111.111'] def test_multiple_ban(self): """Test that log is parsed correctly for multiple ban.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor = BanSensor('fail2ban', 'jail_one', log_parser) - self.assertEqual(sensor.name, 'fail2ban jail_one') + assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('multi_ban')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor.update() - self.assertEqual(sensor.state, '222.222.222.222') - self.assertEqual( - sensor.state_attributes[STATE_CURRENT_BANS], + assert sensor.state == '222.222.222.222' + assert sensor.state_attributes[STATE_CURRENT_BANS] == \ ['111.111.111.111', '222.222.222.222'] - ) - self.assertEqual( - sensor.state_attributes[STATE_ALL_BANS], + assert sensor.state_attributes[STATE_ALL_BANS] == \ ['111.111.111.111', '222.222.222.222'] - ) def test_unban_all(self): """Test that log is parsed correctly when unbanning.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor = BanSensor('fail2ban', 'jail_one', log_parser) - self.assertEqual(sensor.name, 'fail2ban jail_one') + assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('unban_all')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor.update() - self.assertEqual(sensor.state, 'None') - self.assertEqual(sensor.state_attributes[STATE_CURRENT_BANS], []) - self.assertEqual( - sensor.state_attributes[STATE_ALL_BANS], + assert sensor.state == 'None' + assert sensor.state_attributes[STATE_CURRENT_BANS] == [] + assert sensor.state_attributes[STATE_ALL_BANS] == \ ['111.111.111.111', '222.222.222.222'] - ) def test_unban_one(self): """Test that log is parsed correctly when unbanning one ip.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor = BanSensor('fail2ban', 'jail_one', log_parser) - self.assertEqual(sensor.name, 'fail2ban jail_one') + assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('unban_one')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor.update() - self.assertEqual(sensor.state, '222.222.222.222') - self.assertEqual( - sensor.state_attributes[STATE_CURRENT_BANS], + assert sensor.state == '222.222.222.222' + assert sensor.state_attributes[STATE_CURRENT_BANS] == \ ['222.222.222.222'] - ) - self.assertEqual( - sensor.state_attributes[STATE_ALL_BANS], + assert sensor.state_attributes[STATE_ALL_BANS] == \ ['111.111.111.111', '222.222.222.222'] - ) def test_multi_jail(self): """Test that log is parsed correctly when using multiple jails.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor1 = BanSensor('fail2ban', 'jail_one', log_parser) sensor2 = BanSensor('fail2ban', 'jail_two', log_parser) - self.assertEqual(sensor1.name, 'fail2ban jail_one') - self.assertEqual(sensor2.name, 'fail2ban jail_two') + assert sensor1.name == 'fail2ban jail_one' + assert sensor2.name == 'fail2ban jail_two' mock_fh = MockOpen(read_data=fake_log('multi_jail')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor1.update() sensor2.update() - self.assertEqual(sensor1.state, '111.111.111.111') - self.assertEqual( - sensor1.state_attributes[STATE_CURRENT_BANS], ['111.111.111.111'] - ) - self.assertEqual( - sensor1.state_attributes[STATE_ALL_BANS], ['111.111.111.111'] - ) - self.assertEqual(sensor2.state, '222.222.222.222') - self.assertEqual( - sensor2.state_attributes[STATE_CURRENT_BANS], ['222.222.222.222'] - ) - self.assertEqual( - sensor2.state_attributes[STATE_ALL_BANS], ['222.222.222.222'] - ) + assert sensor1.state == '111.111.111.111' + assert \ + sensor1.state_attributes[STATE_CURRENT_BANS] == ['111.111.111.111'] + assert sensor1.state_attributes[STATE_ALL_BANS] == ['111.111.111.111'] + assert sensor2.state == '222.222.222.222' + assert \ + sensor2.state_attributes[STATE_CURRENT_BANS] == ['222.222.222.222'] + assert sensor2.state_attributes[STATE_ALL_BANS] == ['222.222.222.222'] def test_ban_active_after_update(self): """Test that ban persists after subsequent update.""" log_parser = BanLogParser(timedelta(seconds=-1), '/tmp') sensor = BanSensor('fail2ban', 'jail_one', log_parser) - self.assertEqual(sensor.name, 'fail2ban jail_one') + assert sensor.name == 'fail2ban jail_one' mock_fh = MockOpen(read_data=fake_log('single_ban')) with patch('homeassistant.components.sensor.fail2ban.open', mock_fh, create=True): sensor.update() - self.assertEqual(sensor.state, '111.111.111.111') + assert sensor.state == '111.111.111.111' sensor.update() - self.assertEqual(sensor.state, '111.111.111.111') - self.assertEqual( - sensor.state_attributes[STATE_CURRENT_BANS], ['111.111.111.111'] - ) - self.assertEqual( - sensor.state_attributes[STATE_ALL_BANS], ['111.111.111.111'] - ) + assert sensor.state == '111.111.111.111' + assert \ + sensor.state_attributes[STATE_CURRENT_BANS] == ['111.111.111.111'] + assert sensor.state_attributes[STATE_ALL_BANS] == ['111.111.111.111'] diff --git a/tests/components/sensor/test_file.py b/tests/components/sensor/test_file.py index 7171289de69..3ac37b989c7 100644 --- a/tests/components/sensor/test_file.py +++ b/tests/components/sensor/test_file.py @@ -45,7 +45,7 @@ class TestFileSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.file1') - self.assertEqual(state.state, '21') + assert state.state == '21' @patch('os.path.isfile', Mock(return_value=True)) @patch('os.access', Mock(return_value=True)) @@ -70,7 +70,7 @@ class TestFileSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.file2') - self.assertEqual(state.state, '26') + assert state.state == '26' @patch('os.path.isfile', Mock(return_value=True)) @patch('os.access', Mock(return_value=True)) @@ -91,4 +91,4 @@ class TestFileSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.file3') - self.assertEqual(state.state, STATE_UNKNOWN) + assert state.state == STATE_UNKNOWN diff --git a/tests/components/sensor/test_filesize.py b/tests/components/sensor/test_filesize.py index 23ef1c6081b..f0ea5c95de5 100644 --- a/tests/components/sensor/test_filesize.py +++ b/tests/components/sensor/test_filesize.py @@ -38,8 +38,7 @@ class TestFileSensor(unittest.TestCase): 'platform': 'filesize', CONF_FILE_PATHS: ['invalid_path']} } - self.assertTrue( - setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) assert len(self.hass.states.entity_ids()) == 0 def test_valid_path(self): @@ -50,8 +49,7 @@ class TestFileSensor(unittest.TestCase): 'platform': 'filesize', CONF_FILE_PATHS: [TEST_FILE]} } - self.assertTrue( - setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('sensor.mock_file_test_filesizetxt') assert state.state == '0.0' diff --git a/tests/components/sensor/test_filter.py b/tests/components/sensor/test_filter.py index 433d1aa2512..3d44b7d131d 100644 --- a/tests/components/sensor/test_filter.py +++ b/tests/components/sensor/test_filter.py @@ -99,7 +99,7 @@ class TestFilterSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual('17.05', state.state) + assert '17.05' == state.state def test_outlier(self): """Test if outlier filter works.""" @@ -109,7 +109,7 @@ class TestFilterSensor(unittest.TestCase): radius=4.0) for state in self.values: filtered = filt.filter_state(state) - self.assertEqual(22, filtered.state) + assert 22 == filtered.state def test_initial_outlier(self): """Test issue #13363.""" @@ -120,7 +120,7 @@ class TestFilterSensor(unittest.TestCase): out = ha.State('sensor.test_monitored', 4000) for state in [out]+self.values: filtered = filt.filter_state(state) - self.assertEqual(22, filtered.state) + assert 22 == filtered.state def test_lowpass(self): """Test if lowpass filter works.""" @@ -130,7 +130,7 @@ class TestFilterSensor(unittest.TestCase): time_constant=10) for state in self.values: filtered = filt.filter_state(state) - self.assertEqual(18.05, filtered.state) + assert 18.05 == filtered.state def test_range(self): """Test if range filter works.""" @@ -143,11 +143,11 @@ class TestFilterSensor(unittest.TestCase): unf = float(unf_state.state) filtered = filt.filter_state(unf_state) if unf < lower: - self.assertEqual(lower, filtered.state) + assert lower == filtered.state elif unf > upper: - self.assertEqual(upper, filtered.state) + assert upper == filtered.state else: - self.assertEqual(unf, filtered.state) + assert unf == filtered.state def test_range_zero(self): """Test if range filter works with zeroes as bounds.""" @@ -160,11 +160,11 @@ class TestFilterSensor(unittest.TestCase): unf = float(unf_state.state) filtered = filt.filter_state(unf_state) if unf < lower: - self.assertEqual(lower, filtered.state) + assert lower == filtered.state elif unf > upper: - self.assertEqual(upper, filtered.state) + assert upper == filtered.state else: - self.assertEqual(unf, filtered.state) + assert unf == filtered.state def test_throttle(self): """Test if lowpass filter works.""" @@ -176,7 +176,7 @@ class TestFilterSensor(unittest.TestCase): new_state = filt.filter_state(state) if not filt.skip_processing: filtered.append(new_state) - self.assertEqual([20, 21], [f.state for f in filtered]) + assert [20, 21] == [f.state for f in filtered] def test_time_sma(self): """Test if time_sma filter works.""" @@ -186,4 +186,4 @@ class TestFilterSensor(unittest.TestCase): type='last') for state in self.values: filtered = filt.filter_state(state) - self.assertEqual(21.5, filtered.state) + assert 21.5 == filtered.state diff --git a/tests/components/sensor/test_folder.py b/tests/components/sensor/test_folder.py index 85ae8a688e7..e4f97fbaa46 100644 --- a/tests/components/sensor/test_folder.py +++ b/tests/components/sensor/test_folder.py @@ -44,8 +44,7 @@ class TestFolderSensor(unittest.TestCase): 'platform': 'folder', CONF_FOLDER_PATHS: 'invalid_path'} } - self.assertTrue( - setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) assert len(self.hass.states.entity_ids()) == 0 def test_valid_path(self): @@ -56,8 +55,7 @@ class TestFolderSensor(unittest.TestCase): 'platform': 'folder', CONF_FOLDER_PATHS: TEST_DIR} } - self.assertTrue( - setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('sensor.test_folder') assert state.state == '0.0' diff --git a/tests/components/sensor/test_geo_rss_events.py b/tests/components/sensor/test_geo_rss_events.py index 3362f799392..988b7b686ab 100644 --- a/tests/components/sensor/test_geo_rss_events.py +++ b/tests/components/sensor/test_geo_rss_events.py @@ -81,8 +81,7 @@ class TestGeoRssServiceUpdater(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, sensor.DOMAIN): - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, - VALID_CONFIG)) + assert setup_component(self.hass, sensor.DOMAIN, VALID_CONFIG) # Artificially trigger update. self.hass.bus.fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -92,7 +91,7 @@ class TestGeoRssServiceUpdater(unittest.TestCase): assert len(all_states) == 1 state = self.hass.states.get("sensor.event_service_any") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Event Service Any" assert int(state.state) == 2 assert state.attributes == { @@ -142,8 +141,8 @@ class TestGeoRssServiceUpdater(unittest.TestCase): mock_entry_2] with assert_setup_component(1, sensor.DOMAIN): - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, - VALID_CONFIG_WITH_CATEGORIES)) + assert setup_component(self.hass, sensor.DOMAIN, + VALID_CONFIG_WITH_CATEGORIES) # Artificially trigger update. self.hass.bus.fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -153,7 +152,7 @@ class TestGeoRssServiceUpdater(unittest.TestCase): assert len(all_states) == 1 state = self.hass.states.get("sensor.event_service_category_1") - self.assertIsNotNone(state) + assert state is not None assert state.name == "Event Service Category 1" assert int(state.state) == 2 assert state.attributes == { diff --git a/tests/components/sensor/test_google_wifi.py b/tests/components/sensor/test_google_wifi.py index 55afedab536..a4b18d0ed4a 100644 --- a/tests/components/sensor/test_google_wifi.py +++ b/tests/components/sensor/test_google_wifi.py @@ -49,12 +49,12 @@ class TestGoogleWifiSetup(unittest.TestCase): resource = '{}{}{}'.format( 'http://', google_wifi.DEFAULT_HOST, google_wifi.ENDPOINT) mock_req.get(resource, status_code=200) - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': { 'platform': 'google_wifi', 'monitored_conditions': ['uptime'] } - })) + }) assert_setup_component(1, 'sensor') @requests_mock.Mocker() @@ -63,7 +63,7 @@ class TestGoogleWifiSetup(unittest.TestCase): resource = '{}{}{}'.format( 'http://', 'localhost', google_wifi.ENDPOINT) mock_req.get(resource, status_code=200) - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': { 'platform': 'google_wifi', 'host': 'localhost', @@ -75,7 +75,7 @@ class TestGoogleWifiSetup(unittest.TestCase): 'local_ip', 'status'] } - })) + }) assert_setup_component(6, 'sensor') @@ -127,20 +127,20 @@ class TestGoogleWifiSensor(unittest.TestCase): for name in self.sensor_dict: sensor = self.sensor_dict[name]['sensor'] test_name = self.sensor_dict[name]['name'] - self.assertEqual(test_name, sensor.name) + assert test_name == sensor.name def test_unit_of_measurement(self): """Test the unit of measurement.""" for name in self.sensor_dict: sensor = self.sensor_dict[name]['sensor'] - self.assertEqual( - self.sensor_dict[name]['units'], sensor.unit_of_measurement) + assert \ + self.sensor_dict[name]['units'] == sensor.unit_of_measurement def test_icon(self): """Test the icon.""" for name in self.sensor_dict: sensor = self.sensor_dict[name]['sensor'] - self.assertEqual(self.sensor_dict[name]['icon'], sensor.icon) + assert self.sensor_dict[name]['icon'] == sensor.icon @requests_mock.Mocker() def test_state(self, mock_req): @@ -153,13 +153,13 @@ class TestGoogleWifiSensor(unittest.TestCase): self.fake_delay(2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - self.assertEqual('1969-12-31 00:00:00', sensor.state) + assert '1969-12-31 00:00:00' == sensor.state elif name == google_wifi.ATTR_UPTIME: - self.assertEqual(1, sensor.state) + assert 1 == sensor.state elif name == google_wifi.ATTR_STATUS: - self.assertEqual('Online', sensor.state) + assert 'Online' == sensor.state else: - self.assertEqual('initial', sensor.state) + assert 'initial' == sensor.state @requests_mock.Mocker() def test_update_when_value_is_none(self, mock_req): @@ -169,7 +169,7 @@ class TestGoogleWifiSensor(unittest.TestCase): sensor = self.sensor_dict[name]['sensor'] self.fake_delay(2) sensor.update() - self.assertEqual(STATE_UNKNOWN, sensor.state) + assert STATE_UNKNOWN == sensor.state @requests_mock.Mocker() def test_update_when_value_changed(self, mock_req): @@ -182,17 +182,17 @@ class TestGoogleWifiSensor(unittest.TestCase): self.fake_delay(2) sensor.update() if name == google_wifi.ATTR_LAST_RESTART: - self.assertEqual('1969-12-30 00:00:00', sensor.state) + assert '1969-12-30 00:00:00' == sensor.state elif name == google_wifi.ATTR_UPTIME: - self.assertEqual(2, sensor.state) + assert 2 == sensor.state elif name == google_wifi.ATTR_STATUS: - self.assertEqual('Offline', sensor.state) + assert 'Offline' == sensor.state elif name == google_wifi.ATTR_NEW_VERSION: - self.assertEqual('Latest', sensor.state) + assert 'Latest' == sensor.state elif name == google_wifi.ATTR_LOCAL_IP: - self.assertEqual(STATE_UNKNOWN, sensor.state) + assert STATE_UNKNOWN == sensor.state else: - self.assertEqual('next', sensor.state) + assert 'next' == sensor.state @requests_mock.Mocker() def test_when_api_data_missing(self, mock_req): @@ -204,7 +204,7 @@ class TestGoogleWifiSensor(unittest.TestCase): sensor = self.sensor_dict[name]['sensor'] self.fake_delay(2) sensor.update() - self.assertEqual(STATE_UNKNOWN, sensor.state) + assert STATE_UNKNOWN == sensor.state def test_update_when_unavailable(self): """Test state updates when Google Wifi unavailable.""" @@ -213,7 +213,7 @@ class TestGoogleWifiSensor(unittest.TestCase): for name in self.sensor_dict: sensor = self.sensor_dict[name]['sensor'] sensor.update() - self.assertEqual(STATE_UNKNOWN, sensor.state) + assert STATE_UNKNOWN == sensor.state def update_side_effect(self): """Mock representation of update function.""" diff --git a/tests/components/sensor/test_hddtemp.py b/tests/components/sensor/test_hddtemp.py index 1b65af7fd7e..eeca7fcf565 100644 --- a/tests/components/sensor/test_hddtemp.py +++ b/tests/components/sensor/test_hddtemp.py @@ -129,13 +129,13 @@ class TestHDDTempSensor(unittest.TestCase): reference = self.reference[state.attributes.get('device')] - self.assertEqual(state.state, reference['temperature']) - self.assertEqual(state.attributes.get('device'), reference['device']) - self.assertEqual(state.attributes.get('model'), reference['model']) - self.assertEqual(state.attributes.get('unit_of_measurement'), - reference['unit_of_measurement']) - self.assertEqual(state.attributes.get('friendly_name'), - 'HD Temperature ' + reference['device']) + assert state.state == reference['temperature'] + assert state.attributes.get('device') == reference['device'] + assert state.attributes.get('model') == reference['model'] + assert state.attributes.get('unit_of_measurement') == \ + reference['unit_of_measurement'] + assert state.attributes.get('friendly_name') == \ + 'HD Temperature ' + reference['device'] @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_rename_config(self): @@ -147,8 +147,8 @@ class TestHDDTempSensor(unittest.TestCase): reference = self.reference[state.attributes.get('device')] - self.assertEqual(state.attributes.get('friendly_name'), - 'FooBar ' + reference['device']) + assert state.attributes.get('friendly_name') == \ + 'FooBar ' + reference['device'] @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_one_disk(self): @@ -159,23 +159,23 @@ class TestHDDTempSensor(unittest.TestCase): reference = self.reference[state.attributes.get('device')] - self.assertEqual(state.state, reference['temperature']) - self.assertEqual(state.attributes.get('device'), reference['device']) - self.assertEqual(state.attributes.get('model'), reference['model']) - self.assertEqual(state.attributes.get('unit_of_measurement'), - reference['unit_of_measurement']) - self.assertEqual(state.attributes.get('friendly_name'), - 'HD Temperature ' + reference['device']) + assert state.state == reference['temperature'] + assert state.attributes.get('device') == reference['device'] + assert state.attributes.get('model') == reference['model'] + assert state.attributes.get('unit_of_measurement') == \ + reference['unit_of_measurement'] + assert state.attributes.get('friendly_name') == \ + 'HD Temperature ' + reference['device'] @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_wrong_disk(self): """Test hddtemp wrong disk configuration.""" assert setup_component(self.hass, 'sensor', VALID_CONFIG_WRONG_DISK) - self.assertEqual(len(self.hass.states.all()), 1) + assert len(self.hass.states.all()) == 1 state = self.hass.states.get('sensor.hd_temperature_devsdx1') - self.assertEqual(state.attributes.get('friendly_name'), - 'HD Temperature ' + '/dev/sdx1') + assert state.attributes.get('friendly_name') == \ + 'HD Temperature ' + '/dev/sdx1' @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_multiple_disks(self): @@ -191,26 +191,26 @@ class TestHDDTempSensor(unittest.TestCase): reference = self.reference[state.attributes.get('device')] - self.assertEqual(state.state, - reference['temperature']) - self.assertEqual(state.attributes.get('device'), - reference['device']) - self.assertEqual(state.attributes.get('model'), - reference['model']) - self.assertEqual(state.attributes.get('unit_of_measurement'), - reference['unit_of_measurement']) - self.assertEqual(state.attributes.get('friendly_name'), - 'HD Temperature ' + reference['device']) + assert state.state == \ + reference['temperature'] + assert state.attributes.get('device') == \ + reference['device'] + assert state.attributes.get('model') == \ + reference['model'] + assert state.attributes.get('unit_of_measurement') == \ + reference['unit_of_measurement'] + assert state.attributes.get('friendly_name') == \ + 'HD Temperature ' + reference['device'] @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_host_refused(self): """Test hddtemp if host unreachable.""" assert setup_component(self.hass, 'sensor', VALID_CONFIG_HOST) - self.assertEqual(len(self.hass.states.all()), 0) + assert len(self.hass.states.all()) == 0 @patch('telnetlib.Telnet', new=TelnetMock) def test_hddtemp_host_unreachable(self): """Test hddtemp if host unreachable.""" assert setup_component(self.hass, 'sensor', VALID_CONFIG_HOST_UNREACHABLE) - self.assertEqual(len(self.hass.states.all()), 0) + assert len(self.hass.states.all()) == 0 diff --git a/tests/components/sensor/test_history_stats.py b/tests/components/sensor/test_history_stats.py index 1a2ec086e77..67cacb29880 100644 --- a/tests/components/sensor/test_history_stats.py +++ b/tests/components/sensor/test_history_stats.py @@ -12,6 +12,7 @@ from homeassistant.helpers.template import Template import homeassistant.util.dt as dt_util from tests.common import init_recorder_component, get_test_home_assistant +import pytest class TestHistoryStatsSensor(unittest.TestCase): @@ -42,10 +43,10 @@ class TestHistoryStatsSensor(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.test') - self.assertEqual(state.state, STATE_UNKNOWN) + assert state.state == STATE_UNKNOWN def test_period_parsing(self): """Test the conversion from templates to period.""" @@ -64,24 +65,24 @@ class TestHistoryStatsSensor(unittest.TestCase): sensor2_start, sensor2_end = sensor2._period # Start = 00:00:00 - self.assertEqual(sensor1_start.hour, 0) - self.assertEqual(sensor1_start.minute, 0) - self.assertEqual(sensor1_start.second, 0) + assert sensor1_start.hour == 0 + assert sensor1_start.minute == 0 + assert sensor1_start.second == 0 # End = 02:01:00 - self.assertEqual(sensor1_end.hour, 2) - self.assertEqual(sensor1_end.minute, 1) - self.assertEqual(sensor1_end.second, 0) + assert sensor1_end.hour == 2 + assert sensor1_end.minute == 1 + assert sensor1_end.second == 0 # Start = 21:59:00 - self.assertEqual(sensor2_start.hour, 21) - self.assertEqual(sensor2_start.minute, 59) - self.assertEqual(sensor2_start.second, 0) + assert sensor2_start.hour == 21 + assert sensor2_start.minute == 59 + assert sensor2_start.second == 0 # End = 00:00:00 - self.assertEqual(sensor2_end.hour, 0) - self.assertEqual(sensor2_end.minute, 0) - self.assertEqual(sensor2_end.second, 0) + assert sensor2_end.hour == 0 + assert sensor2_end.minute == 0 + assert sensor2_end.second == 0 def test_measure(self): """Test the history statistics sensor measure.""" @@ -119,9 +120,9 @@ class TestHistoryStatsSensor(unittest.TestCase): self.hass, 'binary_sensor.test_id', 'on', start, end, None, 'ratio', 'test') - self.assertEqual(sensor1._type, 'time') - self.assertEqual(sensor3._type, 'count') - self.assertEqual(sensor4._type, 'ratio') + assert sensor1._type == 'time' + assert sensor3._type == 'count' + assert sensor4._type == 'ratio' with patch('homeassistant.components.history.' 'state_changes_during_period', return_value=fake_states): @@ -132,10 +133,10 @@ class TestHistoryStatsSensor(unittest.TestCase): sensor3.update() sensor4.update() - self.assertEqual(sensor1.state, 0.5) - self.assertEqual(sensor2.state, None) - self.assertEqual(sensor3.state, 2) - self.assertEqual(sensor4.state, 50) + assert sensor1.state == 0.5 + assert sensor2.state is None + assert sensor3.state == 2 + assert sensor4.state == 50 def test_wrong_date(self): """Test when start or end value is not a timestamp or a date.""" @@ -153,8 +154,8 @@ class TestHistoryStatsSensor(unittest.TestCase): sensor1.update_period() sensor2.update_period() - self.assertEqual(before_update1, sensor1._period) - self.assertEqual(before_update2, sensor2._period) + assert before_update1 == sensor1._period + assert before_update2 == sensor2._period def test_wrong_duration(self): """Test when duration value is not a timedelta.""" @@ -173,9 +174,9 @@ class TestHistoryStatsSensor(unittest.TestCase): } setup_component(self.hass, 'sensor', config) - self.assertEqual(self.hass.states.get('sensor.test'), None) - self.assertRaises(TypeError, - setup_component(self.hass, 'sensor', config)) + assert self.hass.states.get('sensor.test')is None + with pytest.raises(TypeError): + setup_component(self.hass, 'sensor', config)() def test_bad_template(self): """Test Exception when the template cannot be parsed.""" @@ -193,8 +194,8 @@ class TestHistoryStatsSensor(unittest.TestCase): sensor1.update_period() sensor2.update_period() - self.assertEqual(before_update1, sensor1._period) - self.assertEqual(before_update2, sensor2._period) + assert before_update1 == sensor1._period + assert before_update2 == sensor2._period def test_not_enough_arguments(self): """Test config when not enough arguments provided.""" @@ -212,9 +213,9 @@ class TestHistoryStatsSensor(unittest.TestCase): } setup_component(self.hass, 'sensor', config) - self.assertEqual(self.hass.states.get('sensor.test'), None) - self.assertRaises(TypeError, - setup_component(self.hass, 'sensor', config)) + assert self.hass.states.get('sensor.test')is None + with pytest.raises(TypeError): + setup_component(self.hass, 'sensor', config)() def test_too_many_arguments(self): """Test config when too many arguments provided.""" @@ -234,9 +235,9 @@ class TestHistoryStatsSensor(unittest.TestCase): } setup_component(self.hass, 'sensor', config) - self.assertEqual(self.hass.states.get('sensor.test'), None) - self.assertRaises(TypeError, - setup_component(self.hass, 'sensor', config)) + assert self.hass.states.get('sensor.test')is None + with pytest.raises(TypeError): + setup_component(self.hass, 'sensor', config)() def init_recorder(self): """Initialize the recorder.""" diff --git a/tests/components/sensor/test_imap_email_content.py b/tests/components/sensor/test_imap_email_content.py index a07d94e3dcd..a0cfb783d0b 100644 --- a/tests/components/sensor/test_imap_email_content.py +++ b/tests/components/sensor/test_imap_email_content.py @@ -60,14 +60,14 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual('Test', sensor.state) - self.assertEqual("Test Message", - sensor.device_state_attributes['body']) - self.assertEqual('sender@test.com', - sensor.device_state_attributes['from']) - self.assertEqual('Test', sensor.device_state_attributes['subject']) - self.assertEqual(datetime.datetime(2016, 1, 1, 12, 44, 57), - sensor.device_state_attributes['date']) + assert 'Test' == sensor.state + assert "Test Message" == \ + sensor.device_state_attributes['body'] + assert 'sender@test.com' == \ + sensor.device_state_attributes['from'] + assert 'Test' == sensor.device_state_attributes['subject'] + assert datetime.datetime(2016, 1, 1, 12, 44, 57) == \ + sensor.device_state_attributes['date'] def test_multi_part_with_text(self): """Test multi part emails.""" @@ -91,9 +91,9 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = "sensor.emailtest" sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual('Link', sensor.state) - self.assertEqual("Test Message", - sensor.device_state_attributes['body']) + assert 'Link' == sensor.state + assert "Test Message" == \ + sensor.device_state_attributes['body'] def test_multi_part_only_html(self): """Test multi part emails with only HTML.""" @@ -117,10 +117,9 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual('Link', sensor.state) - self.assertEqual( - "Test Message", - sensor.device_state_attributes['body']) + assert 'Link' == sensor.state + assert "Test Message" == \ + sensor.device_state_attributes['body'] def test_multi_part_only_other_text(self): """Test multi part emails with only other text.""" @@ -141,9 +140,9 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual('Link', sensor.state) - self.assertEqual("Test Message", - sensor.device_state_attributes['body']) + assert 'Link' == sensor.state + assert "Test Message" == \ + sensor.device_state_attributes['body'] def test_multiple_emails(self): """Test multiple emails.""" @@ -179,11 +178,11 @@ class EmailContentSensor(unittest.TestCase): sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual("Test", states[0].state) - self.assertEqual("Test 2", states[1].state) + assert "Test" == states[0].state + assert "Test 2" == states[1].state - self.assertEqual("Test Message 2", - sensor.device_state_attributes['body']) + assert "Test Message 2" == \ + sensor.device_state_attributes['body'] def test_sender_not_allowed(self): """Test not whitelisted emails.""" @@ -200,7 +199,7 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual(None, sensor.state) + assert sensor.state is None def test_template(self): """Test value template.""" @@ -219,6 +218,5 @@ class EmailContentSensor(unittest.TestCase): sensor.entity_id = 'sensor.emailtest' sensor.schedule_update_ha_state(True) self.hass.block_till_done() - self.assertEqual( - "Test from sender@test.com with message Test Message", - sensor.state) + assert "Test from sender@test.com with message Test Message" == \ + sensor.state diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py index ba3a11d862b..9274ab678a9 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -71,7 +71,7 @@ class TestJewishCalenderSensor(unittest.TestCase): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, '23 Elul 5778') + assert sensor.state == '23 Elul 5778' def test_jewish_calendar_sensor_date_output_hebrew(self): """Test Jewish calendar sensor date output in hebrew.""" @@ -83,7 +83,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, "כ\"ג באלול ה\' תשע\"ח") + assert sensor.state == "כ\"ג באלול ה\' תשע\"ח" def test_jewish_calendar_sensor_holiday_name(self): """Test Jewish calendar sensor holiday name output in hebrew.""" @@ -95,7 +95,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, "א\' ראש השנה") + assert sensor.state == "א\' ראש השנה" def test_jewish_calendar_sensor_holiday_name_english(self): """Test Jewish calendar sensor holiday name output in english.""" @@ -107,7 +107,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, "Rosh Hashana I") + assert sensor.state == "Rosh Hashana I" def test_jewish_calendar_sensor_holyness(self): """Test Jewish calendar sensor holyness value.""" @@ -119,7 +119,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, 1) + assert sensor.state == 1 def test_jewish_calendar_sensor_torah_reading(self): """Test Jewish calendar sensor torah reading in hebrew.""" @@ -131,7 +131,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, "פרשת נצבים") + assert sensor.state == "פרשת נצבים" def test_jewish_calendar_sensor_first_stars_ny(self): """Test Jewish calendar sensor first stars time in NY, US.""" @@ -143,7 +143,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, time(19, 48)) + assert sensor.state == time(19, 48) def test_jewish_calendar_sensor_first_stars_jerusalem(self): """Test Jewish calendar sensor first stars time in Jerusalem, IL.""" @@ -155,7 +155,7 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, time(19, 21)) + assert sensor.state == time(19, 21) def test_jewish_calendar_sensor_torah_reading_weekday(self): """Test the sensor showing torah reading also on weekdays.""" @@ -167,4 +167,4 @@ class TestJewishCalenderSensor(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - self.assertEqual(sensor.state, "פרשת לך לך") + assert sensor.state == "פרשת לך לך" diff --git a/tests/components/sensor/test_london_air.py b/tests/components/sensor/test_london_air.py index 56084095bd2..e6a11b6724c 100644 --- a/tests/components/sensor/test_london_air.py +++ b/tests/components/sensor/test_london_air.py @@ -31,8 +31,7 @@ class TestLondonAirSensor(unittest.TestCase): def test_setup(self, mock_req): """Test for operational tube_state sensor with proper attributes.""" mock_req.get(URL, text=load_fixture('london_air.json')) - self.assertTrue( - setup_component(self.hass, 'sensor', {'sensor': self.config})) + assert setup_component(self.hass, 'sensor', {'sensor': self.config}) state = self.hass.states.get('sensor.merton') assert state.state == 'Low' diff --git a/tests/components/sensor/test_london_underground.py b/tests/components/sensor/test_london_underground.py index fbffcbd1d8f..7acd61f6440 100644 --- a/tests/components/sensor/test_london_underground.py +++ b/tests/components/sensor/test_london_underground.py @@ -30,8 +30,7 @@ class TestLondonTubeSensor(unittest.TestCase): def test_setup(self, mock_req): """Test for operational tube_state sensor with proper attributes.""" mock_req.get(URL, text=load_fixture('london_underground.json')) - self.assertTrue( - setup_component(self.hass, 'sensor', {'sensor': self.config})) + assert setup_component(self.hass, 'sensor', {'sensor': self.config}) state = self.hass.states.get('sensor.london_overground') assert state.state == 'Minor Delays' diff --git a/tests/components/sensor/test_melissa.py b/tests/components/sensor/test_melissa.py index 7ac90221f16..aff3df83c3d 100644 --- a/tests/components/sensor/test_melissa.py +++ b/tests/components/sensor/test_melissa.py @@ -50,40 +50,40 @@ class TestMelissa(unittest.TestCase): def test_name(self): """Test name property.""" device = self.api.fetch_devices()[self._serial] - self.assertEqual(self.temp.name, '{0} {1}'.format( + assert self.temp.name == '{0} {1}'.format( device['name'], self.temp._type - )) - self.assertEqual(self.hum.name, '{0} {1}'.format( + ) + assert self.hum.name == '{0} {1}'.format( device['name'], self.hum._type - )) + ) def test_state(self): """Test state property.""" device = self.api.status()[self._serial] self.temp.update() - self.assertEqual(self.temp.state, device[self.api.TEMP]) + assert self.temp.state == device[self.api.TEMP] self.hum.update() - self.assertEqual(self.hum.state, device[self.api.HUMIDITY]) + assert self.hum.state == device[self.api.HUMIDITY] def test_unit_of_measurement(self): """Test unit of measurement property.""" - self.assertEqual(self.temp.unit_of_measurement, TEMP_CELSIUS) - self.assertEqual(self.hum.unit_of_measurement, '%') + assert self.temp.unit_of_measurement == TEMP_CELSIUS + assert self.hum.unit_of_measurement == '%' def test_update(self): """Test for update.""" self.temp.update() - self.assertEqual(self.temp.state, 27.4) + assert self.temp.state == 27.4 self.hum.update() - self.assertEqual(self.hum.state, 18.7) + assert self.hum.state == 18.7 def test_update_keyerror(self): """Test for faulty update.""" self.temp._api.status.return_value = {} self.temp.update() - self.assertEqual(None, self.temp.state) + assert self.temp.state is None self.hum._api.status.return_value = {} self.hum.update() - self.assertEqual(None, self.hum.state) + assert self.hum.state is None diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index a10246ad777..d30618a330d 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -55,17 +55,15 @@ class TestMfiSensorSetup(unittest.TestCase): from mficlient.client import FailedToLogin mock_client.side_effect = FailedToLogin - self.assertFalse( - self.PLATFORM.setup_platform( - self.hass, dict(self.GOOD_CONFIG), None)) + assert not self.PLATFORM.setup_platform( + self.hass, dict(self.GOOD_CONFIG), None) @mock.patch('mficlient.client.MFiClient') def test_setup_failed_connect(self, mock_client): """Test setup with connection failure.""" mock_client.side_effect = requests.exceptions.ConnectionError - self.assertFalse( - self.PLATFORM.setup_platform( - self.hass, dict(self.GOOD_CONFIG), None)) + assert not self.PLATFORM.setup_platform( + self.hass, dict(self.GOOD_CONFIG), None) @mock.patch('mficlient.client.MFiClient') def test_setup_minimum(self, mock_client): @@ -73,13 +71,11 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) del config[self.THING]['port'] assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - self.assertEqual(mock_client.call_count, 1) - self.assertEqual( - mock_client.call_args, + assert mock_client.call_count == 1 + assert mock_client.call_args == \ mock.call( 'foo', 'user', 'pass', port=6443, use_tls=True, verify=True ) - ) @mock.patch('mficlient.client.MFiClient') def test_setup_with_port(self, mock_client): @@ -87,13 +83,11 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) config[self.THING]['port'] = 6123 assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - self.assertEqual(mock_client.call_count, 1) - self.assertEqual( - mock_client.call_args, + assert mock_client.call_count == 1 + assert mock_client.call_args == \ mock.call( 'foo', 'user', 'pass', port=6123, use_tls=True, verify=True ) - ) @mock.patch('mficlient.client.MFiClient') def test_setup_with_tls_disabled(self, mock_client): @@ -103,13 +97,11 @@ class TestMfiSensorSetup(unittest.TestCase): config[self.THING]['ssl'] = False config[self.THING]['verify_ssl'] = False assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) - self.assertEqual(mock_client.call_count, 1) - self.assertEqual( - mock_client.call_args, + assert mock_client.call_count == 1 + assert mock_client.call_args == \ mock.call( 'foo', 'user', 'pass', port=6080, use_tls=False, verify=False ) - ) @mock.patch('mficlient.client.MFiClient') @mock.patch('homeassistant.components.sensor.mfi.MfiSensor') @@ -142,59 +134,59 @@ class TestMfiSensor(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual(self.port.label, self.sensor.name) + assert self.port.label == self.sensor.name def test_uom_temp(self): """Test the UOM temperature.""" self.port.tag = 'temperature' - self.assertEqual(TEMP_CELSIUS, self.sensor.unit_of_measurement) + assert TEMP_CELSIUS == self.sensor.unit_of_measurement def test_uom_power(self): """Test the UOEM power.""" self.port.tag = 'active_pwr' - self.assertEqual('Watts', self.sensor.unit_of_measurement) + assert 'Watts' == self.sensor.unit_of_measurement def test_uom_digital(self): """Test the UOM digital input.""" self.port.model = 'Input Digital' - self.assertEqual('State', self.sensor.unit_of_measurement) + assert 'State' == self.sensor.unit_of_measurement def test_uom_unknown(self): """Test the UOM.""" self.port.tag = 'balloons' - self.assertEqual('balloons', self.sensor.unit_of_measurement) + assert 'balloons' == self.sensor.unit_of_measurement def test_uom_uninitialized(self): """Test that the UOM defaults if not initialized.""" type(self.port).tag = mock.PropertyMock(side_effect=ValueError) - self.assertEqual('State', self.sensor.unit_of_measurement) + assert 'State' == self.sensor.unit_of_measurement def test_state_digital(self): """Test the digital input.""" self.port.model = 'Input Digital' self.port.value = 0 - self.assertEqual(mfi.STATE_OFF, self.sensor.state) + assert mfi.STATE_OFF == self.sensor.state self.port.value = 1 - self.assertEqual(mfi.STATE_ON, self.sensor.state) + assert mfi.STATE_ON == self.sensor.state self.port.value = 2 - self.assertEqual(mfi.STATE_ON, self.sensor.state) + assert mfi.STATE_ON == self.sensor.state def test_state_digits(self): """Test the state of digits.""" self.port.tag = 'didyoucheckthedict?' self.port.value = 1.25 with mock.patch.dict(mfi.DIGITS, {'didyoucheckthedict?': 1}): - self.assertEqual(1.2, self.sensor.state) + assert 1.2 == self.sensor.state with mock.patch.dict(mfi.DIGITS, {}): - self.assertEqual(1.0, self.sensor.state) + assert 1.0 == self.sensor.state def test_state_uninitialized(self): """Test the state of uninitialized sensors.""" type(self.port).tag = mock.PropertyMock(side_effect=ValueError) - self.assertEqual(mfi.STATE_OFF, self.sensor.state) + assert mfi.STATE_OFF == self.sensor.state def test_update(self): """Test the update.""" self.sensor.update() - self.assertEqual(self.port.refresh.call_count, 1) - self.assertEqual(self.port.refresh.call_args, mock.call()) + assert self.port.refresh.call_count == 1 + assert self.port.refresh.call_args == mock.call() diff --git a/tests/components/sensor/test_mhz19.py b/tests/components/sensor/test_mhz19.py index 421035995dc..003cc199954 100644 --- a/tests/components/sensor/test_mhz19.py +++ b/tests/components/sensor/test_mhz19.py @@ -31,10 +31,10 @@ class TestMHZ19Sensor(unittest.TestCase): @patch('pmsensor.co2sensor.read_mh_z19', side_effect=OSError('test error')) def test_setup_failed_connect(self, mock_co2): """Test setup when connection error occurs.""" - self.assertFalse(mhz19.setup_platform(self.hass, { + assert not mhz19.setup_platform(self.hass, { 'platform': 'mhz19', mhz19.CONF_SERIAL_DEVICE: 'test.serial', - }, None)) + }, None) def test_setup_connected(self): """Test setup when connection succeeds.""" @@ -43,12 +43,12 @@ class TestMHZ19Sensor(unittest.TestCase): from pmsensor.co2sensor import read_mh_z19_with_temperature read_mh_z19_with_temperature.return_value = None mock_add = Mock() - self.assertTrue(mhz19.setup_platform(self.hass, { + assert mhz19.setup_platform(self.hass, { 'platform': 'mhz19', 'monitored_conditions': ['co2', 'temperature'], mhz19.CONF_SERIAL_DEVICE: 'test.serial', - }, mock_add)) - self.assertEqual(1, mock_add.call_count) + }, mock_add) + assert 1 == mock_add.call_count @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', side_effect=OSError('test error')) @@ -57,7 +57,7 @@ class TestMHZ19Sensor(unittest.TestCase): from pmsensor import co2sensor client = mhz19.MHZClient(co2sensor, 'test.serial') client.update() - self.assertEqual({}, client.data) + assert {} == client.data @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', return_value=(5001, 24)) @@ -66,7 +66,7 @@ class TestMHZ19Sensor(unittest.TestCase): from pmsensor import co2sensor client = mhz19.MHZClient(co2sensor, 'test.serial') client.update() - self.assertIsNone(client.data.get('co2')) + assert client.data.get('co2') is None @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', return_value=(1000, 24)) @@ -75,7 +75,7 @@ class TestMHZ19Sensor(unittest.TestCase): from pmsensor import co2sensor client = mhz19.MHZClient(co2sensor, 'test.serial') client.update() - self.assertEqual({'temperature': 24, 'co2': 1000}, client.data) + assert {'temperature': 24, 'co2': 1000} == client.data @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', return_value=(1000, 24)) @@ -86,11 +86,11 @@ class TestMHZ19Sensor(unittest.TestCase): sensor = mhz19.MHZ19Sensor(client, mhz19.SENSOR_CO2, None, 'name') sensor.update() - self.assertEqual('name: CO2', sensor.name) - self.assertEqual(1000, sensor.state) - self.assertEqual('ppm', sensor.unit_of_measurement) - self.assertTrue(sensor.should_poll) - self.assertEqual({'temperature': 24}, sensor.device_state_attributes) + assert 'name: CO2' == sensor.name + assert 1000 == sensor.state + assert 'ppm' == sensor.unit_of_measurement + assert sensor.should_poll + assert {'temperature': 24} == sensor.device_state_attributes @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', return_value=(1000, 24)) @@ -102,12 +102,11 @@ class TestMHZ19Sensor(unittest.TestCase): client, mhz19.SENSOR_TEMPERATURE, None, 'name') sensor.update() - self.assertEqual('name: Temperature', sensor.name) - self.assertEqual(24, sensor.state) - self.assertEqual('°C', sensor.unit_of_measurement) - self.assertTrue(sensor.should_poll) - self.assertEqual( - {'co2_concentration': 1000}, sensor.device_state_attributes) + assert 'name: Temperature' == sensor.name + assert 24 == sensor.state + assert '°C' == sensor.unit_of_measurement + assert sensor.should_poll + assert {'co2_concentration': 1000} == sensor.device_state_attributes @patch('pmsensor.co2sensor.read_mh_z19_with_temperature', return_value=(1000, 24)) @@ -119,4 +118,4 @@ class TestMHZ19Sensor(unittest.TestCase): client, mhz19.SENSOR_TEMPERATURE, TEMP_FAHRENHEIT, 'name') sensor.update() - self.assertEqual(75.2, sensor.state) + assert 75.2 == sensor.state diff --git a/tests/components/sensor/test_min_max.py b/tests/components/sensor/test_min_max.py index 0376c780ee7..ae2f40e5802 100644 --- a/tests/components/sensor/test_min_max.py +++ b/tests/components/sensor/test_min_max.py @@ -50,9 +50,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_min') - self.assertEqual(str(float(self.min)), state.state) - self.assertEqual(self.max, state.attributes.get('max_value')) - self.assertEqual(self.mean, state.attributes.get('mean')) + assert str(float(self.min)) == state.state + assert self.max == state.attributes.get('max_value') + assert self.mean == state.attributes.get('mean') def test_max_sensor(self): """Test the max sensor.""" @@ -79,9 +79,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_max') - self.assertEqual(str(float(self.max)), state.state) - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.mean, state.attributes.get('mean')) + assert str(float(self.max)) == state.state + assert self.min == state.attributes.get('min_value') + assert self.mean == state.attributes.get('mean') def test_mean_sensor(self): """Test the mean sensor.""" @@ -108,9 +108,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(str(float(self.mean)), state.state) - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.max, state.attributes.get('max_value')) + assert str(float(self.mean)) == state.state + assert self.min == state.attributes.get('min_value') + assert self.max == state.attributes.get('max_value') def test_mean_1_digit_sensor(self): """Test the mean with 1-digit precision sensor.""" @@ -138,9 +138,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(str(float(self.mean_1_digit)), state.state) - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.max, state.attributes.get('max_value')) + assert str(float(self.mean_1_digit)) == state.state + assert self.min == state.attributes.get('min_value') + assert self.max == state.attributes.get('max_value') def test_mean_4_digit_sensor(self): """Test the mean with 1-digit precision sensor.""" @@ -168,9 +168,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(str(float(self.mean_4_digits)), state.state) - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.max, state.attributes.get('max_value')) + assert str(float(self.mean_4_digits)) == state.state + assert self.min == state.attributes.get('min_value') + assert self.max == state.attributes.get('max_value') def test_not_enough_sensor_value(self): """Test that there is nothing done if not enough values available.""" @@ -195,25 +195,25 @@ class TestMinMaxSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test_max') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state self.hass.states.set(entity_ids[1], self.values[1]) self.hass.block_till_done() state = self.hass.states.get('sensor.test_max') - self.assertNotEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN != state.state self.hass.states.set(entity_ids[2], STATE_UNKNOWN) self.hass.block_till_done() state = self.hass.states.get('sensor.test_max') - self.assertNotEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN != state.state self.hass.states.set(entity_ids[1], STATE_UNKNOWN) self.hass.block_till_done() state = self.hass.states.get('sensor.test_max') - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state def test_different_unit_of_measurement(self): """Test for different unit of measurement.""" @@ -240,8 +240,8 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test') - self.assertEqual(str(float(self.values[0])), state.state) - self.assertEqual('°C', state.attributes.get('unit_of_measurement')) + assert str(float(self.values[0])) == state.state + assert '°C' == state.attributes.get('unit_of_measurement') self.hass.states.set(entity_ids[1], self.values[1], {ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT}) @@ -249,8 +249,8 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNKNOWN, state.state) - self.assertEqual('ERR', state.attributes.get('unit_of_measurement')) + assert STATE_UNKNOWN == state.state + assert 'ERR' == state.attributes.get('unit_of_measurement') self.hass.states.set(entity_ids[2], self.values[2], {ATTR_UNIT_OF_MEASUREMENT: '%'}) @@ -258,8 +258,8 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNKNOWN, state.state) - self.assertEqual('ERR', state.attributes.get('unit_of_measurement')) + assert STATE_UNKNOWN == state.state + assert 'ERR' == state.attributes.get('unit_of_measurement') def test_last_sensor(self): """Test the last sensor.""" @@ -285,8 +285,8 @@ class TestMinMaxSensor(unittest.TestCase): self.hass.states.set(entity_id, value) self.hass.block_till_done() state = self.hass.states.get('sensor.test_last') - self.assertEqual(str(float(value)), state.state) + assert str(float(value)) == state.state - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.max, state.attributes.get('max_value')) - self.assertEqual(self.mean, state.attributes.get('mean')) + assert self.min == state.attributes.get('min_value') + assert self.max == state.attributes.get('max_value') + assert self.mean == state.attributes.get('mean') diff --git a/tests/components/sensor/test_moldindicator.py b/tests/components/sensor/test_moldindicator.py index 7b2480f1298..8b39804cacd 100644 --- a/tests/components/sensor/test_moldindicator.py +++ b/tests/components/sensor/test_moldindicator.py @@ -30,7 +30,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_setup(self): """Test the mold indicator sensor setup.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -38,7 +38,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 2.0 } - })) + }) moldind = self.hass.states.get('sensor.mold_indicator') assert moldind @@ -53,7 +53,7 @@ class TestSensorMoldIndicator(unittest.TestCase): self.hass.states.set('test.indoorhumidity', '0', {ATTR_UNIT_OF_MEASUREMENT: '%'}) - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -61,7 +61,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 0 } - })) + }) self.hass.start() self.hass.block_till_done() moldind = self.hass.states.get('sensor.mold_indicator') @@ -79,7 +79,7 @@ class TestSensorMoldIndicator(unittest.TestCase): self.hass.states.set('test.indoorhumidity', '-1', {ATTR_UNIT_OF_MEASUREMENT: '%'}) - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -87,7 +87,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 2.0 } - })) + }) self.hass.start() self.hass.block_till_done() @@ -117,7 +117,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_calculation(self): """Test the mold indicator internal calculations.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -125,7 +125,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 2.0 } - })) + }) self.hass.start() self.hass.block_till_done() moldind = self.hass.states.get('sensor.mold_indicator') @@ -150,7 +150,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_unknown_sensor(self): """Test the sensor_changed function.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -158,7 +158,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 2.0 } - })) + }) self.hass.start() self.hass.states.set('test.indoortemp', STATE_UNKNOWN, @@ -210,7 +210,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_sensor_changed(self): """Test the sensor_changed function.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -218,7 +218,7 @@ class TestSensorMoldIndicator(unittest.TestCase): 'indoor_humidity_sensor': 'test.indoorhumidity', 'calibration_factor': 2.0 } - })) + }) self.hass.start() self.hass.states.set('test.indoortemp', '30', diff --git a/tests/components/sensor/test_moon.py b/tests/components/sensor/test_moon.py index 9086df6e79b..14c93678a0f 100644 --- a/tests/components/sensor/test_moon.py +++ b/tests/components/sensor/test_moon.py @@ -37,7 +37,7 @@ class TestMoonSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.moon_day1') - self.assertEqual(state.state, 'waxing_crescent') + assert state.state == 'waxing_crescent' @patch('homeassistant.components.sensor.moon.dt_util.utcnow', return_value=DAY2) @@ -53,4 +53,4 @@ class TestMoonSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.moon_day2') - self.assertEqual(state.state, 'waning_gibbous') + assert state.state == 'waning_gibbous' diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 873de5a9bd6..15042805a66 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -47,9 +47,9 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual('100', state.state) - self.assertEqual('fav unit', - state.attributes.get('unit_of_measurement')) + assert '100' == state.state + assert 'fav unit' == \ + state.attributes.get('unit_of_measurement') @patch('homeassistant.core.dt_util.utcnow') def test_setting_sensor_value_expires(self, mock_utcnow): @@ -67,7 +67,7 @@ class TestSensorMQTT(unittest.TestCase): }) state = self.hass.states.get('sensor.test') - self.assertEqual('unknown', state.state) + assert 'unknown' == state.state now = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) mock_utcnow.return_value = now @@ -76,7 +76,7 @@ class TestSensorMQTT(unittest.TestCase): # Value was set correctly. state = self.hass.states.get('sensor.test') - self.assertEqual('100', state.state) + assert '100' == state.state # Time jump +3s now = now + timedelta(seconds=3) @@ -85,7 +85,7 @@ class TestSensorMQTT(unittest.TestCase): # Value is not yet expired state = self.hass.states.get('sensor.test') - self.assertEqual('100', state.state) + assert '100' == state.state # Next message resets timer mock_utcnow.return_value = now @@ -94,7 +94,7 @@ class TestSensorMQTT(unittest.TestCase): # Value was updated correctly. state = self.hass.states.get('sensor.test') - self.assertEqual('101', state.state) + assert '101' == state.state # Time jump +3s now = now + timedelta(seconds=3) @@ -103,7 +103,7 @@ class TestSensorMQTT(unittest.TestCase): # Value is not yet expired state = self.hass.states.get('sensor.test') - self.assertEqual('101', state.state) + assert '101' == state.state # Time jump +2s now = now + timedelta(seconds=2) @@ -112,7 +112,7 @@ class TestSensorMQTT(unittest.TestCase): # Value is expired now state = self.hass.states.get('sensor.test') - self.assertEqual('unknown', state.state) + assert 'unknown' == state.state def test_setting_sensor_value_via_mqtt_json_message(self): """Test the setting of the value via MQTT with JSON payload.""" @@ -131,7 +131,7 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual('100', state.state) + assert '100' == state.state def test_force_update_disabled(self): """Test force update option.""" @@ -155,11 +155,11 @@ class TestSensorMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', '100') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) fire_mqtt_message(self.hass, 'test-topic', '100') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) def test_force_update_enabled(self): """Test force update option.""" @@ -184,41 +184,41 @@ class TestSensorMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', '100') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) fire_mqtt_message(self.hass, 'test-topic', '100') self.hass.block_till_done() - self.assertEqual(2, len(events)) + assert 2 == len(events) def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'test-topic', 'availability_topic': 'availability-topic' } - })) + }) state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { 'platform': 'mqtt', 'name': 'test', @@ -227,22 +227,22 @@ class TestSensorMQTT(unittest.TestCase): 'payload_available': 'good', 'payload_not_available': 'nogood' } - })) + }) state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def _send_time_changed(self, now): """Send a time changed event.""" @@ -265,8 +265,8 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual('100', - state.attributes.get('val')) + assert '100' == \ + state.attributes.get('val') @patch('homeassistant.components.sensor.mqtt._LOGGER') def test_update_with_json_attrs_not_dict(self, mock_logger): @@ -286,9 +286,8 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual(None, - state.attributes.get('val')) - self.assertTrue(mock_logger.warning.called) + assert state.attributes.get('val') is None + assert mock_logger.warning.called @patch('homeassistant.components.sensor.mqtt._LOGGER') def test_update_with_json_attrs_bad_JSON(self, mock_logger): @@ -308,10 +307,9 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual(None, - state.attributes.get('val')) - self.assertTrue(mock_logger.warning.called) - self.assertTrue(mock_logger.debug.called) + assert state.attributes.get('val') is None + assert mock_logger.warning.called + assert mock_logger.debug.called def test_update_with_json_attrs_and_template(self): """Test attributes get extracted from a JSON result.""" @@ -331,9 +329,9 @@ class TestSensorMQTT(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('sensor.test') - self.assertEqual('100', - state.attributes.get('val')) - self.assertEqual('100', state.state) + assert '100' == \ + state.attributes.get('val') + assert '100' == state.state def test_invalid_device_class(self): """Test device_class option with invalid value.""" diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py index 88fa611b2a6..74852abff4d 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -53,7 +53,7 @@ class TestMQTTRoomSensor(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { + assert setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { CONF_PLATFORM: 'mqtt_room', CONF_NAME: NAME, @@ -61,7 +61,7 @@ class TestMQTTRoomSensor(unittest.TestCase): CONF_STATE_TOPIC: 'room_presence', CONF_QOS: DEFAULT_QOS, CONF_TIMEOUT: 5 - }})) + }}) # Clear state between tests self.hass.states.set(SENSOR_STATE, None) @@ -79,12 +79,12 @@ class TestMQTTRoomSensor(unittest.TestCase): def assert_state(self, room): """Test the assertion of a room state.""" state = self.hass.states.get(SENSOR_STATE) - self.assertEqual(state.state, room) + assert state.state == room def assert_distance(self, distance): """Test the assertion of a distance state.""" state = self.hass.states.get(SENSOR_STATE) - self.assertEqual(state.attributes.get('distance'), distance) + assert state.attributes.get('distance') == distance def test_room_update(self): """Test the updating between rooms.""" diff --git a/tests/components/sensor/test_nsw_fuel_station.py b/tests/components/sensor/test_nsw_fuel_station.py index 1ee314d9eee..aa5c2fbe563 100644 --- a/tests/components/sensor/test_nsw_fuel_station.py +++ b/tests/components/sensor/test_nsw_fuel_station.py @@ -92,8 +92,8 @@ class TestNSWFuelStation(unittest.TestCase): def test_setup(self, mock_nsw_fuel): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): - self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { - 'sensor': VALID_CONFIG})) + assert setup_component(self.hass, sensor.DOMAIN, { + 'sensor': VALID_CONFIG}) fake_entities = [ 'my_fake_station_p95', @@ -102,16 +102,16 @@ class TestNSWFuelStation(unittest.TestCase): for entity_id in fake_entities: state = self.hass.states.get('sensor.{}'.format(entity_id)) - self.assertIsNotNone(state) + assert state is not None @MockDependency('nsw_fuel') @patch('nsw_fuel.FuelCheckClient', new=FuelCheckClientMock) def test_sensor_values(self, mock_nsw_fuel): """Test retrieval of sensor values.""" - self.assertTrue(setup_component( - self.hass, sensor.DOMAIN, {'sensor': VALID_CONFIG})) + assert setup_component( + self.hass, sensor.DOMAIN, {'sensor': VALID_CONFIG}) - self.assertEqual('140.0', self.hass.states.get( - 'sensor.my_fake_station_e10').state) - self.assertEqual('150.0', self.hass.states.get( - 'sensor.my_fake_station_p95').state) + assert '140.0' == self.hass.states.get( + 'sensor.my_fake_station_e10').state + assert '150.0' == self.hass.states.get( + 'sensor.my_fake_station_p95').state diff --git a/tests/components/sensor/test_openhardwaremonitor.py b/tests/components/sensor/test_openhardwaremonitor.py index f66b6dcb3b5..5117f87cd70 100644 --- a/tests/components/sensor/test_openhardwaremonitor.py +++ b/tests/components/sensor/test_openhardwaremonitor.py @@ -29,12 +29,12 @@ class TestOpenHardwareMonitorSetup(unittest.TestCase): mock_req.get('http://localhost:8085/data.json', text=load_fixture('openhardwaremonitor.json')) - self.assertTrue(setup_component(self.hass, 'sensor', self.config)) + assert setup_component(self.hass, 'sensor', self.config) entities = self.hass.states.async_entity_ids('sensor') - self.assertEqual(len(entities), 38) + assert len(entities) == 38 state = self.hass.states.get( 'sensor.testpc_intel_core_i77700_clocks_bus_speed') - self.assertIsNot(state, None) - self.assertEqual(state.state, '100') + assert state is not None + assert state.state == '100' diff --git a/tests/components/sensor/test_radarr.py b/tests/components/sensor/test_radarr.py index 30195b73a13..5ac23f76251 100644 --- a/tests/components/sensor/test_radarr.py +++ b/tests/components/sensor/test_radarr.py @@ -224,14 +224,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('263.10', device.state) - self.assertEqual('mdi:harddisk', device.icon) - self.assertEqual('GB', device.unit_of_measurement) - self.assertEqual('Radarr Disk Space', device.name) - self.assertEqual( - '263.10/465.42GB (56.53%)', + assert '263.10' == device.state + assert 'mdi:harddisk' == device.icon + assert 'GB' == device.unit_of_measurement + assert 'Radarr Disk Space' == device.name + assert '263.10/465.42GB (56.53%)' == \ device.device_state_attributes["/data"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_diskspace_paths(self, req_mock): @@ -251,14 +249,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('263.10', device.state) - self.assertEqual('mdi:harddisk', device.icon) - self.assertEqual('GB', device.unit_of_measurement) - self.assertEqual('Radarr Disk Space', device.name) - self.assertEqual( - '263.10/465.42GB (56.53%)', + assert '263.10' == device.state + assert 'mdi:harddisk' == device.icon + assert 'GB' == device.unit_of_measurement + assert 'Radarr Disk Space' == device.name + assert '263.10/465.42GB (56.53%)' == \ device.device_state_attributes["/data"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_commands(self, req_mock): @@ -278,14 +274,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:code-braces', device.icon) - self.assertEqual('Commands', device.unit_of_measurement) - self.assertEqual('Radarr Commands', device.name) - self.assertEqual( - 'pending', + assert 1 == device.state + assert 'mdi:code-braces' == device.icon + assert 'Commands' == device.unit_of_measurement + assert 'Radarr Commands' == device.name + assert 'pending' == \ device.device_state_attributes["RescanMovie"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_movies(self, req_mock): @@ -305,14 +299,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Movies', device.unit_of_measurement) - self.assertEqual('Radarr Movies', device.name) - self.assertEqual( - 'false', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Movies' == device.unit_of_measurement + assert 'Radarr Movies' == device.name + assert 'false' == \ device.device_state_attributes["Assassin's Creed (2016)"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_upcoming_multiple_days(self, req_mock): @@ -332,14 +324,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Movies', device.unit_of_measurement) - self.assertEqual('Radarr Upcoming', device.name) - self.assertEqual( - '2017-01-27T00:00:00Z', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Movies' == device.unit_of_measurement + assert 'Radarr Upcoming' == device.name + assert '2017-01-27T00:00:00Z' == \ device.device_state_attributes["Resident Evil (2017)"] - ) @pytest.mark.skip @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) @@ -363,14 +353,12 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Movies', device.unit_of_measurement) - self.assertEqual('Radarr Upcoming', device.name) - self.assertEqual( - '2017-01-27T00:00:00Z', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Movies' == device.unit_of_measurement + assert 'Radarr Upcoming' == device.name + assert '2017-01-27T00:00:00Z' == \ device.device_state_attributes["Resident Evil (2017)"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_system_status(self, req_mock): @@ -390,11 +378,10 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('0.2.0.210', device.state) - self.assertEqual('mdi:information', device.icon) - self.assertEqual('Radarr Status', device.name) - self.assertEqual( - '4.8.13.1', device.device_state_attributes['osVersion']) + assert '0.2.0.210' == device.state + assert 'mdi:information' == device.icon + assert 'Radarr Status' == device.name + assert '4.8.13.1' == device.device_state_attributes['osVersion'] @pytest.mark.skip @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) @@ -416,15 +403,13 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('s', device.ssl) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Movies', device.unit_of_measurement) - self.assertEqual('Radarr Upcoming', device.name) - self.assertEqual( - '2017-01-27T00:00:00Z', + assert 1 == device.state + assert 's' == device.ssl + assert 'mdi:television' == device.icon + assert 'Movies' == device.unit_of_measurement + assert 'Radarr Upcoming' == device.name + assert '2017-01-27T00:00:00Z' == \ device.device_state_attributes["Resident Evil (2017)"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_exception) def test_exception_handling(self, req_mock): @@ -444,4 +429,4 @@ class TestRadarrSetup(unittest.TestCase): radarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(None, device.state) + assert device.state is None diff --git a/tests/components/sensor/test_random.py b/tests/components/sensor/test_random.py index e04fc31af84..81f7a18f486 100644 --- a/tests/components/sensor/test_random.py +++ b/tests/components/sensor/test_random.py @@ -32,5 +32,5 @@ class TestRandomSensor(unittest.TestCase): state = self.hass.states.get('sensor.test') - self.assertLessEqual(int(state.state), config['sensor']['maximum']) - self.assertGreaterEqual(int(state.state), config['sensor']['minimum']) + assert int(state.state) <= config['sensor']['maximum'] + assert int(state.state) >= config['sensor']['minimum'] diff --git a/tests/components/sensor/test_rest.py b/tests/components/sensor/test_rest.py index 7f818193a29..2ce72fc4fc4 100644 --- a/tests/components/sensor/test_rest.py +++ b/tests/components/sensor/test_rest.py @@ -15,6 +15,7 @@ from homeassistant.const import STATE_UNKNOWN from homeassistant.helpers.config_validation import template from tests.common import get_test_home_assistant, assert_setup_component +import pytest class TestRestSensorSetup(unittest.TestCase): @@ -36,7 +37,7 @@ class TestRestSensorSetup(unittest.TestCase): def test_setup_missing_schema(self): """Test setup with resource missing schema.""" - with self.assertRaises(MissingSchema): + with pytest.raises(MissingSchema): rest.setup_platform(self.hass, { 'platform': 'rest', 'resource': 'localhost', @@ -67,20 +68,20 @@ class TestRestSensorSetup(unittest.TestCase): """Test setup with minimum configuration.""" mock_req.get('http://localhost', status_code=200) with assert_setup_component(1, 'sensor'): - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': { 'platform': 'rest', 'resource': 'http://localhost' } - })) - self.assertEqual(2, mock_req.call_count) + }) + assert 2 == mock_req.call_count @requests_mock.Mocker() def test_setup_get(self, mock_req): """Test setup with valid configuration.""" mock_req.get('http://localhost', status_code=200) with assert_setup_component(1, 'sensor'): - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': { 'platform': 'rest', 'resource': 'http://localhost', @@ -94,15 +95,15 @@ class TestRestSensorSetup(unittest.TestCase): 'password': 'my password', 'headers': {'Accept': 'application/json'} } - })) - self.assertEqual(2, mock_req.call_count) + }) + assert 2 == mock_req.call_count @requests_mock.Mocker() def test_setup_post(self, mock_req): """Test setup with valid configuration.""" mock_req.post('http://localhost', status_code=200) with assert_setup_component(1, 'sensor'): - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': { 'platform': 'rest', 'resource': 'http://localhost', @@ -117,8 +118,8 @@ class TestRestSensorSetup(unittest.TestCase): 'password': 'my password', 'headers': {'Accept': 'application/json'} } - })) - self.assertEqual(2, mock_req.call_count) + }) + assert 2 == mock_req.call_count class TestRestSensor(unittest.TestCase): @@ -153,30 +154,28 @@ class TestRestSensor(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual(self.name, self.sensor.name) + assert self.name == self.sensor.name def test_unit_of_measurement(self): """Test the unit of measurement.""" - self.assertEqual( - self.unit_of_measurement, self.sensor.unit_of_measurement) + assert self.unit_of_measurement == self.sensor.unit_of_measurement def test_force_update(self): """Test the unit of measurement.""" - self.assertEqual( - self.force_update, self.sensor.force_update) + assert self.force_update == self.sensor.force_update def test_state(self): """Test the initial state.""" self.sensor.update() - self.assertEqual(self.initial_state, self.sensor.state) + assert self.initial_state == self.sensor.state def test_update_when_value_is_none(self): """Test state gets updated to unknown when sensor returns no data.""" self.rest.update = Mock( 'rest.RestData.update', side_effect=self.update_side_effect(None)) self.sensor.update() - self.assertEqual(STATE_UNKNOWN, self.sensor.state) - self.assertFalse(self.sensor.available) + assert STATE_UNKNOWN == self.sensor.state + assert not self.sensor.available def test_update_when_value_changed(self): """Test state gets updated when sensor returns a new status.""" @@ -184,8 +183,8 @@ class TestRestSensor(unittest.TestCase): side_effect=self.update_side_effect( '{ "key": "updated_state" }')) self.sensor.update() - self.assertEqual('updated_state', self.sensor.state) - self.assertTrue(self.sensor.available) + assert 'updated_state' == self.sensor.state + assert self.sensor.available def test_update_with_no_template(self): """Test update when there is no value template.""" @@ -196,8 +195,8 @@ class TestRestSensor(unittest.TestCase): self.unit_of_measurement, None, [], self.force_update) self.sensor.update() - self.assertEqual('plain_state', self.sensor.state) - self.assertTrue(self.sensor.available) + assert 'plain_state' == self.sensor.state + assert self.sensor.available def test_update_with_json_attrs(self): """Test attributes get extracted from a JSON result.""" @@ -208,8 +207,8 @@ class TestRestSensor(unittest.TestCase): self.unit_of_measurement, None, ['key'], self.force_update) self.sensor.update() - self.assertEqual('some_json_value', - self.sensor.device_state_attributes['key']) + assert 'some_json_value' == \ + self.sensor.device_state_attributes['key'] @patch('homeassistant.components.sensor.rest._LOGGER') def test_update_with_json_attrs_no_data(self, mock_logger): @@ -220,8 +219,8 @@ class TestRestSensor(unittest.TestCase): self.unit_of_measurement, None, ['key'], self.force_update) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called @patch('homeassistant.components.sensor.rest._LOGGER') def test_update_with_json_attrs_not_dict(self, mock_logger): @@ -233,8 +232,8 @@ class TestRestSensor(unittest.TestCase): self.unit_of_measurement, None, ['key'], self.force_update) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called @patch('homeassistant.components.sensor.rest._LOGGER') def test_update_with_json_attrs_bad_JSON(self, mock_logger): @@ -246,9 +245,9 @@ class TestRestSensor(unittest.TestCase): self.unit_of_measurement, None, ['key'], self.force_update) self.sensor.update() - self.assertEqual({}, self.sensor.device_state_attributes) - self.assertTrue(mock_logger.warning.called) - self.assertTrue(mock_logger.debug.called) + assert {} == self.sensor.device_state_attributes + assert mock_logger.warning.called + assert mock_logger.debug.called def test_update_with_json_attrs_and_template(self): """Test attributes get extracted from a JSON result.""" @@ -261,10 +260,10 @@ class TestRestSensor(unittest.TestCase): self.force_update) self.sensor.update() - self.assertEqual('json_state_updated_value', self.sensor.state) - self.assertEqual('json_state_updated_value', - self.sensor.device_state_attributes['key'], - self.force_update) + assert 'json_state_updated_value' == self.sensor.state + assert 'json_state_updated_value' == \ + self.sensor.device_state_attributes['key'], \ + self.force_update class TestRestData(unittest.TestCase): @@ -283,10 +282,10 @@ class TestRestData(unittest.TestCase): """Test update.""" mock_req.get('http://localhost', text='test data') self.rest.update() - self.assertEqual('test data', self.rest.data) + assert 'test data' == self.rest.data @patch('requests.Session', side_effect=RequestException) def test_update_request_exception(self, mock_req): """Test update when a request exception occurs.""" self.rest.update() - self.assertEqual(None, self.rest.data) + assert self.rest.data is None diff --git a/tests/components/sensor/test_rfxtrx.py b/tests/components/sensor/test_rfxtrx.py index 3f577127a11..653a17e4111 100644 --- a/tests/components/sensor/test_rfxtrx.py +++ b/tests/components/sensor/test_rfxtrx.py @@ -29,66 +29,66 @@ class TestSensorRfxtrx(unittest.TestCase): def test_default_config(self): """Test with 0 sensor.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': - {}}})) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + {}}}) + assert 0 == len(rfxtrx_core.RFX_DEVICES) def test_old_config_sensor(self): """Test with 1 sensor.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': {'sensor_0502': { 'name': 'Test', 'packetid': '0a52080705020095220269', - 'data_type': 'Temperature'}}}})) + 'data_type': 'Temperature'}}}}) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['sensor_0502']['Temperature'] - self.assertEqual('Test', entity.name) - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual(None, entity.state) + assert 'Test' == entity.name + assert TEMP_CELSIUS == entity.unit_of_measurement + assert entity.state is None def test_one_sensor(self): """Test with 1 sensor.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': {'0a52080705020095220269': { 'name': 'Test', - 'data_type': 'Temperature'}}}})) + 'data_type': 'Temperature'}}}}) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['sensor_0502']['Temperature'] - self.assertEqual('Test', entity.name) - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual(None, entity.state) + assert 'Test' == entity.name + assert TEMP_CELSIUS == entity.unit_of_measurement + assert entity.state is None def test_one_sensor_no_datatype(self): """Test with 1 sensor.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': {'0a52080705020095220269': { - 'name': 'Test'}}}})) + 'name': 'Test'}}}}) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['sensor_0502']['Temperature'] - self.assertEqual('Test', entity.name) - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual(None, entity.state) + assert 'Test' == entity.name + assert TEMP_CELSIUS == entity.unit_of_measurement + assert entity.state is None entity_id = rfxtrx_core.RFX_DEVICES['sensor_0502']['Temperature']\ .entity_id entity = self.hass.states.get(entity_id) - self.assertEqual('Test', entity.name) - self.assertEqual('unknown', entity.state) + assert 'Test' == entity.name + assert 'unknown' == entity.state def test_several_sensors(self): """Test with 3 sensors.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': {'0a52080705020095220269': { @@ -97,119 +97,119 @@ class TestSensorRfxtrx(unittest.TestCase): '0a520802060100ff0e0269': { 'name': 'Bath', 'data_type': ['Temperature', 'Humidity'] - }}}})) + }}}}) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: if id == 'sensor_0601': device_num = device_num + 1 - self.assertEqual(len(rfxtrx_core.RFX_DEVICES[id]), 2) + assert len(rfxtrx_core.RFX_DEVICES[id]) == 2 _entity_temp = rfxtrx_core.RFX_DEVICES[id]['Temperature'] _entity_hum = rfxtrx_core.RFX_DEVICES[id]['Humidity'] - self.assertEqual('%', _entity_hum.unit_of_measurement) - self.assertEqual('Bath', _entity_hum.__str__()) - self.assertEqual(None, _entity_hum.state) - self.assertEqual(TEMP_CELSIUS, - _entity_temp.unit_of_measurement) - self.assertEqual('Bath', _entity_temp.__str__()) + assert '%' == _entity_hum.unit_of_measurement + assert 'Bath' == _entity_hum.__str__() + assert _entity_hum.state is None + assert TEMP_CELSIUS == \ + _entity_temp.unit_of_measurement + assert 'Bath' == _entity_temp.__str__() elif id == 'sensor_0502': device_num = device_num + 1 entity = rfxtrx_core.RFX_DEVICES[id]['Temperature'] - self.assertEqual(None, entity.state) - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual('Test', entity.__str__()) + assert entity.state is None + assert TEMP_CELSIUS == entity.unit_of_measurement + assert 'Test' == entity.__str__() - self.assertEqual(2, device_num) + assert 2 == device_num def test_discover_sensor(self): """Test with discovery of sensor.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'automatic_add': True, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0a520801070100b81b0279') event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['sensor_0701']['Temperature'] - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual({'Humidity status': 'normal', - 'Temperature': 18.4, - 'Rssi numeric': 7, 'Humidity': 27, - 'Battery numeric': 9, - 'Humidity status numeric': 2}, - entity.device_state_attributes) - self.assertEqual('0a520801070100b81b0279', - entity.__str__()) + assert 1 == len(rfxtrx_core.RFX_DEVICES) + assert {'Humidity status': 'normal', + 'Temperature': 18.4, + 'Rssi numeric': 7, 'Humidity': 27, + 'Battery numeric': 9, + 'Humidity status numeric': 2} == \ + entity.device_state_attributes + assert '0a520801070100b81b0279' == \ + entity.__str__() rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0a52080405020095240279') event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['sensor_0502']['Temperature'] - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual({'Humidity status': 'normal', - 'Temperature': 14.9, - 'Rssi numeric': 7, 'Humidity': 36, - 'Battery numeric': 9, - 'Humidity status numeric': 2}, - entity.device_state_attributes) - self.assertEqual('0a52080405020095240279', - entity.__str__()) + assert 2 == len(rfxtrx_core.RFX_DEVICES) + assert {'Humidity status': 'normal', + 'Temperature': 14.9, + 'Rssi numeric': 7, 'Humidity': 36, + 'Battery numeric': 9, + 'Humidity status numeric': 2} == \ + entity.device_state_attributes + assert '0a52080405020095240279' == \ + entity.__str__() event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['sensor_0701']['Temperature'] - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual({'Humidity status': 'normal', - 'Temperature': 17.9, - 'Rssi numeric': 7, 'Humidity': 27, - 'Battery numeric': 9, - 'Humidity status numeric': 2}, - entity.device_state_attributes) - self.assertEqual('0a520801070100b81b0279', - entity.__str__()) + assert 2 == len(rfxtrx_core.RFX_DEVICES) + assert {'Humidity status': 'normal', + 'Temperature': 17.9, + 'Rssi numeric': 7, 'Humidity': 27, + 'Battery numeric': 9, + 'Humidity status numeric': 2} == \ + entity.device_state_attributes + assert '0a520801070100b81b0279' == \ + entity.__str__() # trying to add a switch event = rfxtrx_core.get_rfx_object('0b1100cd0213c7f210010f70') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) def test_discover_sensor_noautoadd(self): """Test with discover of sensor when auto add is False.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'automatic_add': False, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0a520801070100b81b0279') event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y') - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0a52080405020095240279') event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) def test_update_of_sensors(self): """Test with 3 sensors.""" - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'devices': {'0a52080705020095220269': { @@ -218,30 +218,30 @@ class TestSensorRfxtrx(unittest.TestCase): '0a520802060100ff0e0269': { 'name': 'Bath', 'data_type': ['Temperature', 'Humidity'] - }}}})) + }}}}) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: if id == 'sensor_0601': device_num = device_num + 1 - self.assertEqual(len(rfxtrx_core.RFX_DEVICES[id]), 2) + assert len(rfxtrx_core.RFX_DEVICES[id]) == 2 _entity_temp = rfxtrx_core.RFX_DEVICES[id]['Temperature'] _entity_hum = rfxtrx_core.RFX_DEVICES[id]['Humidity'] - self.assertEqual('%', _entity_hum.unit_of_measurement) - self.assertEqual('Bath', _entity_hum.__str__()) - self.assertEqual(None, _entity_temp.state) - self.assertEqual(TEMP_CELSIUS, - _entity_temp.unit_of_measurement) - self.assertEqual('Bath', _entity_temp.__str__()) + assert '%' == _entity_hum.unit_of_measurement + assert 'Bath' == _entity_hum.__str__() + assert _entity_temp.state is None + assert TEMP_CELSIUS == \ + _entity_temp.unit_of_measurement + assert 'Bath' == _entity_temp.__str__() elif id == 'sensor_0502': device_num = device_num + 1 entity = rfxtrx_core.RFX_DEVICES[id]['Temperature'] - self.assertEqual(None, entity.state) - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual('Test', entity.__str__()) + assert entity.state is None + assert TEMP_CELSIUS == entity.unit_of_measurement + assert 'Test' == entity.__str__() - self.assertEqual(2, device_num) + assert 2 == device_num event = rfxtrx_core.get_rfx_object('0a520802060101ff0f0269') event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y') @@ -252,45 +252,45 @@ class TestSensorRfxtrx(unittest.TestCase): event.data = bytearray(b'\nR\x08\x04\x05\x02\x00\x95$\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: if id == 'sensor_0601': device_num = device_num + 1 - self.assertEqual(len(rfxtrx_core.RFX_DEVICES[id]), 2) + assert len(rfxtrx_core.RFX_DEVICES[id]) == 2 _entity_temp = rfxtrx_core.RFX_DEVICES[id]['Temperature'] _entity_hum = rfxtrx_core.RFX_DEVICES[id]['Humidity'] - self.assertEqual('%', _entity_hum.unit_of_measurement) - self.assertEqual(15, _entity_hum.state) - self.assertEqual({'Battery numeric': 9, 'Temperature': 51.1, - 'Humidity': 15, 'Humidity status': 'normal', - 'Humidity status numeric': 2, - 'Rssi numeric': 6}, - _entity_hum.device_state_attributes) - self.assertEqual('Bath', _entity_hum.__str__()) + assert '%' == _entity_hum.unit_of_measurement + assert 15 == _entity_hum.state + assert {'Battery numeric': 9, 'Temperature': 51.1, + 'Humidity': 15, 'Humidity status': 'normal', + 'Humidity status numeric': 2, + 'Rssi numeric': 6} == \ + _entity_hum.device_state_attributes + assert 'Bath' == _entity_hum.__str__() - self.assertEqual(TEMP_CELSIUS, - _entity_temp.unit_of_measurement) - self.assertEqual(51.1, _entity_temp.state) - self.assertEqual({'Battery numeric': 9, 'Temperature': 51.1, - 'Humidity': 15, 'Humidity status': 'normal', - 'Humidity status numeric': 2, - 'Rssi numeric': 6}, - _entity_temp.device_state_attributes) - self.assertEqual('Bath', _entity_temp.__str__()) + assert TEMP_CELSIUS == \ + _entity_temp.unit_of_measurement + assert 51.1 == _entity_temp.state + assert {'Battery numeric': 9, 'Temperature': 51.1, + 'Humidity': 15, 'Humidity status': 'normal', + 'Humidity status numeric': 2, + 'Rssi numeric': 6} == \ + _entity_temp.device_state_attributes + assert 'Bath' == _entity_temp.__str__() elif id == 'sensor_0502': device_num = device_num + 1 entity = rfxtrx_core.RFX_DEVICES[id]['Temperature'] - self.assertEqual(TEMP_CELSIUS, entity.unit_of_measurement) - self.assertEqual(13.3, entity.state) - self.assertEqual({'Humidity status': 'normal', - 'Temperature': 13.3, - 'Rssi numeric': 6, 'Humidity': 34, - 'Battery numeric': 9, - 'Humidity status numeric': 2}, - entity.device_state_attributes) - self.assertEqual('Test', entity.__str__()) + assert TEMP_CELSIUS == entity.unit_of_measurement + assert 13.3 == entity.state + assert {'Humidity status': 'normal', + 'Temperature': 13.3, + 'Rssi numeric': 6, 'Humidity': 34, + 'Battery numeric': 9, + 'Humidity status numeric': 2} == \ + entity.device_state_attributes + assert 'Test' == entity.__str__() - self.assertEqual(2, device_num) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == device_num + assert 2 == len(rfxtrx_core.RFX_DEVICES) diff --git a/tests/components/sensor/test_ring.py b/tests/components/sensor/test_ring.py index 05685376ef9..3844079172a 100644 --- a/tests/components/sensor/test_ring.py +++ b/tests/components/sensor/test_ring.py @@ -72,40 +72,40 @@ class TestRingSensorSetup(unittest.TestCase): for device in self.DEVICES: device.update() if device.name == 'Front Battery': - self.assertEqual(80, device.state) - self.assertEqual('hp_cam_v1', - device.device_state_attributes['kind']) - self.assertEqual('stickup_cams', - device.device_state_attributes['type']) + assert 80 == device.state + assert 'hp_cam_v1' == \ + device.device_state_attributes['kind'] + assert 'stickup_cams' == \ + device.device_state_attributes['type'] if device.name == 'Front Door Battery': - self.assertEqual(100, device.state) - self.assertEqual('lpd_v1', - device.device_state_attributes['kind']) - self.assertNotEqual('chimes', - device.device_state_attributes['type']) + assert 100 == device.state + assert 'lpd_v1' == \ + device.device_state_attributes['kind'] + assert 'chimes' != \ + device.device_state_attributes['type'] if device.name == 'Downstairs Volume': - self.assertEqual(2, device.state) - self.assertEqual('1.2.3', - device.device_state_attributes['firmware']) - self.assertEqual('ring_mock_wifi', - device.device_state_attributes['wifi_name']) - self.assertEqual('mdi:bell-ring', device.icon) - self.assertEqual('chimes', - device.device_state_attributes['type']) + assert 2 == device.state + assert '1.2.3' == \ + device.device_state_attributes['firmware'] + assert 'ring_mock_wifi' == \ + device.device_state_attributes['wifi_name'] + assert 'mdi:bell-ring' == device.icon + assert 'chimes' == \ + device.device_state_attributes['type'] if device.name == 'Front Door Last Activity': - self.assertFalse(device.device_state_attributes['answered']) - self.assertEqual('America/New_York', - device.device_state_attributes['timezone']) + assert not device.device_state_attributes['answered'] + assert 'America/New_York' == \ + device.device_state_attributes['timezone'] if device.name == 'Downstairs WiFi Signal Strength': - self.assertEqual(-39, device.state) + assert -39 == device.state if device.name == 'Front Door WiFi Signal Category': - self.assertEqual('good', device.state) + assert 'good' == device.state if device.name == 'Front Door WiFi Signal Strength': - self.assertEqual(-58, device.state) + assert -58 == device.state - self.assertIsNone(device.entity_picture) - self.assertEqual(ATTRIBUTION, - device.device_state_attributes['attribution']) + assert device.entity_picture is None + assert ATTRIBUTION == \ + device.device_state_attributes['attribution'] diff --git a/tests/components/sensor/test_season.py b/tests/components/sensor/test_season.py index 21e18a00c14..20432857aa3 100644 --- a/tests/components/sensor/test_season.py +++ b/tests/components/sensor/test_season.py @@ -79,8 +79,8 @@ class TestSeason(unittest.TestCase): summer_day = datetime(2017, 9, 3, 0, 0) current_season = season.get_season(summer_day, season.NORTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_SUMMER, - current_season) + assert season.STATE_SUMMER == \ + current_season def test_season_should_be_summer_northern_meteorological(self): """Test that season should be summer.""" @@ -88,8 +88,8 @@ class TestSeason(unittest.TestCase): summer_day = datetime(2017, 8, 13, 0, 0) current_season = season.get_season(summer_day, season.NORTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_SUMMER, - current_season) + assert season.STATE_SUMMER == \ + current_season def test_season_should_be_autumn_northern_astronomical(self): """Test that season should be autumn.""" @@ -97,8 +97,8 @@ class TestSeason(unittest.TestCase): autumn_day = datetime(2017, 9, 23, 0, 0) current_season = season.get_season(autumn_day, season.NORTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_AUTUMN, - current_season) + assert season.STATE_AUTUMN == \ + current_season def test_season_should_be_autumn_northern_meteorological(self): """Test that season should be autumn.""" @@ -106,8 +106,8 @@ class TestSeason(unittest.TestCase): autumn_day = datetime(2017, 9, 3, 0, 0) current_season = season.get_season(autumn_day, season.NORTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_AUTUMN, - current_season) + assert season.STATE_AUTUMN == \ + current_season def test_season_should_be_winter_northern_astronomical(self): """Test that season should be winter.""" @@ -115,8 +115,8 @@ class TestSeason(unittest.TestCase): winter_day = datetime(2017, 12, 25, 0, 0) current_season = season.get_season(winter_day, season.NORTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_WINTER, - current_season) + assert season.STATE_WINTER == \ + current_season def test_season_should_be_winter_northern_meteorological(self): """Test that season should be winter.""" @@ -124,8 +124,8 @@ class TestSeason(unittest.TestCase): winter_day = datetime(2017, 12, 3, 0, 0) current_season = season.get_season(winter_day, season.NORTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_WINTER, - current_season) + assert season.STATE_WINTER == \ + current_season def test_season_should_be_spring_northern_astronomical(self): """Test that season should be spring.""" @@ -133,8 +133,8 @@ class TestSeason(unittest.TestCase): spring_day = datetime(2017, 4, 1, 0, 0) current_season = season.get_season(spring_day, season.NORTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_SPRING, - current_season) + assert season.STATE_SPRING == \ + current_season def test_season_should_be_spring_northern_meteorological(self): """Test that season should be spring.""" @@ -142,8 +142,8 @@ class TestSeason(unittest.TestCase): spring_day = datetime(2017, 3, 3, 0, 0) current_season = season.get_season(spring_day, season.NORTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_SPRING, - current_season) + assert season.STATE_SPRING == \ + current_season def test_season_should_be_winter_southern_astronomical(self): """Test that season should be winter.""" @@ -151,8 +151,8 @@ class TestSeason(unittest.TestCase): winter_day = datetime(2017, 9, 3, 0, 0) current_season = season.get_season(winter_day, season.SOUTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_WINTER, - current_season) + assert season.STATE_WINTER == \ + current_season def test_season_should_be_winter_southern_meteorological(self): """Test that season should be winter.""" @@ -160,8 +160,8 @@ class TestSeason(unittest.TestCase): winter_day = datetime(2017, 8, 13, 0, 0) current_season = season.get_season(winter_day, season.SOUTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_WINTER, - current_season) + assert season.STATE_WINTER == \ + current_season def test_season_should_be_spring_southern_astronomical(self): """Test that season should be spring.""" @@ -169,8 +169,8 @@ class TestSeason(unittest.TestCase): spring_day = datetime(2017, 9, 23, 0, 0) current_season = season.get_season(spring_day, season.SOUTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_SPRING, - current_season) + assert season.STATE_SPRING == \ + current_season def test_season_should_be_spring_southern_meteorological(self): """Test that season should be spring.""" @@ -178,8 +178,8 @@ class TestSeason(unittest.TestCase): spring_day = datetime(2017, 9, 3, 0, 0) current_season = season.get_season(spring_day, season.SOUTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_SPRING, - current_season) + assert season.STATE_SPRING == \ + current_season def test_season_should_be_summer_southern_astronomical(self): """Test that season should be summer.""" @@ -187,8 +187,8 @@ class TestSeason(unittest.TestCase): summer_day = datetime(2017, 12, 25, 0, 0) current_season = season.get_season(summer_day, season.SOUTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_SUMMER, - current_season) + assert season.STATE_SUMMER == \ + current_season def test_season_should_be_summer_southern_meteorological(self): """Test that season should be summer.""" @@ -196,8 +196,8 @@ class TestSeason(unittest.TestCase): summer_day = datetime(2017, 12, 3, 0, 0) current_season = season.get_season(summer_day, season.SOUTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_SUMMER, - current_season) + assert season.STATE_SUMMER == \ + current_season def test_season_should_be_autumn_southern_astronomical(self): """Test that season should be spring.""" @@ -205,8 +205,8 @@ class TestSeason(unittest.TestCase): autumn_day = datetime(2017, 4, 1, 0, 0) current_season = season.get_season(autumn_day, season.SOUTHERN, season.TYPE_ASTRONOMICAL) - self.assertEqual(season.STATE_AUTUMN, - current_season) + assert season.STATE_AUTUMN == \ + current_season def test_season_should_be_autumn_southern_meteorological(self): """Test that season should be autumn.""" @@ -214,8 +214,8 @@ class TestSeason(unittest.TestCase): autumn_day = datetime(2017, 3, 3, 0, 0) current_season = season.get_season(autumn_day, season.SOUTHERN, season.TYPE_METEOROLOGICAL) - self.assertEqual(season.STATE_AUTUMN, - current_season) + assert season.STATE_AUTUMN == \ + current_season def test_on_equator_results_in_none(self): """Test that season should be unknown.""" @@ -224,40 +224,40 @@ class TestSeason(unittest.TestCase): current_season = season.get_season(summer_day, season.EQUATOR, season.TYPE_ASTRONOMICAL) - self.assertEqual(None, current_season) + assert current_season is None def test_setup_hemisphere_northern(self): """Test platform setup of northern hemisphere.""" self.hass.config.latitude = HEMISPHERE_NORTHERN[ 'homeassistant']['latitude'] assert setup_component(self.hass, 'sensor', HEMISPHERE_NORTHERN) - self.assertEqual(self.hass.config.as_dict()['latitude'], - HEMISPHERE_NORTHERN['homeassistant']['latitude']) + assert self.hass.config.as_dict()['latitude'] == \ + HEMISPHERE_NORTHERN['homeassistant']['latitude'] state = self.hass.states.get('sensor.season') - self.assertEqual(state.attributes.get('friendly_name'), 'Season') + assert state.attributes.get('friendly_name') == 'Season' def test_setup_hemisphere_southern(self): """Test platform setup of southern hemisphere.""" self.hass.config.latitude = HEMISPHERE_SOUTHERN[ 'homeassistant']['latitude'] assert setup_component(self.hass, 'sensor', HEMISPHERE_SOUTHERN) - self.assertEqual(self.hass.config.as_dict()['latitude'], - HEMISPHERE_SOUTHERN['homeassistant']['latitude']) + assert self.hass.config.as_dict()['latitude'] == \ + HEMISPHERE_SOUTHERN['homeassistant']['latitude'] state = self.hass.states.get('sensor.season') - self.assertEqual(state.attributes.get('friendly_name'), 'Season') + assert state.attributes.get('friendly_name') == 'Season' def test_setup_hemisphere_equator(self): """Test platform setup of equator.""" self.hass.config.latitude = HEMISPHERE_EQUATOR[ 'homeassistant']['latitude'] assert setup_component(self.hass, 'sensor', HEMISPHERE_EQUATOR) - self.assertEqual(self.hass.config.as_dict()['latitude'], - HEMISPHERE_EQUATOR['homeassistant']['latitude']) + assert self.hass.config.as_dict()['latitude'] == \ + HEMISPHERE_EQUATOR['homeassistant']['latitude'] state = self.hass.states.get('sensor.season') - self.assertEqual(state.attributes.get('friendly_name'), 'Season') + assert state.attributes.get('friendly_name') == 'Season' def test_setup_hemisphere_empty(self): """Test platform setup of missing latlong.""" self.hass.config.latitude = None assert setup_component(self.hass, 'sensor', HEMISPHERE_EMPTY) - self.assertEqual(self.hass.config.as_dict()['latitude'], None) + assert self.hass.config.as_dict()['latitude']is None diff --git a/tests/components/sensor/test_sigfox.py b/tests/components/sensor/test_sigfox.py index 569fab584ad..c785d272f55 100644 --- a/tests/components/sensor/test_sigfox.py +++ b/tests/components/sensor/test_sigfox.py @@ -42,8 +42,7 @@ class TestSigfoxSensor(unittest.TestCase): with requests_mock.Mocker() as mock_req: url = re.compile(API_URL + 'devicetypes') mock_req.get(url, text='{}', status_code=401) - self.assertTrue( - setup_component(self.hass, 'sensor', VALID_CONFIG)) + assert setup_component(self.hass, 'sensor', VALID_CONFIG) assert len(self.hass.states.entity_ids()) == 0 def test_valid_credentials(self): @@ -59,8 +58,7 @@ class TestSigfoxSensor(unittest.TestCase): url3 = re.compile(API_URL + 'devices/fake_id/messages*') mock_req.get(url3, text=VALID_MESSAGE) - self.assertTrue( - setup_component(self.hass, 'sensor', VALID_CONFIG)) + assert setup_component(self.hass, 'sensor', VALID_CONFIG) assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('sensor.sigfox_fake_id') diff --git a/tests/components/sensor/test_simulated.py b/tests/components/sensor/test_simulated.py index 50552baa33e..c51e281d123 100644 --- a/tests/components/sensor/test_simulated.py +++ b/tests/components/sensor/test_simulated.py @@ -28,7 +28,7 @@ class TestSimulatedSensor(unittest.TestCase): 'sensor': { 'platform': 'simulated'} } - self.assertTrue(setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 diff --git a/tests/components/sensor/test_sleepiq.py b/tests/components/sensor/test_sleepiq.py index 646f8e5d888..96787473abf 100644 --- a/tests/components/sensor/test_sleepiq.py +++ b/tests/components/sensor/test_sleepiq.py @@ -51,12 +51,12 @@ class TestSleepIQSensorSetup(unittest.TestCase): self.config, self.add_entities, MagicMock()) - self.assertEqual(2, len(self.DEVICES)) + assert 2 == len(self.DEVICES) left_side = self.DEVICES[1] - self.assertEqual('SleepNumber ILE Test1 SleepNumber', left_side.name) - self.assertEqual(40, left_side.state) + assert 'SleepNumber ILE Test1 SleepNumber' == left_side.name + assert 40 == left_side.state right_side = self.DEVICES[0] - self.assertEqual('SleepNumber ILE Test2 SleepNumber', right_side.name) - self.assertEqual(80, right_side.state) + assert 'SleepNumber ILE Test2 SleepNumber' == right_side.name + assert 80 == right_side.state diff --git a/tests/components/sensor/test_sonarr.py b/tests/components/sensor/test_sonarr.py index e44d3d9a99f..e8c7c17d006 100644 --- a/tests/components/sensor/test_sonarr.py +++ b/tests/components/sensor/test_sonarr.py @@ -610,14 +610,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('263.10', device.state) - self.assertEqual('mdi:harddisk', device.icon) - self.assertEqual('GB', device.unit_of_measurement) - self.assertEqual('Sonarr Disk Space', device.name) - self.assertEqual( - '263.10/465.42GB (56.53%)', + assert '263.10' == device.state + assert 'mdi:harddisk' == device.icon + assert 'GB' == device.unit_of_measurement + assert 'Sonarr Disk Space' == device.name + assert '263.10/465.42GB (56.53%)' == \ device.device_state_attributes["/data"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_diskspace_paths(self, req_mock): @@ -637,14 +635,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('263.10', device.state) - self.assertEqual('mdi:harddisk', device.icon) - self.assertEqual('GB', device.unit_of_measurement) - self.assertEqual('Sonarr Disk Space', device.name) - self.assertEqual( - '263.10/465.42GB (56.53%)', + assert '263.10' == device.state + assert 'mdi:harddisk' == device.icon + assert 'GB' == device.unit_of_measurement + assert 'Sonarr Disk Space' == device.name + assert '263.10/465.42GB (56.53%)' == \ device.device_state_attributes["/data"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_commands(self, req_mock): @@ -664,14 +660,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:code-braces', device.icon) - self.assertEqual('Commands', device.unit_of_measurement) - self.assertEqual('Sonarr Commands', device.name) - self.assertEqual( - 'pending', + assert 1 == device.state + assert 'mdi:code-braces' == device.icon + assert 'Commands' == device.unit_of_measurement + assert 'Sonarr Commands' == device.name + assert 'pending' == \ device.device_state_attributes["RescanSeries"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_queue(self, req_mock): @@ -691,14 +685,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:download', device.icon) - self.assertEqual('Episodes', device.unit_of_measurement) - self.assertEqual('Sonarr Queue', device.name) - self.assertEqual( - '100.00%', + assert 1 == device.state + assert 'mdi:download' == device.icon + assert 'Episodes' == device.unit_of_measurement + assert 'Sonarr Queue' == device.name + assert '100.00%' == \ device.device_state_attributes["Game of Thrones S03E08"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_series(self, req_mock): @@ -718,14 +710,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Shows', device.unit_of_measurement) - self.assertEqual('Sonarr Series', device.name) - self.assertEqual( - '26/26 Episodes', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Shows' == device.unit_of_measurement + assert 'Sonarr Series' == device.name + assert '26/26 Episodes' == \ device.device_state_attributes["Marvel's Daredevil"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_wanted(self, req_mock): @@ -745,14 +735,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Episodes', device.unit_of_measurement) - self.assertEqual('Sonarr Wanted', device.name) - self.assertEqual( - '2014-02-03', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Episodes' == device.unit_of_measurement + assert 'Sonarr Wanted' == device.name + assert '2014-02-03' == \ device.device_state_attributes["Archer (2009) S05E04"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_upcoming_multiple_days(self, req_mock): @@ -772,14 +760,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Episodes', device.unit_of_measurement) - self.assertEqual('Sonarr Upcoming', device.name) - self.assertEqual( - 'S04E11', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Episodes' == device.unit_of_measurement + assert 'Sonarr Upcoming' == device.name + assert 'S04E11' == \ device.device_state_attributes["Bob's Burgers"] - ) @pytest.mark.skip @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) @@ -803,14 +789,12 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Episodes', device.unit_of_measurement) - self.assertEqual('Sonarr Upcoming', device.name) - self.assertEqual( - 'S04E11', + assert 1 == device.state + assert 'mdi:television' == device.icon + assert 'Episodes' == device.unit_of_measurement + assert 'Sonarr Upcoming' == device.name + assert 'S04E11' == \ device.device_state_attributes["Bob's Burgers"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) def test_system_status(self, req_mock): @@ -830,12 +814,11 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual('2.0.0.1121', device.state) - self.assertEqual('mdi:information', device.icon) - self.assertEqual('Sonarr Status', device.name) - self.assertEqual( - '6.2.9200.0', - device.device_state_attributes['osVersion']) + assert '2.0.0.1121' == device.state + assert 'mdi:information' == device.icon + assert 'Sonarr Status' == device.name + assert '6.2.9200.0' == \ + device.device_state_attributes['osVersion'] @pytest.mark.skip @unittest.mock.patch('requests.get', side_effect=mocked_requests_get) @@ -857,15 +840,13 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(1, device.state) - self.assertEqual('s', device.ssl) - self.assertEqual('mdi:television', device.icon) - self.assertEqual('Episodes', device.unit_of_measurement) - self.assertEqual('Sonarr Upcoming', device.name) - self.assertEqual( - 'S04E11', + assert 1 == device.state + assert 's' == device.ssl + assert 'mdi:television' == device.icon + assert 'Episodes' == device.unit_of_measurement + assert 'Sonarr Upcoming' == device.name + assert 'S04E11' == \ device.device_state_attributes["Bob's Burgers"] - ) @unittest.mock.patch('requests.get', side_effect=mocked_exception) def test_exception_handling(self, req_mock): @@ -885,4 +866,4 @@ class TestSonarrSetup(unittest.TestCase): sonarr.setup_platform(self.hass, config, self.add_entities, None) for device in self.DEVICES: device.update() - self.assertEqual(None, device.state) + assert device.state is None diff --git a/tests/components/sensor/test_sql.py b/tests/components/sensor/test_sql.py index 7665b5c9037..c966af653d2 100644 --- a/tests/components/sensor/test_sql.py +++ b/tests/components/sensor/test_sql.py @@ -61,4 +61,4 @@ class TestSQLSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) state = self.hass.states.get('sensor.count_tables') - self.assertEqual(state.state, STATE_UNKNOWN) + assert state.state == STATE_UNKNOWN diff --git a/tests/components/sensor/test_statistics.py b/tests/components/sensor/test_statistics.py index e7cfec4d825..0bf9ecd8c6f 100644 --- a/tests/components/sensor/test_statistics.py +++ b/tests/components/sensor/test_statistics.py @@ -54,7 +54,7 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_count') - self.assertEqual(str(len(values)), state.state) + assert str(len(values)) == state.state def test_sensor_source(self): """Test if source is a sensor.""" @@ -73,20 +73,20 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(str(self.mean), state.state) - self.assertEqual(self.min, state.attributes.get('min_value')) - self.assertEqual(self.max, state.attributes.get('max_value')) - self.assertEqual(self.variance, state.attributes.get('variance')) - self.assertEqual(self.median, state.attributes.get('median')) - self.assertEqual(self.deviation, - state.attributes.get('standard_deviation')) - self.assertEqual(self.mean, state.attributes.get('mean')) - self.assertEqual(self.count, state.attributes.get('count')) - self.assertEqual(self.total, state.attributes.get('total')) - self.assertEqual('°C', state.attributes.get('unit_of_measurement')) - self.assertEqual(self.change, state.attributes.get('change')) - self.assertEqual(self.average_change, - state.attributes.get('average_change')) + assert str(self.mean) == state.state + assert self.min == state.attributes.get('min_value') + assert self.max == state.attributes.get('max_value') + assert self.variance == state.attributes.get('variance') + assert self.median == state.attributes.get('median') + assert self.deviation == \ + state.attributes.get('standard_deviation') + assert self.mean == state.attributes.get('mean') + assert self.count == state.attributes.get('count') + assert self.total == state.attributes.get('total') + assert '°C' == state.attributes.get('unit_of_measurement') + assert self.change == state.attributes.get('change') + assert self.average_change == \ + state.attributes.get('average_change') def test_sampling_size(self): """Test rotation.""" @@ -106,8 +106,8 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(3.8, state.attributes.get('min_value')) - self.assertEqual(14, state.attributes.get('max_value')) + assert 3.8 == state.attributes.get('min_value') + assert 14 == state.attributes.get('max_value') def test_sampling_size_1(self): """Test validity of stats requiring only one sample.""" @@ -128,18 +128,18 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') # require only one data point - self.assertEqual(self.values[-1], state.attributes.get('min_value')) - self.assertEqual(self.values[-1], state.attributes.get('max_value')) - self.assertEqual(self.values[-1], state.attributes.get('mean')) - self.assertEqual(self.values[-1], state.attributes.get('median')) - self.assertEqual(self.values[-1], state.attributes.get('total')) - self.assertEqual(0, state.attributes.get('change')) - self.assertEqual(0, state.attributes.get('average_change')) + assert self.values[-1] == state.attributes.get('min_value') + assert self.values[-1] == state.attributes.get('max_value') + assert self.values[-1] == state.attributes.get('mean') + assert self.values[-1] == state.attributes.get('median') + assert self.values[-1] == state.attributes.get('total') + assert 0 == state.attributes.get('change') + assert 0 == state.attributes.get('average_change') # require at least two data points - self.assertEqual(STATE_UNKNOWN, state.attributes.get('variance')) - self.assertEqual(STATE_UNKNOWN, - state.attributes.get('standard_deviation')) + assert STATE_UNKNOWN == state.attributes.get('variance') + assert STATE_UNKNOWN == \ + state.attributes.get('standard_deviation') def test_max_age(self): """Test value deprecation.""" @@ -170,8 +170,8 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(6, state.attributes.get('min_value')) - self.assertEqual(14, state.attributes.get('max_value')) + assert 6 == state.attributes.get('min_value') + assert 14 == state.attributes.get('max_value') def test_change_rate(self): """Test min_age/max_age and change_rate.""" @@ -202,12 +202,12 @@ class TestStatisticsSensor(unittest.TestCase): state = self.hass.states.get('sensor.test_mean') - self.assertEqual(datetime(2017, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC), - state.attributes.get('min_age')) - self.assertEqual(datetime(2017, 8, 2, 12, 23 + self.count - 1, 42, - tzinfo=dt_util.UTC), - state.attributes.get('max_age')) - self.assertEqual(self.change_rate, state.attributes.get('change_rate')) + assert datetime(2017, 8, 2, 12, 23, 42, tzinfo=dt_util.UTC) == \ + state.attributes.get('min_age') + assert datetime(2017, 8, 2, 12, 23 + self.count - 1, 42, + tzinfo=dt_util.UTC) == \ + state.attributes.get('max_age') + assert self.change_rate == state.attributes.get('change_rate') def test_initialize_from_database(self): """Test initializing the statistics from the database.""" @@ -232,4 +232,4 @@ class TestStatisticsSensor(unittest.TestCase): }) # check if the result is as in test_sensor_source() state = self.hass.states.get('sensor.test_mean') - self.assertEqual(str(self.mean), state.state) + assert str(self.mean) == state.state diff --git a/tests/components/sensor/test_transport_nsw.py b/tests/components/sensor/test_transport_nsw.py index fe933272962..c0ad4be4110 100644 --- a/tests/components/sensor/test_transport_nsw.py +++ b/tests/components/sensor/test_transport_nsw.py @@ -43,8 +43,8 @@ class TestRMVtransportSensor(unittest.TestCase): """Test minimal TransportNSW configuration.""" assert setup_component(self.hass, 'sensor', VALID_CONFIG) state = self.hass.states.get('sensor.next_bus') - self.assertEqual(state.state, '16') - self.assertEqual(state.attributes['stop_id'], '209516') - self.assertEqual(state.attributes['route'], '199') - self.assertEqual(state.attributes['delay'], 6) - self.assertEqual(state.attributes['real_time'], 'y') + assert state.state == '16' + assert state.attributes['stop_id'] == '209516' + assert state.attributes['route'] == '199' + assert state.attributes['delay'] == 6 + assert state.attributes['real_time'] == 'y' diff --git a/tests/components/sensor/test_uk_transport.py b/tests/components/sensor/test_uk_transport.py index b051d8e1a1b..65e6b7f0f38 100644 --- a/tests/components/sensor/test_uk_transport.py +++ b/tests/components/sensor/test_uk_transport.py @@ -50,8 +50,8 @@ class TestUkTransportSensor(unittest.TestCase): with requests_mock.Mocker() as mock_req: uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + '*') mock_req.get(uri, text=load_fixture('uk_transport_bus.json')) - self.assertTrue( - setup_component(self.hass, 'sensor', {'sensor': self.config})) + assert setup_component( + self.hass, 'sensor', {'sensor': self.config}) bus_state = self.hass.states.get('sensor.next_bus_to_wantage') @@ -73,8 +73,8 @@ class TestUkTransportSensor(unittest.TestCase): with requests_mock.Mocker() as mock_req: uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + '*') mock_req.get(uri, text=load_fixture('uk_transport_train.json')) - self.assertTrue( - setup_component(self.hass, 'sensor', {'sensor': self.config})) + assert setup_component( + self.hass, 'sensor', {'sensor': self.config}) train_state = self.hass.states.get('sensor.next_train_to_WAT') diff --git a/tests/components/sensor/test_uptime.py b/tests/components/sensor/test_uptime.py index a919e7d20db..00552dd9e49 100644 --- a/tests/components/sensor/test_uptime.py +++ b/tests/components/sensor/test_uptime.py @@ -62,56 +62,56 @@ class TestUptimeSensor(unittest.TestCase): def test_uptime_sensor_days_output(self): """Test uptime sensor output data.""" sensor = UptimeSensor('test', 'days') - self.assertEqual(sensor.unit_of_measurement, 'days') + assert sensor.unit_of_measurement == 'days' new_time = sensor.initial + timedelta(days=1) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 1.00) + assert sensor.state == 1.00 new_time = sensor.initial + timedelta(days=111.499) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 111.50) + assert sensor.state == 111.50 def test_uptime_sensor_hours_output(self): """Test uptime sensor output data.""" sensor = UptimeSensor('test', 'hours') - self.assertEqual(sensor.unit_of_measurement, 'hours') + assert sensor.unit_of_measurement == 'hours' new_time = sensor.initial + timedelta(hours=16) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 16.00) + assert sensor.state == 16.00 new_time = sensor.initial + timedelta(hours=72.499) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 72.50) + assert sensor.state == 72.50 def test_uptime_sensor_minutes_output(self): """Test uptime sensor output data.""" sensor = UptimeSensor('test', 'minutes') - self.assertEqual(sensor.unit_of_measurement, 'minutes') + assert sensor.unit_of_measurement == 'minutes' new_time = sensor.initial + timedelta(minutes=16) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 16.00) + assert sensor.state == 16.00 new_time = sensor.initial + timedelta(minutes=12.499) with patch('homeassistant.util.dt.now', return_value=new_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop ).result() - self.assertEqual(sensor.state, 12.50) + assert sensor.state == 12.50 diff --git a/tests/components/sensor/test_version.py b/tests/components/sensor/test_version.py index 270cfd1709d..e4ddbd15318 100644 --- a/tests/components/sensor/test_version.py +++ b/tests/components/sensor/test_version.py @@ -47,4 +47,4 @@ class TestVersionSensor(unittest.TestCase): state = self.hass.states.get('sensor.test') - self.assertEqual(state.state, '10.0') + assert state.state == '10.0' diff --git a/tests/components/sensor/test_vultr.py b/tests/components/sensor/test_vultr.py index ee2dd35dc8f..294657c22ec 100644 --- a/tests/components/sensor/test_vultr.py +++ b/tests/components/sensor/test_vultr.py @@ -73,9 +73,9 @@ class TestVultrSensorSetup(unittest.TestCase): setup = vultr.setup_platform( self.hass, config, self.add_entities, None) - self.assertIsNone(setup) + assert setup is None - self.assertEqual(5, len(self.DEVICES)) + assert 5 == len(self.DEVICES) tested = 0 @@ -83,47 +83,46 @@ class TestVultrSensorSetup(unittest.TestCase): # Test pre update if device.subscription == '576965': - self.assertEqual(vultr.DEFAULT_NAME, device.name) + assert vultr.DEFAULT_NAME == device.name device.update() if device.unit_of_measurement == 'GB': # Test Bandwidth Used if device.subscription == '576965': - self.assertEqual( - 'Vultr my new server Current Bandwidth Used', - device.name) - self.assertEqual('mdi:chart-histogram', device.icon) - self.assertEqual(131.51, device.state) - self.assertEqual('mdi:chart-histogram', device.icon) + assert 'Vultr my new server Current Bandwidth Used' == \ + device.name + assert 'mdi:chart-histogram' == device.icon + assert 131.51 == device.state + assert 'mdi:chart-histogram' == device.icon tested += 1 elif device.subscription == '123456': - self.assertEqual('Server Current Bandwidth Used', - device.name) - self.assertEqual(957.46, device.state) + assert 'Server Current Bandwidth Used' == \ + device.name + assert 957.46 == device.state tested += 1 elif device.unit_of_measurement == 'US$': # Test Pending Charges if device.subscription == '576965': # Default 'Vultr {} {}' - self.assertEqual('Vultr my new server Pending Charges', - device.name) - self.assertEqual('mdi:currency-usd', device.icon) - self.assertEqual(46.67, device.state) - self.assertEqual('mdi:currency-usd', device.icon) + assert 'Vultr my new server Pending Charges' == \ + device.name + assert 'mdi:currency-usd' == device.icon + assert 46.67 == device.state + assert 'mdi:currency-usd' == device.icon tested += 1 elif device.subscription == '123456': # Custom name with 1 {} - self.assertEqual('Server Pending Charges', device.name) - self.assertEqual('not a number', device.state) + assert 'Server Pending Charges' == device.name + assert 'not a number' == device.state tested += 1 elif device.subscription == '555555': # No {} in name - self.assertEqual('VPS Charges', device.name) - self.assertEqual(5.45, device.state) + assert 'VPS Charges' == device.name + assert 5.45 == device.state tested += 1 - self.assertEqual(tested, 5) + assert tested == 5 def test_invalid_sensor_config(self): """Test config type failures.""" @@ -162,5 +161,5 @@ class TestVultrSensorSetup(unittest.TestCase): no_sub_setup = vultr.setup_platform( self.hass, bad_conf, self.add_entities, None) - self.assertIsNone(no_sub_setup) - self.assertEqual(0, len(self.DEVICES)) + assert no_sub_setup is None + assert 0 == len(self.DEVICES) diff --git a/tests/components/sensor/test_worldclock.py b/tests/components/sensor/test_worldclock.py index 9c5392675fb..fc61d921070 100644 --- a/tests/components/sensor/test_worldclock.py +++ b/tests/components/sensor/test_worldclock.py @@ -21,7 +21,7 @@ class TestWorldClockSensor(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, 'sensor', config)) + assert setup_component(self.hass, 'sensor', config) def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/sensor/test_wsdot.py b/tests/components/sensor/test_wsdot.py index 8eb542b2b68..b90529693b6 100644 --- a/tests/components/sensor/test_wsdot.py +++ b/tests/components/sensor/test_wsdot.py @@ -43,8 +43,7 @@ class TestWSDOT(unittest.TestCase): def test_setup_with_config(self): """Test the platform setup with configuration.""" - self.assertTrue( - setup_component(self.hass, 'sensor', {'wsdot': self.config})) + assert setup_component(self.hass, 'sensor', {'wsdot': self.config}) @requests_mock.Mocker() def test_setup(self, mock_req): @@ -52,12 +51,11 @@ class TestWSDOT(unittest.TestCase): uri = re.compile(RESOURCE + '*') mock_req.get(uri, text=load_fixture('wsdot.json')) wsdot.setup_platform(self.hass, self.config, self.add_entities) - self.assertEqual(len(self.entities), 1) + assert len(self.entities) == 1 sensor = self.entities[0] - self.assertEqual(sensor.name, 'I90 EB') - self.assertEqual(sensor.state, 11) - self.assertEqual(sensor.device_state_attributes[ATTR_DESCRIPTION], - 'Downtown Seattle to Downtown Bellevue via I-90') - self.assertEqual(sensor.device_state_attributes[ATTR_TIME_UPDATED], - datetime(2017, 1, 21, 15, 10, - tzinfo=timezone(timedelta(hours=-8)))) + assert sensor.name == 'I90 EB' + assert sensor.state == 11 + assert sensor.device_state_attributes[ATTR_DESCRIPTION] == \ + 'Downtown Seattle to Downtown Bellevue via I-90' + assert sensor.device_state_attributes[ATTR_TIME_UPDATED] == \ + datetime(2017, 1, 21, 15, 10, tzinfo=timezone(timedelta(hours=-8))) diff --git a/tests/components/sensor/test_yahoo_finance.py b/tests/components/sensor/test_yahoo_finance.py index 7b46ad99d41..d442b9c9b22 100644 --- a/tests/components/sensor/test_yahoo_finance.py +++ b/tests/components/sensor/test_yahoo_finance.py @@ -39,6 +39,6 @@ class TestYahooFinanceSetup(unittest.TestCase): 'sensor': VALID_CONFIG}) state = self.hass.states.get('sensor.yhoo') - self.assertEqual('41.69', state.attributes.get('open')) - self.assertEqual('41.79', state.attributes.get('prev_close')) - self.assertEqual('YHOO', state.attributes.get('unit_of_measurement')) + assert '41.69' == state.attributes.get('open') + assert '41.79' == state.attributes.get('prev_close') + assert 'YHOO' == state.attributes.get('unit_of_measurement') diff --git a/tests/components/sensor/test_yweather.py b/tests/components/sensor/test_yweather.py index 2912229d712..18bf8abeb0b 100644 --- a/tests/components/sensor/test_yweather.py +++ b/tests/components/sensor/test_yweather.py @@ -148,8 +148,8 @@ class TestWeather(unittest.TestCase): assert state is not None assert state.state == 'Mostly Cloudy' - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Condition') + assert state.attributes.get('friendly_name') == \ + 'Yweather Condition' @MockDependency('yahooweather') @patch('yahooweather._yql_query', new=_yql_queryMock) @@ -161,59 +161,59 @@ class TestWeather(unittest.TestCase): state = self.hass.states.get('sensor.yweather_condition') assert state is not None - self.assertEqual(state.state, 'Mostly Cloudy') - self.assertEqual(state.attributes.get('condition_code'), - '28') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Condition') + assert state.state == 'Mostly Cloudy' + assert state.attributes.get('condition_code') == \ + '28' + assert state.attributes.get('friendly_name') == \ + 'Yweather Condition' state = self.hass.states.get('sensor.yweather_current') assert state is not None - self.assertEqual(state.state, 'Cloudy') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Current') + assert state.state == 'Cloudy' + assert state.attributes.get('friendly_name') == \ + 'Yweather Current' state = self.hass.states.get('sensor.yweather_temperature') assert state is not None - self.assertEqual(state.state, '18') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Temperature') + assert state.state == '18' + assert state.attributes.get('friendly_name') == \ + 'Yweather Temperature' state = self.hass.states.get('sensor.yweather_temperature_max') assert state is not None - self.assertEqual(state.state, '23') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Temperature max') + assert state.state == '23' + assert state.attributes.get('friendly_name') == \ + 'Yweather Temperature max' state = self.hass.states.get('sensor.yweather_temperature_min') assert state is not None - self.assertEqual(state.state, '16') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Temperature min') + assert state.state == '16' + assert state.attributes.get('friendly_name') == \ + 'Yweather Temperature min' state = self.hass.states.get('sensor.yweather_wind_speed') assert state is not None - self.assertEqual(state.state, '3.94') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Wind speed') + assert state.state == '3.94' + assert state.attributes.get('friendly_name') == \ + 'Yweather Wind speed' state = self.hass.states.get('sensor.yweather_pressure') assert state is not None - self.assertEqual(state.state, '1000.0') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Pressure') + assert state.state == '1000.0' + assert state.attributes.get('friendly_name') == \ + 'Yweather Pressure' state = self.hass.states.get('sensor.yweather_visibility') assert state is not None - self.assertEqual(state.state, '14.23') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Visibility') + assert state.state == '14.23' + assert state.attributes.get('friendly_name') == \ + 'Yweather Visibility' state = self.hass.states.get('sensor.yweather_humidity') assert state is not None - self.assertEqual(state.state, '71') - self.assertEqual(state.attributes.get('friendly_name'), - 'Yweather Humidity') + assert state.state == '71' + assert state.attributes.get('friendly_name') == \ + 'Yweather Humidity' @MockDependency('yahooweather') @patch('yahooweather._yql_query', new=_yql_queryMock) diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py index a84281b4375..06618e248ce 100644 --- a/tests/components/switch/test_command_line.py +++ b/tests/components/switch/test_command_line.py @@ -33,29 +33,29 @@ class TestCommandSwitch(unittest.TestCase): 'command_on': 'echo 1 > {}'.format(path), 'command_off': 'echo 0 > {}'.format(path), } - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { 'test': test_switch } } - })) + }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_state_value(self): """Test with state value.""" @@ -67,29 +67,29 @@ class TestCommandSwitch(unittest.TestCase): 'command_off': 'echo 0 > {}'.format(path), 'value_template': '{{ value=="1" }}' } - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { 'test': test_switch } } - })) + }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_state_json_value(self): """Test with state JSON value.""" @@ -103,29 +103,29 @@ class TestCommandSwitch(unittest.TestCase): 'command_off': 'echo \'{}\' > {}'.format(offcmd, path), 'value_template': '{{ value_json.status=="ok" }}' } - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { 'test': test_switch } } - })) + }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_state_code(self): """Test with state code.""" @@ -136,29 +136,29 @@ class TestCommandSwitch(unittest.TestCase): 'command_on': 'echo 1 > {}'.format(path), 'command_off': 'echo 0 > {}'.format(path), } - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { 'test': test_switch } } - })) + }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.test') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state def test_assumed_state_should_be_true_if_command_state_is_none(self): """Test with state value.""" @@ -175,13 +175,13 @@ class TestCommandSwitch(unittest.TestCase): ] no_state_device = command_line.CommandSwitch(*init_args) - self.assertTrue(no_state_device.assumed_state) + assert no_state_device.assumed_state # Set state command init_args[-2] = 'cat {}' state_device = command_line.CommandSwitch(*init_args) - self.assertFalse(state_device.assumed_state) + assert not state_device.assumed_state def test_entity_id_set_correctly(self): """Test that entity_id is set correctly from object_id.""" @@ -196,5 +196,5 @@ class TestCommandSwitch(unittest.TestCase): ] test_switch = command_line.CommandSwitch(*init_args) - self.assertEqual(test_switch.entity_id, 'switch.test_device_name') - self.assertEqual(test_switch.name, 'Test friendly name!') + assert test_switch.entity_id == 'switch.test_device_name' + assert test_switch.name == 'Test friendly name!' diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index cb5207adb3e..dbe31e8f47b 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -75,17 +75,16 @@ class TestSwitchFlux(unittest.TestCase): """Test the flux switch when it is off.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=10, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -110,23 +109,22 @@ class TestSwitchFlux(unittest.TestCase): self.hass, light.DOMAIN, SERVICE_TURN_ON) fire_time_changed(self.hass, test_time) self.hass.block_till_done() - self.assertEqual(0, len(turn_on_calls)) + assert 0 == len(turn_on_calls) def test_flux_before_sunrise(self): """Test the flux switch before sunrise.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -154,25 +152,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 112) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.606, 0.379]) + assert call.data[light.ATTR_BRIGHTNESS] == 112 + assert call.data[light.ATTR_XY_COLOR] == [0.606, 0.379] # pylint: disable=invalid-name def test_flux_after_sunrise_before_sunset(self): """Test the flux switch after sunrise and before sunset.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -201,25 +198,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 173) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.439, 0.37]) + assert call.data[light.ATTR_BRIGHTNESS] == 173 + assert call.data[light.ATTR_XY_COLOR] == [0.439, 0.37] # pylint: disable=invalid-name def test_flux_after_sunset_before_stop(self): """Test the flux switch after sunset and before stop.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -249,25 +245,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 146) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.506, 0.385]) + assert call.data[light.ATTR_BRIGHTNESS] == 146 + assert call.data[light.ATTR_XY_COLOR] == [0.506, 0.385] # pylint: disable=invalid-name def test_flux_after_stop_before_sunrise(self): """Test the flux switch after stop and before sunrise.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=23, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -295,25 +290,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 112) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.606, 0.379]) + assert call.data[light.ATTR_BRIGHTNESS] == 112 + assert call.data[light.ATTR_XY_COLOR] == [0.606, 0.379] # pylint: disable=invalid-name def test_flux_with_custom_start_stop_times(self): """Test the flux with custom start and stop times.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -344,8 +338,8 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 147) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.504, 0.385]) + assert call.data[light.ATTR_BRIGHTNESS] == 147 + assert call.data[light.ATTR_XY_COLOR] == [0.504, 0.385] def test_flux_before_sunrise_stop_next_day(self): """Test the flux switch before sunrise. @@ -354,17 +348,16 @@ class TestSwitchFlux(unittest.TestCase): """ platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -394,8 +387,8 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 112) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.606, 0.379]) + assert call.data[light.ATTR_BRIGHTNESS] == 112 + assert call.data[light.ATTR_XY_COLOR] == [0.606, 0.379] # pylint: disable=invalid-name def test_flux_after_sunrise_before_sunset_stop_next_day(self): @@ -406,17 +399,16 @@ class TestSwitchFlux(unittest.TestCase): """ platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -446,8 +438,8 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 173) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.439, 0.37]) + assert call.data[light.ATTR_BRIGHTNESS] == 173 + assert call.data[light.ATTR_XY_COLOR] == [0.439, 0.37] # pylint: disable=invalid-name def test_flux_after_sunset_before_midnight_stop_next_day(self): @@ -457,17 +449,16 @@ class TestSwitchFlux(unittest.TestCase): """ platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=23, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -496,8 +487,8 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 119) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.588, 0.386]) + assert call.data[light.ATTR_BRIGHTNESS] == 119 + assert call.data[light.ATTR_XY_COLOR] == [0.588, 0.386] # pylint: disable=invalid-name def test_flux_after_sunset_after_midnight_stop_next_day(self): @@ -507,17 +498,16 @@ class TestSwitchFlux(unittest.TestCase): """ platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=00, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -547,8 +537,8 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 114) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.601, 0.382]) + assert call.data[light.ATTR_BRIGHTNESS] == 114 + assert call.data[light.ATTR_XY_COLOR] == [0.601, 0.382] # pylint: disable=invalid-name def test_flux_after_stop_before_sunrise_stop_next_day(self): @@ -558,17 +548,16 @@ class TestSwitchFlux(unittest.TestCase): """ platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -598,25 +587,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 112) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.606, 0.379]) + assert call.data[light.ATTR_BRIGHTNESS] == 112 + assert call.data[light.ATTR_XY_COLOR] == [0.606, 0.379] # pylint: disable=invalid-name def test_flux_with_custom_colortemps(self): """Test the flux with custom start and stop colortemps.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -648,25 +636,24 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 159) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.469, 0.378]) + assert call.data[light.ATTR_BRIGHTNESS] == 159 + assert call.data[light.ATTR_XY_COLOR] == [0.469, 0.378] # pylint: disable=invalid-name def test_flux_with_custom_brightness(self): """Test the flux with custom start and stop colortemps.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -697,16 +684,15 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 255) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.506, 0.385]) + assert call.data[light.ATTR_BRIGHTNESS] == 255 + assert call.data[light.ATTR_XY_COLOR] == [0.506, 0.385] def test_flux_with_multiple_lights(self): """Test the flux switch with multiple light entities.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1, dev2, dev3 = platform.DEVICES common_light.turn_on(self.hass, entity_id=dev2.entity_id) @@ -715,19 +701,19 @@ class TestSwitchFlux(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None state = self.hass.states.get(dev2.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None state = self.hass.states.get(dev3.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - self.assertIsNone(state.attributes.get('brightness')) + assert STATE_ON == state.state + assert state.attributes.get('xy_color') is None + assert state.attributes.get('brightness') is None test_time = dt_util.now().replace(hour=12, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -760,29 +746,28 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 163) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.46, 0.376]) + assert call.data[light.ATTR_BRIGHTNESS] == 163 + assert call.data[light.ATTR_XY_COLOR] == [0.46, 0.376] call = turn_on_calls[-2] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 163) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.46, 0.376]) + assert call.data[light.ATTR_BRIGHTNESS] == 163 + assert call.data[light.ATTR_XY_COLOR] == [0.46, 0.376] call = turn_on_calls[-3] - self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 163) - self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.46, 0.376]) + assert call.data[light.ATTR_BRIGHTNESS] == 163 + assert call.data[light.ATTR_XY_COLOR] == [0.46, 0.376] def test_flux_with_mired(self): """Test the flux switch´s mode mired.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('color_temp')) + assert STATE_ON == state.state + assert state.attributes.get('color_temp') is None test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -812,22 +797,21 @@ class TestSwitchFlux(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() call = turn_on_calls[-1] - self.assertEqual(call.data[light.ATTR_COLOR_TEMP], 269) + assert call.data[light.ATTR_COLOR_TEMP] == 269 def test_flux_with_rgb(self): """Test the flux switch´s mode rgb.""" platform = loader.get_component(self.hass, 'light.test') platform.init() - self.assertTrue( - setup_component(self.hass, light.DOMAIN, - {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + assert setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}}) dev1 = platform.DEVICES[0] # Verify initial state of light state = self.hass.states.get(dev1.entity_id) - self.assertEqual(STATE_ON, state.state) - self.assertIsNone(state.attributes.get('color_temp')) + assert STATE_ON == state.state + assert state.attributes.get('color_temp') is None test_time = dt_util.now().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) @@ -859,4 +843,4 @@ class TestSwitchFlux(unittest.TestCase): call = turn_on_calls[-1] rgb = (255, 198, 152) rounded_call = tuple(map(round, call.data[light.ATTR_RGB_COLOR])) - self.assertEqual(rounded_call, rgb) + assert rounded_call == rgb diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index a7462eecd42..1a51457df96 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -31,51 +31,48 @@ class TestSwitch(unittest.TestCase): def test_methods(self): """Test is_on, turn_on, turn_off methods.""" - self.assertTrue(setup_component( + assert setup_component( self.hass, switch.DOMAIN, {switch.DOMAIN: {CONF_PLATFORM: 'test'}} - )) - self.assertTrue(switch.is_on(self.hass)) - self.assertEqual( - STATE_ON, - self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state) - self.assertTrue(switch.is_on(self.hass, self.switch_1.entity_id)) - self.assertFalse(switch.is_on(self.hass, self.switch_2.entity_id)) - self.assertFalse(switch.is_on(self.hass, self.switch_3.entity_id)) + ) + assert switch.is_on(self.hass) + assert STATE_ON == \ + self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state + assert switch.is_on(self.hass, self.switch_1.entity_id) + assert not switch.is_on(self.hass, self.switch_2.entity_id) + assert not switch.is_on(self.hass, self.switch_3.entity_id) common.turn_off(self.hass, self.switch_1.entity_id) common.turn_on(self.hass, self.switch_2.entity_id) self.hass.block_till_done() - self.assertTrue(switch.is_on(self.hass)) - self.assertFalse(switch.is_on(self.hass, self.switch_1.entity_id)) - self.assertTrue(switch.is_on(self.hass, self.switch_2.entity_id)) + assert switch.is_on(self.hass) + assert not switch.is_on(self.hass, self.switch_1.entity_id) + assert switch.is_on(self.hass, self.switch_2.entity_id) # Turn all off common.turn_off(self.hass) self.hass.block_till_done() - self.assertFalse(switch.is_on(self.hass)) - self.assertEqual( - STATE_OFF, - self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state) - self.assertFalse(switch.is_on(self.hass, self.switch_1.entity_id)) - self.assertFalse(switch.is_on(self.hass, self.switch_2.entity_id)) - self.assertFalse(switch.is_on(self.hass, self.switch_3.entity_id)) + assert not switch.is_on(self.hass) + assert STATE_OFF == \ + self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state + assert not switch.is_on(self.hass, self.switch_1.entity_id) + assert not switch.is_on(self.hass, self.switch_2.entity_id) + assert not switch.is_on(self.hass, self.switch_3.entity_id) # Turn all on common.turn_on(self.hass) self.hass.block_till_done() - self.assertTrue(switch.is_on(self.hass)) - self.assertEqual( - STATE_ON, - self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state) - self.assertTrue(switch.is_on(self.hass, self.switch_1.entity_id)) - self.assertTrue(switch.is_on(self.hass, self.switch_2.entity_id)) - self.assertTrue(switch.is_on(self.hass, self.switch_3.entity_id)) + assert switch.is_on(self.hass) + assert STATE_ON == \ + self.hass.states.get(switch.ENTITY_ID_ALL_SWITCHES).state + assert switch.is_on(self.hass, self.switch_1.entity_id) + assert switch.is_on(self.hass, self.switch_2.entity_id) + assert switch.is_on(self.hass, self.switch_3.entity_id) def test_setup_two_platforms(self): """Test with bad configuration.""" @@ -86,12 +83,12 @@ class TestSwitch(unittest.TestCase): loader.set_component(self.hass, 'switch.test2', test_platform) test_platform.init(False) - self.assertTrue(setup_component( + assert setup_component( self.hass, switch.DOMAIN, { switch.DOMAIN: {CONF_PLATFORM: 'test'}, '{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'}, } - )) + ) async def test_switch_context(hass): diff --git a/tests/components/switch/test_mfi.py b/tests/components/switch/test_mfi.py index d2bf3c57ab6..222efee0e46 100644 --- a/tests/components/switch/test_mfi.py +++ b/tests/components/switch/test_mfi.py @@ -60,13 +60,13 @@ class TestMfiSwitch(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual(self.port.label, self.switch.name) + assert self.port.label == self.switch.name def test_update(self): """Test update.""" self.switch.update() - self.assertEqual(self.port.refresh.call_count, 1) - self.assertEqual(self.port.refresh.call_args, mock.call()) + assert self.port.refresh.call_count == 1 + assert self.port.refresh.call_args == mock.call() def test_update_with_target_state(self): """Test update with target state.""" @@ -74,39 +74,39 @@ class TestMfiSwitch(unittest.TestCase): self.port.data = {} self.port.data['output'] = 'stale' self.switch.update() - self.assertEqual(1.0, self.port.data['output']) - self.assertEqual(None, self.switch._target_state) + assert 1.0 == self.port.data['output'] + assert self.switch._target_state is None self.port.data['output'] = 'untouched' self.switch.update() - self.assertEqual('untouched', self.port.data['output']) + assert 'untouched' == self.port.data['output'] def test_turn_on(self): """Test turn_on.""" self.switch.turn_on() - self.assertEqual(self.port.control.call_count, 1) - self.assertEqual(self.port.control.call_args, mock.call(True)) - self.assertTrue(self.switch._target_state) + assert self.port.control.call_count == 1 + assert self.port.control.call_args == mock.call(True) + assert self.switch._target_state def test_turn_off(self): """Test turn_off.""" self.switch.turn_off() - self.assertEqual(self.port.control.call_count, 1) - self.assertEqual(self.port.control.call_args, mock.call(False)) - self.assertFalse(self.switch._target_state) + assert self.port.control.call_count == 1 + assert self.port.control.call_args == mock.call(False) + assert not self.switch._target_state def test_current_power_w(self): """Test current power.""" self.port.data = {'active_pwr': 10} - self.assertEqual(10, self.switch.current_power_w) + assert 10 == self.switch.current_power_w def test_current_power_w_no_data(self): """Test current power if there is no data.""" self.port.data = {'notpower': 123} - self.assertEqual(0, self.switch.current_power_w) + assert 0 == self.switch.current_power_w def test_device_state_attributes(self): """Test the state attributes.""" self.port.data = {'v_rms': 1.25, 'i_rms': 2.75} - self.assertEqual({'volts': 1.2, 'amps': 2.8}, - self.switch.device_state_attributes) + assert {'volts': 1.2, 'amps': 2.8} == \ + self.switch.device_state_attributes diff --git a/tests/components/switch/test_mochad.py b/tests/components/switch/test_mochad.py index bfbd67e6b0c..76640f88723 100644 --- a/tests/components/switch/test_mochad.py +++ b/tests/components/switch/test_mochad.py @@ -51,7 +51,7 @@ class TestMochadSwitchSetup(unittest.TestCase): ], } } - self.assertTrue(setup_component(self.hass, switch.DOMAIN, good_config)) + assert setup_component(self.hass, switch.DOMAIN, good_config) class TestMochadSwitch(unittest.TestCase): @@ -71,7 +71,7 @@ class TestMochadSwitch(unittest.TestCase): def test_name(self): """Test the name.""" - self.assertEqual('fake_switch', self.switch.name) + assert 'fake_switch' == self.switch.name def test_turn_on(self): """Test turn_on.""" diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 3552ec0dc2a..5cdd7d23063 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -42,20 +42,20 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state fire_mqtt_message(self.hass, 'state-topic', '0') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_sending_mqtt_commands_and_optimistic(self): """Test the sending MQTT commands in optimistic mode.""" @@ -75,8 +75,8 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) - self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_ON == state.state + assert state.attributes.get(ATTR_ASSUMED_STATE) common.turn_on(self.hass, 'switch.test') self.hass.block_till_done() @@ -85,7 +85,7 @@ class TestSwitchMQTT(unittest.TestCase): 'command-topic', 'beer on', 2, False) self.mock_publish.async_publish.reset_mock() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.test') self.hass.block_till_done() @@ -93,7 +93,7 @@ class TestSwitchMQTT(unittest.TestCase): self.mock_publish.async_publish.assert_called_once_with( 'command-topic', 'beer off', 2, False) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_controlling_state_via_topic_and_json_message(self): """Test the controlling state via topic and JSON message.""" @@ -110,19 +110,19 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer on"}') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer off"}') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state def test_controlling_availability(self): """Test the controlling state via topic.""" @@ -141,32 +141,32 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'availability_topic', '0') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state def test_default_availability_payload(self): """Test the availability payload.""" @@ -183,32 +183,32 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'online') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'availability_topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'online') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state def test_custom_availability_payload(self): """Test the availability payload.""" @@ -227,32 +227,32 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'good') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'availability_topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'state-topic', '1') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability_topic', 'good') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state def test_custom_state_payload(self): """Test the state payload.""" @@ -270,20 +270,20 @@ class TestSwitchMQTT(unittest.TestCase): }) state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) - self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + assert STATE_OFF == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) fire_mqtt_message(self.hass, 'state-topic', 'HIGH') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state fire_mqtt_message(self.hass, 'state-topic', 'LOW') self.hass.block_till_done() state = self.hass.states.get('switch.test') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state async def test_unique_id(hass): diff --git a/tests/components/switch/test_rfxtrx.py b/tests/components/switch/test_rfxtrx.py index ae242a1dafb..ca59f9c9a29 100644 --- a/tests/components/switch/test_rfxtrx.py +++ b/tests/components/switch/test_rfxtrx.py @@ -28,29 +28,29 @@ class TestSwitchRfxtrx(unittest.TestCase): def test_valid_config(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'0b1100cd0213c7f210010f51': { 'name': 'Test', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_valid_config_int_device_id(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {710000141010170: { 'name': 'Test', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config1(self): """Test invalid configuration.""" - self.assertFalse(setup_component(self.hass, 'switch', { + assert not setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': @@ -58,11 +58,11 @@ class TestSwitchRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', 'signal_repetitions': 3} - }}})) + }}}) def test_invalid_config2(self): """Test invalid configuration.""" - self.assertFalse(setup_component(self.hass, 'switch', { + assert not setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'invalid_key': 'afda', @@ -71,11 +71,11 @@ class TestSwitchRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': '0b1100cd0213c7f210010f51', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config3(self): """Test invalid configuration.""" - self.assertFalse(setup_component(self.hass, 'switch', { + assert not setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': @@ -83,96 +83,96 @@ class TestSwitchRfxtrx(unittest.TestCase): 'name': 'Test', 'packetid': 'AA1100cd0213c7f210010f51', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_invalid_config4(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'switch', { + assert not setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'213c7f216': { 'name': 'Test', rfxtrx_core.ATTR_FIREEVENT: True} - }}})) + }}}) def test_default_config(self): """Test with 0 switches.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'devices': - {}}})) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + {}}}) + assert 0 == len(rfxtrx_core.RFX_DEVICES) def test_old_config(self): """Test with 1 switch.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'devices': {'123efab1': { 'name': 'Test', - 'packetid': '0b1100cd0213c7f210010f51'}}}})) + 'packetid': '0b1100cd0213c7f210010f51'}}}}) import RFXtrx as rfxtrxmod rfxtrx_core.RFXOBJECT =\ rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['213c7f216'] - self.assertEqual('Test', entity.name) - self.assertEqual('off', entity.state) - self.assertTrue(entity.assumed_state) - self.assertEqual(entity.signal_repetitions, 1) - self.assertFalse(entity.should_fire_event) - self.assertFalse(entity.should_poll) + assert 'Test' == entity.name + assert 'off' == entity.state + assert entity.assumed_state + assert entity.signal_repetitions == 1 + assert not entity.should_fire_event + assert not entity.should_poll - self.assertFalse(entity.is_on) + assert not entity.is_on entity.turn_on() - self.assertTrue(entity.is_on) + assert entity.is_on entity.turn_off() - self.assertFalse(entity.is_on) + assert not entity.is_on def test_one_switch(self): """Test with 1 switch.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'devices': {'0b1100cd0213c7f210010f51': { - 'name': 'Test'}}}})) + 'name': 'Test'}}}}) import RFXtrx as rfxtrxmod rfxtrx_core.RFXOBJECT =\ rfxtrxmod.Core("", transport_protocol=rfxtrxmod.DummyTransport) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) entity = rfxtrx_core.RFX_DEVICES['213c7f216'] - self.assertEqual('Test', entity.name) - self.assertEqual('off', entity.state) - self.assertTrue(entity.assumed_state) - self.assertEqual(entity.signal_repetitions, 1) - self.assertFalse(entity.should_fire_event) - self.assertFalse(entity.should_poll) + assert 'Test' == entity.name + assert 'off' == entity.state + assert entity.assumed_state + assert entity.signal_repetitions == 1 + assert not entity.should_fire_event + assert not entity.should_poll - self.assertFalse(entity.is_on) + assert not entity.is_on entity.turn_on() - self.assertTrue(entity.is_on) + assert entity.is_on entity.turn_off() - self.assertFalse(entity.is_on) + assert not entity.is_on entity_id = rfxtrx_core.RFX_DEVICES['213c7f216'].entity_id entity_hass = self.hass.states.get(entity_id) - self.assertEqual('Test', entity_hass.name) - self.assertEqual('off', entity_hass.state) + assert 'Test' == entity_hass.name + assert 'off' == entity_hass.state entity.turn_on() entity_hass = self.hass.states.get(entity_id) - self.assertEqual('on', entity_hass.state) + assert 'on' == entity_hass.state entity.turn_off() entity_hass = self.hass.states.get(entity_id) - self.assertEqual('off', entity_hass.state) + assert 'off' == entity_hass.state def test_several_switches(self): """Test with 3 switches.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'signal_repetitions': 3, 'devices': @@ -181,34 +181,34 @@ class TestSwitchRfxtrx(unittest.TestCase): '0b1100100118cdea02010f70': { 'name': 'Bath'}, '0b1100101118cdea02010f70': { - 'name': 'Living'}}}})) + 'name': 'Living'}}}}) - self.assertEqual(3, len(rfxtrx_core.RFX_DEVICES)) + assert 3 == len(rfxtrx_core.RFX_DEVICES) device_num = 0 for id in rfxtrx_core.RFX_DEVICES: entity = rfxtrx_core.RFX_DEVICES[id] - self.assertEqual(entity.signal_repetitions, 3) + assert entity.signal_repetitions == 3 if entity.name == 'Living': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() elif entity.name == 'Bath': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() elif entity.name == 'Test': device_num = device_num + 1 - self.assertEqual('off', entity.state) - self.assertEqual('', entity.__str__()) + assert 'off' == entity.state + assert '' == entity.__str__() - self.assertEqual(3, device_num) + assert 3 == device_num def test_discover_switch(self): """Test with discovery of switches.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18, @@ -216,12 +216,12 @@ class TestSwitchRfxtrx(unittest.TestCase): rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['118cdea2'] - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual('', - entity.__str__()) + assert 1 == len(rfxtrx_core.RFX_DEVICES) + assert '' == \ + entity.__str__() rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(1, len(rfxtrx_core.RFX_DEVICES)) + assert 1 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0b1100100118cdeb02010f70') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, @@ -229,70 +229,70 @@ class TestSwitchRfxtrx(unittest.TestCase): rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) entity = rfxtrx_core.RFX_DEVICES['118cdeb2'] - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual('', - entity.__str__()) + assert 2 == len(rfxtrx_core.RFX_DEVICES) + assert '' == \ + entity.__str__() # Trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a light event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') event.data = bytearray([0x0b, 0x11, 0x11, 0x10, 0x01, 0x18, 0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a rollershutter event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, 0xAB, 0x02, 0x0E, 0x00, 0x60]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(2, len(rfxtrx_core.RFX_DEVICES)) + assert 2 == len(rfxtrx_core.RFX_DEVICES) def test_discover_switch_noautoadd(self): """Test with discovery of switch when auto add is False.""" - self.assertTrue(setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': False, - 'devices': {}}})) + 'devices': {}}}) event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18, 0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) + assert 0 == len(rfxtrx_core.RFX_DEVICES) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) event = rfxtrx_core.get_rfx_object('0b1100100118cdeb02010f70') event.data = bytearray([0x0b, 0x11, 0x00, 0x12, 0x01, 0x18, 0xcd, 0xea, 0x02, 0x00, 0x00, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a sensor event = rfxtrx_core.get_rfx_object('0a52085e070100b31b0279') event.data = bytearray(b'\nR\x08^\x07\x01\x00\xb3\x1b\x02y') rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a light event = rfxtrx_core.get_rfx_object('0b1100100118cdea02010f70') event.data = bytearray([0x0b, 0x11, 0x11, 0x10, 0x01, 0x18, 0xcd, 0xea, 0x01, 0x02, 0x0f, 0x70]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) # Trying to add a rollershutter event = rfxtrx_core.get_rfx_object('0a1400adf394ab020e0060') event.data = bytearray([0x0A, 0x14, 0x00, 0xAD, 0xF3, 0x94, 0xAB, 0x02, 0x0E, 0x00, 0x60]) rfxtrx_core.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.assertEqual(0, len(rfxtrx_core.RFX_DEVICES)) + assert 0 == len(rfxtrx_core.RFX_DEVICES) diff --git a/tests/components/switch/test_vultr.py b/tests/components/switch/test_vultr.py index ce8740e9bff..699da34319a 100644 --- a/tests/components/switch/test_vultr.py +++ b/tests/components/switch/test_vultr.py @@ -74,59 +74,59 @@ class TestVultrSwitchSetup(unittest.TestCase): self.add_entities, None) - self.assertEqual(len(self.DEVICES), 3) + assert len(self.DEVICES) == 3 tested = 0 for device in self.DEVICES: if device.subscription == '555555': - self.assertEqual('Vultr {}', device.name) + assert 'Vultr {}' == device.name tested += 1 device.update() device_attrs = device.device_state_attributes if device.subscription == '555555': - self.assertEqual('Vultr Another Server', device.name) + assert 'Vultr Another Server' == device.name tested += 1 if device.name == 'A Server': - self.assertEqual(True, device.is_on) - self.assertEqual('on', device.state) - self.assertEqual('mdi:server', device.icon) - self.assertEqual('1000', - device_attrs[ATTR_ALLOWED_BANDWIDTH]) - self.assertEqual('yes', - device_attrs[ATTR_AUTO_BACKUPS]) - self.assertEqual('123.123.123.123', - device_attrs[ATTR_IPV4_ADDRESS]) - self.assertEqual('10.05', - device_attrs[ATTR_COST_PER_MONTH]) - self.assertEqual('2013-12-19 14:45:41', - device_attrs[ATTR_CREATED_AT]) - self.assertEqual('576965', - device_attrs[ATTR_SUBSCRIPTION_ID]) + assert device.is_on is True + assert 'on' == device.state + assert 'mdi:server' == device.icon + assert '1000' == \ + device_attrs[ATTR_ALLOWED_BANDWIDTH] + assert 'yes' == \ + device_attrs[ATTR_AUTO_BACKUPS] + assert '123.123.123.123' == \ + device_attrs[ATTR_IPV4_ADDRESS] + assert '10.05' == \ + device_attrs[ATTR_COST_PER_MONTH] + assert '2013-12-19 14:45:41' == \ + device_attrs[ATTR_CREATED_AT] + assert '576965' == \ + device_attrs[ATTR_SUBSCRIPTION_ID] tested += 1 elif device.name == 'Failed Server': - self.assertEqual(False, device.is_on) - self.assertEqual('off', device.state) - self.assertEqual('mdi:server-off', device.icon) - self.assertEqual('1000', - device_attrs[ATTR_ALLOWED_BANDWIDTH]) - self.assertEqual('no', - device_attrs[ATTR_AUTO_BACKUPS]) - self.assertEqual('192.168.100.50', - device_attrs[ATTR_IPV4_ADDRESS]) - self.assertEqual('73.25', - device_attrs[ATTR_COST_PER_MONTH]) - self.assertEqual('2014-10-13 14:45:41', - device_attrs[ATTR_CREATED_AT]) - self.assertEqual('123456', - device_attrs[ATTR_SUBSCRIPTION_ID]) + assert device.is_on is False + assert 'off' == device.state + assert 'mdi:server-off' == device.icon + assert '1000' == \ + device_attrs[ATTR_ALLOWED_BANDWIDTH] + assert 'no' == \ + device_attrs[ATTR_AUTO_BACKUPS] + assert '192.168.100.50' == \ + device_attrs[ATTR_IPV4_ADDRESS] + assert '73.25' == \ + device_attrs[ATTR_COST_PER_MONTH] + assert '2014-10-13 14:45:41' == \ + device_attrs[ATTR_CREATED_AT] + assert '123456' == \ + device_attrs[ATTR_SUBSCRIPTION_ID] tested += 1 - self.assertEqual(4, tested) + assert 4 == tested @requests_mock.Mocker() def test_turn_on(self, mock): @@ -140,7 +140,7 @@ class TestVultrSwitchSetup(unittest.TestCase): device.turn_on() # Turn on - self.assertEqual(1, mock_start.call_count) + assert 1 == mock_start.call_count @requests_mock.Mocker() def test_turn_off(self, mock): @@ -154,7 +154,7 @@ class TestVultrSwitchSetup(unittest.TestCase): device.turn_off() # Turn off - self.assertEqual(1, mock_halt.call_count) + assert 1 == mock_halt.call_count def test_invalid_switch_config(self): """Test config type failures.""" @@ -184,7 +184,7 @@ class TestVultrSwitchSetup(unittest.TestCase): self.add_entities, None) - self.assertIsNotNone(no_subs_setup) + assert no_subs_setup is not None bad_conf = { CONF_NAME: "Missing Server", @@ -196,4 +196,4 @@ class TestVultrSwitchSetup(unittest.TestCase): self.add_entities, None) - self.assertIsNotNone(wrong_subs_setup) + assert wrong_subs_setup is not None diff --git a/tests/components/switch/test_wake_on_lan.py b/tests/components/switch/test_wake_on_lan.py index 42ebd1ff231..c3f4e04057d 100644 --- a/tests/components/switch/test_wake_on_lan.py +++ b/tests/components/switch/test_wake_on_lan.py @@ -47,16 +47,16 @@ class TestWOLSwitch(unittest.TestCase): """Test with valid hostname.""" global TEST_STATE TEST_STATE = False - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', 'host': 'validhostname', } - })) + }) state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state TEST_STATE = True @@ -64,13 +64,13 @@ class TestWOLSwitch(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state common.turn_off(self.hass, 'switch.wake_on_lan') self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state @patch('wakeonlan.send_magic_packet', new=send_magic_packet) @patch('subprocess.call', new=call) @@ -79,16 +79,16 @@ class TestWOLSwitch(unittest.TestCase): """Test with valid hostname on windows.""" global TEST_STATE TEST_STATE = False - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', 'host': 'validhostname', } - })) + }) state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state TEST_STATE = True @@ -96,33 +96,33 @@ class TestWOLSwitch(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state @patch('wakeonlan.send_magic_packet', new=send_magic_packet) @patch('subprocess.call', new=call) def test_minimal_config(self): """Test with minimal config.""" - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', } - })) + }) @patch('wakeonlan.send_magic_packet', new=send_magic_packet) @patch('subprocess.call', new=call) def test_broadcast_config(self): """Test with broadcast address config.""" - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', 'broadcast_address': '255.255.255.255', } - })) + }) state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state common.turn_on(self.hass, 'switch.wake_on_lan') self.hass.block_till_done() @@ -133,7 +133,7 @@ class TestWOLSwitch(unittest.TestCase): """Test with turn off script.""" global TEST_STATE TEST_STATE = False - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', @@ -142,11 +142,11 @@ class TestWOLSwitch(unittest.TestCase): 'service': 'shell_command.turn_off_TARGET', }, } - })) + }) calls = mock_service(self.hass, 'shell_command', 'turn_off_TARGET') state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state TEST_STATE = True @@ -154,7 +154,7 @@ class TestWOLSwitch(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_ON, state.state) + assert STATE_ON == state.state assert len(calls) == 0 TEST_STATE = False @@ -163,7 +163,7 @@ class TestWOLSwitch(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state assert len(calls) == 1 @patch('wakeonlan.send_magic_packet', new=send_magic_packet) @@ -173,16 +173,16 @@ class TestWOLSwitch(unittest.TestCase): """Test with invalid hostname on windows.""" global TEST_STATE TEST_STATE = False - self.assertTrue(setup_component(self.hass, switch.DOMAIN, { + assert setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'wake_on_lan', 'mac_address': '00-01-02-03-04-05', 'host': 'invalidhostname', } - })) + }) state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state TEST_STATE = True @@ -190,4 +190,4 @@ class TestWOLSwitch(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('switch.wake_on_lan') - self.assertEqual(STATE_OFF, state.state) + assert STATE_OFF == state.state diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 44ece2fc38e..d7aaa3dd038 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -106,22 +106,22 @@ class TestAlert(unittest.TestCase): """Test is_on method.""" self.hass.states.set(ENTITY_ID, STATE_ON) self.hass.block_till_done() - self.assertTrue(alert.is_on(self.hass, ENTITY_ID)) + assert alert.is_on(self.hass, ENTITY_ID) self.hass.states.set(ENTITY_ID, STATE_OFF) self.hass.block_till_done() - self.assertFalse(alert.is_on(self.hass, ENTITY_ID)) + assert not alert.is_on(self.hass, ENTITY_ID) def test_setup(self): """Test setup method.""" assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) - self.assertEqual(STATE_IDLE, self.hass.states.get(ENTITY_ID).state) + assert STATE_IDLE == self.hass.states.get(ENTITY_ID).state def test_fire(self): """Test the alert firing.""" assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state) + assert STATE_ON == self.hass.states.get(ENTITY_ID).state def test_silence(self): """Test silencing the alert.""" @@ -130,15 +130,15 @@ class TestAlert(unittest.TestCase): self.hass.block_till_done() turn_off(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state) + assert STATE_OFF == self.hass.states.get(ENTITY_ID).state # alert should not be silenced on next fire self.hass.states.set("sensor.test", STATE_OFF) self.hass.block_till_done() - self.assertEqual(STATE_IDLE, self.hass.states.get(ENTITY_ID).state) + assert STATE_IDLE == self.hass.states.get(ENTITY_ID).state self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state) + assert STATE_ON == self.hass.states.get(ENTITY_ID).state def test_reset(self): """Test resetting the alert.""" @@ -147,38 +147,38 @@ class TestAlert(unittest.TestCase): self.hass.block_till_done() turn_off(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state) + assert STATE_OFF == self.hass.states.get(ENTITY_ID).state turn_on(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state) + assert STATE_ON == self.hass.states.get(ENTITY_ID).state def test_toggle(self): """Test toggling alert.""" assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state) + assert STATE_ON == self.hass.states.get(ENTITY_ID).state toggle(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state) + assert STATE_OFF == self.hass.states.get(ENTITY_ID).state toggle(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state) + assert STATE_ON == self.hass.states.get(ENTITY_ID).state def test_hidden(self): """Test entity hiding.""" assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden') - self.assertTrue(hidden) + assert hidden self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden') - self.assertFalse(hidden) + assert not hidden turn_off(self.hass, ENTITY_ID) hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden') - self.assertFalse(hidden) + assert not hidden def test_notification_no_done_message(self): """Test notifications.""" @@ -195,15 +195,15 @@ class TestAlert(unittest.TestCase): notify.DOMAIN, NOTIFIER, record_event) assert setup_component(self.hass, alert.DOMAIN, config) - self.assertEqual(0, len(events)) + assert 0 == len(events) self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) self.hass.states.set("sensor.test", STATE_OFF) self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) def test_notification(self): """Test notifications.""" @@ -218,15 +218,15 @@ class TestAlert(unittest.TestCase): notify.DOMAIN, NOTIFIER, record_event) assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) - self.assertEqual(0, len(events)) + assert 0 == len(events) self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) self.hass.states.set("sensor.test", STATE_OFF) self.hass.block_till_done() - self.assertEqual(2, len(events)) + assert 2 == len(events) def test_skipfirst(self): """Test skipping first notification.""" @@ -243,11 +243,11 @@ class TestAlert(unittest.TestCase): notify.DOMAIN, NOTIFIER, record_event) assert setup_component(self.hass, alert.DOMAIN, config) - self.assertEqual(0, len(events)) + assert 0 == len(events) self.hass.states.set("sensor.test", STATE_ON) self.hass.block_till_done() - self.assertEqual(0, len(events)) + assert 0 == len(events) def test_noack(self): """Test no ack feature.""" @@ -255,7 +255,7 @@ class TestAlert(unittest.TestCase): self.hass.add_job(entity.begin_alerting) self.hass.block_till_done() - self.assertEqual(True, entity.hidden) + assert entity.hidden is True def test_done_message_state_tracker_reset_on_cancel(self): """Test that the done message is reset when canceled.""" diff --git a/tests/components/test_canary.py b/tests/components/test_canary.py index 310f3be9f05..463d722919b 100644 --- a/tests/components/test_canary.py +++ b/tests/components/test_canary.py @@ -60,8 +60,7 @@ class TestCanary(unittest.TestCase): } } - self.assertTrue( - setup.setup_component(self.hass, canary.DOMAIN, config)) + assert setup.setup_component(self.hass, canary.DOMAIN, config) mock_update.assert_called_once_with() mock_login.assert_called_once_with() @@ -74,8 +73,7 @@ class TestCanary(unittest.TestCase): } } - self.assertFalse( - setup.setup_component(self.hass, canary.DOMAIN, config)) + assert not setup.setup_component(self.hass, canary.DOMAIN, config) def test_setup_with_missing_username(self): """Test setup component.""" @@ -85,5 +83,4 @@ class TestCanary(unittest.TestCase): } } - self.assertFalse( - setup.setup_component(self.hass, canary.DOMAIN, config)) + assert not setup.setup_component(self.hass, canary.DOMAIN, config) diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index 22f0d6646aa..44ef592bc69 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -26,19 +26,19 @@ class TestConfigurator(unittest.TestCase): request_id = configurator.request_config( self.hass, "Test Request", lambda _: None) - self.assertEqual( - 1, len(self.hass.services.services.get(configurator.DOMAIN, [])), - "No new service registered") + assert 1 == \ + len(self.hass.services.services.get(configurator.DOMAIN, [])), \ + "No new service registered" states = self.hass.states.all() - self.assertEqual(1, len(states), "Expected a new state registered") + assert 1 == len(states), "Expected a new state registered" state = states[0] - self.assertEqual(configurator.STATE_CONFIGURE, state.state) - self.assertEqual( - request_id, state.attributes.get(configurator.ATTR_CONFIGURE_ID)) + assert configurator.STATE_CONFIGURE == state.state + assert \ + request_id == state.attributes.get(configurator.ATTR_CONFIGURE_ID) def test_request_all_info(self): """Test request config with all possible info.""" @@ -67,10 +67,10 @@ class TestConfigurator(unittest.TestCase): } states = self.hass.states.all() - self.assertEqual(1, len(states)) + assert 1 == len(states) state = states[0] - self.assertEqual(configurator.STATE_CONFIGURE, state.state) + assert configurator.STATE_CONFIGURE == state.state assert exp_attr == state.attributes def test_callback_called_on_configure(self): @@ -84,7 +84,7 @@ class TestConfigurator(unittest.TestCase): {configurator.ATTR_CONFIGURE_ID: request_id}) self.hass.block_till_done() - self.assertEqual(1, len(calls), "Callback not called") + assert 1 == len(calls), "Callback not called" def test_state_change_on_notify_errors(self): """Test state change on notify errors.""" @@ -94,7 +94,7 @@ class TestConfigurator(unittest.TestCase): configurator.notify_errors(self.hass, request_id, error) state = self.hass.states.all()[0] - self.assertEqual(error, state.attributes.get(configurator.ATTR_ERRORS)) + assert error == state.attributes.get(configurator.ATTR_ERRORS) def test_notify_errors_fail_silently_on_bad_request_id(self): """Test if notify errors fails silently with a bad request id.""" @@ -105,11 +105,11 @@ class TestConfigurator(unittest.TestCase): request_id = configurator.request_config( self.hass, "Test Request", lambda _: None) configurator.request_done(self.hass, request_id) - self.assertEqual(1, len(self.hass.states.all())) + assert 1 == len(self.hass.states.all()) self.hass.bus.fire(EVENT_TIME_CHANGED) self.hass.block_till_done() - self.assertEqual(0, len(self.hass.states.all())) + assert 0 == len(self.hass.states.all()) def test_request_done_fail_silently_on_bad_request_id(self): """Test that request_done fails silently with a bad request id.""" diff --git a/tests/components/test_datadog.py b/tests/components/test_datadog.py index f9724989f97..eb7bff23b1b 100644 --- a/tests/components/test_datadog.py +++ b/tests/components/test_datadog.py @@ -51,17 +51,15 @@ class TestDatadog(unittest.TestCase): } }) - self.assertEqual(mock_connection.call_count, 1) - self.assertEqual( - mock_connection.call_args, + assert mock_connection.call_count == 1 + assert mock_connection.call_args == \ mock.call(statsd_host='host', statsd_port=123) - ) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_LOGBOOK_ENTRY, - self.hass.bus.listen.call_args_list[0][0][0]) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[1][0][0]) + assert self.hass.bus.listen.called + assert EVENT_LOGBOOK_ENTRY == \ + self.hass.bus.listen.call_args_list[0][0][0] + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[1][0][0] @MockDependency('datadog') def test_datadog_setup_defaults(self, mock_datadog): @@ -77,12 +75,10 @@ class TestDatadog(unittest.TestCase): } }) - self.assertEqual(mock_connection.call_count, 1) - self.assertEqual( - mock_connection.call_args, + assert mock_connection.call_count == 1 + assert mock_connection.call_args == \ mock.call(statsd_host='host', statsd_port=8125) - ) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called @MockDependency('datadog') def test_logbook_entry(self, mock_datadog): @@ -97,7 +93,7 @@ class TestDatadog(unittest.TestCase): } }) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called handler_method = self.hass.bus.listen.call_args_list[0][0][1] event = { @@ -108,9 +104,8 @@ class TestDatadog(unittest.TestCase): } handler_method(mock.MagicMock(data=event)) - self.assertEqual(mock_client.event.call_count, 1) - self.assertEqual( - mock_client.event.call_args, + assert mock_client.event.call_count == 1 + assert mock_client.event.call_args == \ mock.call( title="Home Assistant", text="%%% \n **{}** {} \n %%%".format( @@ -119,7 +114,6 @@ class TestDatadog(unittest.TestCase): ), tags=["entity:sensor.foo.bar", "domain:automation"] ) - ) mock_client.event.reset_mock() @@ -137,7 +131,7 @@ class TestDatadog(unittest.TestCase): } }) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called handler_method = self.hass.bus.listen.call_args_list[1][0][1] valid = { @@ -157,7 +151,7 @@ class TestDatadog(unittest.TestCase): state=in_, attributes=attributes) handler_method(mock.MagicMock(data={'new_state': state})) - self.assertEqual(mock_client.gauge.call_count, 3) + assert mock_client.gauge.call_count == 3 for attribute, value in attributes.items(): mock_client.gauge.assert_has_calls([ @@ -169,16 +163,14 @@ class TestDatadog(unittest.TestCase): ) ]) - self.assertEqual( - mock_client.gauge.call_args, + assert mock_client.gauge.call_args == \ mock.call("ha.sensor", out, sample_rate=1, tags=[ "entity:{}".format(state.entity_id) ]) - ) mock_client.gauge.reset_mock() for invalid in ('foo', '', object): handler_method(mock.MagicMock(data={ 'new_state': ha.State('domain.test', invalid, {})})) - self.assertFalse(mock_client.gauge.called) + assert not mock_client.gauge.called diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index 7107eee74fe..b9f63922ba3 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -49,13 +49,13 @@ class TestDeviceSunLightTrigger(unittest.TestCase): 'track': True, 'vendor': None} }): - self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, { + assert setup_component(self.hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: {CONF_PLATFORM: 'test'} - })) + }) - self.assertTrue(setup_component(self.hass, light.DOMAIN, { + assert setup_component(self.hass, light.DOMAIN, { light.DOMAIN: {CONF_PLATFORM: 'test'} - })) + }) def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -65,9 +65,9 @@ class TestDeviceSunLightTrigger(unittest.TestCase): """Test lights go on when there is someone home and the sun sets.""" test_time = datetime(2017, 4, 5, 1, 2, 3, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.utcnow', return_value=test_time): - self.assertTrue(setup_component( + assert setup_component( self.hass, device_sun_light_trigger.DOMAIN, { - device_sun_light_trigger.DOMAIN: {}})) + device_sun_light_trigger.DOMAIN: {}}) common_light.turn_off(self.hass) @@ -78,7 +78,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase): fire_time_changed(self.hass, test_time) self.hass.block_till_done() - self.assertTrue(light.is_on(self.hass)) + assert light.is_on(self.hass) def test_lights_turn_off_when_everyone_leaves(self): """Test lights turn off when everyone leaves the house.""" @@ -86,16 +86,16 @@ class TestDeviceSunLightTrigger(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(setup_component( + assert setup_component( self.hass, device_sun_light_trigger.DOMAIN, { - device_sun_light_trigger.DOMAIN: {}})) + device_sun_light_trigger.DOMAIN: {}}) self.hass.states.set(device_tracker.ENTITY_ID_ALL_DEVICES, STATE_NOT_HOME) self.hass.block_till_done() - self.assertFalse(light.is_on(self.hass)) + assert not light.is_on(self.hass) def test_lights_turn_on_when_coming_home_after_sun_set(self): """Test lights turn on when coming home after sun set.""" @@ -104,12 +104,12 @@ class TestDeviceSunLightTrigger(unittest.TestCase): common_light.turn_off(self.hass) self.hass.block_till_done() - self.assertTrue(setup_component( + assert setup_component( self.hass, device_sun_light_trigger.DOMAIN, { - device_sun_light_trigger.DOMAIN: {}})) + device_sun_light_trigger.DOMAIN: {}}) self.hass.states.set( device_tracker.ENTITY_ID_FORMAT.format('device_2'), STATE_HOME) self.hass.block_till_done() - self.assertTrue(light.is_on(self.hass)) + assert light.is_on(self.hass) diff --git a/tests/components/test_dialogflow.py b/tests/components/test_dialogflow.py index 0acf0833543..e05e33b30d6 100644 --- a/tests/components/test_dialogflow.py +++ b/tests/components/test_dialogflow.py @@ -169,8 +169,8 @@ class TestDialogflow(unittest.TestCase): } req = _intent_req(data) - self.assertEqual(200, req.status_code) - self.assertEqual("", req.text) + assert 200 == req.status_code + assert "" == req.text def test_intent_slot_filling(self): """Test when Dialogflow asks for slot-filling return none.""" @@ -238,8 +238,8 @@ class TestDialogflow(unittest.TestCase): } req = _intent_req(data) - self.assertEqual(200, req.status_code) - self.assertEqual("", req.text) + assert 200 == req.status_code + assert "" == req.text def test_intent_request_with_parameters(self): """Test a request with parameters.""" @@ -281,9 +281,9 @@ class TestDialogflow(unittest.TestCase): "originalRequest": None } req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual("You told us your sign is virgo.", text) + assert "You told us your sign is virgo." == text def test_intent_request_with_parameters_but_empty(self): """Test a request with parameters but empty value.""" @@ -325,9 +325,9 @@ class TestDialogflow(unittest.TestCase): "originalRequest": None } req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual("You told us your sign is .", text) + assert "You told us your sign is ." == text def test_intent_request_without_slots(self): """Test a request without slots.""" @@ -367,19 +367,19 @@ class TestDialogflow(unittest.TestCase): "originalRequest": None } req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual("Anne Therese is at unknown and Paulus is at unknown", - text) + assert "Anne Therese is at unknown and Paulus is at unknown" == \ + text hass.states.set("device_tracker.paulus", "home") hass.states.set("device_tracker.anne_therese", "home") req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual("You are both home, you silly", text) + assert "You are both home, you silly" == text def test_intent_request_calling_service(self): """Test a request for calling a service. @@ -426,13 +426,13 @@ class TestDialogflow(unittest.TestCase): } call_count = len(calls) req = _intent_req(data) - self.assertEqual(200, req.status_code) - self.assertEqual(call_count + 1, len(calls)) + assert 200 == req.status_code + assert call_count + 1 == len(calls) call = calls[-1] - self.assertEqual("test", call.domain) - self.assertEqual("dialogflow", call.service) - self.assertEqual(["switch.test"], call.data.get("entity_id")) - self.assertEqual("virgo", call.data.get("hello")) + assert "test" == call.domain + assert "dialogflow" == call.service + assert ["switch.test"] == call.data.get("entity_id") + assert "virgo" == call.data.get("hello") def test_intent_with_no_action(self): """Test an intent with no defined action.""" @@ -474,10 +474,10 @@ class TestDialogflow(unittest.TestCase): "originalRequest": None } req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual( - "You have not defined an action in your Dialogflow intent.", text) + assert \ + "You have not defined an action in your Dialogflow intent." == text def test_intent_with_unknown_action(self): """Test an intent with an action not defined in the conf.""" @@ -519,7 +519,7 @@ class TestDialogflow(unittest.TestCase): "originalRequest": None } req = _intent_req(data) - self.assertEqual(200, req.status_code) + assert 200 == req.status_code text = req.json().get("speech") - self.assertEqual( - "This intent is not yet configured within Home Assistant.", text) + assert \ + "This intent is not yet configured within Home Assistant." == text diff --git a/tests/components/test_dyson.py b/tests/components/test_dyson.py index 0352551aec9..2e7b05b06cd 100644 --- a/tests/components/test_dyson.py +++ b/tests/components/test_dyson.py @@ -51,7 +51,7 @@ class DysonTest(unittest.TestCase): dyson.CONF_PASSWORD: "password", dyson.CONF_LANGUAGE: "FR" }}) - self.assertEqual(mocked_login.call_count, 1) + assert mocked_login.call_count == 1 @mock.patch('libpurecoollink.dyson.DysonAccount.devices', return_value=[]) @mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True) @@ -62,9 +62,9 @@ class DysonTest(unittest.TestCase): dyson.CONF_PASSWORD: "password", dyson.CONF_LANGUAGE: "FR" }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0 @mock.patch('homeassistant.helpers.discovery.load_platform') @mock.patch('libpurecoollink.dyson.DysonAccount.devices', @@ -84,10 +84,10 @@ class DysonTest(unittest.TestCase): } ] }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 1) - self.assertEqual(mocked_discovery.call_count, 4) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1 + assert mocked_discovery.call_count == 4 @mock.patch('libpurecoollink.dyson.DysonAccount.devices', return_value=[_get_dyson_account_device_not_available()]) @@ -106,9 +106,9 @@ class DysonTest(unittest.TestCase): } ] }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0 @mock.patch('libpurecoollink.dyson.DysonAccount.devices', return_value=[_get_dyson_account_device_error()]) @@ -127,9 +127,9 @@ class DysonTest(unittest.TestCase): } ] }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0 @mock.patch('homeassistant.helpers.discovery.load_platform') @mock.patch('libpurecoollink.dyson.DysonAccount.devices', @@ -150,10 +150,10 @@ class DysonTest(unittest.TestCase): } ] }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0) - self.assertEqual(mocked_discovery.call_count, 0) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0 + assert mocked_discovery.call_count == 0 @mock.patch('homeassistant.helpers.discovery.load_platform') @mock.patch('libpurecoollink.dyson.DysonAccount.devices', @@ -169,10 +169,10 @@ class DysonTest(unittest.TestCase): dyson.CONF_TIMEOUT: 5, dyson.CONF_RETRY: 2 }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 1) - self.assertEqual(mocked_discovery.call_count, 4) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 1 + assert mocked_discovery.call_count == 4 @mock.patch('libpurecoollink.dyson.DysonAccount.devices', return_value=[_get_dyson_account_device_not_available()]) @@ -187,6 +187,6 @@ class DysonTest(unittest.TestCase): dyson.CONF_TIMEOUT: 5, dyson.CONF_RETRY: 2 }}) - self.assertEqual(mocked_login.call_count, 1) - self.assertEqual(mocked_devices.call_count, 1) - self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 0) + assert mocked_login.call_count == 1 + assert mocked_devices.call_count == 1 + assert len(self.hass.data[dyson.DYSON_DEVICES]) == 0 diff --git a/tests/components/test_feedreader.py b/tests/components/test_feedreader.py index 668f116362c..b84f6dd7df1 100644 --- a/tests/components/test_feedreader.py +++ b/tests/components/test_feedreader.py @@ -59,8 +59,8 @@ class TestFeedreaderComponent(unittest.TestCase): """Test the general setup of this component.""" with patch("homeassistant.components.feedreader." "track_time_interval") as track_method: - self.assertTrue(setup_component(self.hass, feedreader.DOMAIN, - VALID_CONFIG_1)) + assert setup_component( + self.hass, feedreader.DOMAIN, VALID_CONFIG_1) track_method.assert_called_once_with(self.hass, mock.ANY, DEFAULT_SCAN_INTERVAL) @@ -68,15 +68,14 @@ class TestFeedreaderComponent(unittest.TestCase): """Test the setup of this component with scan interval.""" with patch("homeassistant.components.feedreader." "track_time_interval") as track_method: - self.assertTrue(setup_component(self.hass, feedreader.DOMAIN, - VALID_CONFIG_2)) + assert setup_component( + self.hass, feedreader.DOMAIN, VALID_CONFIG_2) track_method.assert_called_once_with(self.hass, mock.ANY, timedelta(seconds=60)) def test_setup_max_entries(self): """Test the setup of this component with max entries.""" - self.assertTrue(setup_component(self.hass, feedreader.DOMAIN, - VALID_CONFIG_3)) + assert setup_component(self.hass, feedreader.DOMAIN, VALID_CONFIG_3) def setup_manager(self, feed_data, max_entries=DEFAULT_MAX_ENTRIES): """Set up feed manager.""" diff --git a/tests/components/test_google.py b/tests/components/test_google.py index b8dc29b5dea..36c4b572bab 100644 --- a/tests/components/test_google.py +++ b/tests/components/test_google.py @@ -31,7 +31,7 @@ class TestGoogle(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, 'google', config)) + assert setup_component(self.hass, 'google', config) def test_get_calendar_info(self): """Test getting the calendar info.""" @@ -52,7 +52,7 @@ class TestGoogle(unittest.TestCase): } calendar_info = google.get_calendar_info(self.hass, calendar) - self.assertEqual(calendar_info, { + assert calendar_info == { 'cal_id': 'qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com', 'entities': [{ 'device_id': 'we_are_we_are_a_test_calendar', @@ -60,7 +60,7 @@ class TestGoogle(unittest.TestCase): 'track': True, 'ignore_availability': True, }] - }) + } def test_found_calendar(self): """Test when a calendar is found.""" @@ -85,8 +85,7 @@ class TestGoogle(unittest.TestCase): calendar_service = google.GoogleCalendarService( self.hass.config.path(google.TOKEN_FILE)) - self.assertTrue(google.setup_services(self.hass, True, - calendar_service)) + assert google.setup_services(self.hass, True, calendar_service) # self.hass.services.call('google', 'found_calendar', calendar, # blocking=True) diff --git a/tests/components/test_graphite.py b/tests/components/test_graphite.py index 7ceda9e191e..1b96de08985 100644 --- a/tests/components/test_graphite.py +++ b/tests/components/test_graphite.py @@ -29,11 +29,9 @@ class TestGraphite(unittest.TestCase): def test_setup(self, mock_socket): """Test setup.""" assert setup_component(self.hass, graphite.DOMAIN, {'graphite': {}}) - self.assertEqual(mock_socket.call_count, 1) - self.assertEqual( - mock_socket.call_args, + assert mock_socket.call_count == 1 + assert mock_socket.call_args == \ mock.call(socket.AF_INET, socket.SOCK_STREAM) - ) @patch('socket.socket') @patch('homeassistant.components.graphite.GraphiteFeeder') @@ -47,16 +45,12 @@ class TestGraphite(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, graphite.DOMAIN, config)) - self.assertEqual(mock_gf.call_count, 1) - self.assertEqual( - mock_gf.call_args, mock.call(self.hass, 'foo', 123, 'me') - ) - self.assertEqual(mock_socket.call_count, 1) - self.assertEqual( - mock_socket.call_args, + assert setup_component(self.hass, graphite.DOMAIN, config) + assert mock_gf.call_count == 1 + assert mock_gf.call_args == mock.call(self.hass, 'foo', 123, 'me') + assert mock_socket.call_count == 1 + assert mock_socket.call_args == \ mock.call(socket.AF_INET, socket.SOCK_STREAM) - ) @patch('socket.socket') @patch('homeassistant.components.graphite.GraphiteFeeder') @@ -69,13 +63,11 @@ class TestGraphite(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, graphite.DOMAIN, config)) - self.assertTrue(mock_gf.called) - self.assertEqual(mock_socket.call_count, 1) - self.assertEqual( - mock_socket.call_args, + assert setup_component(self.hass, graphite.DOMAIN, config) + assert mock_gf.called + assert mock_socket.call_count == 1 + assert mock_socket.call_args == \ mock.call(socket.AF_INET, socket.SOCK_STREAM) - ) def test_subscribe(self): """Test the subscription.""" @@ -85,34 +77,30 @@ class TestGraphite(unittest.TestCase): mock.call(EVENT_HOMEASSISTANT_START, gf.start_listen), mock.call(EVENT_HOMEASSISTANT_STOP, gf.shutdown), ]) - self.assertEqual(fake_hass.bus.listen.call_count, 1) - self.assertEqual( - fake_hass.bus.listen.call_args, + assert fake_hass.bus.listen.call_count == 1 + assert fake_hass.bus.listen.call_args == \ mock.call(EVENT_STATE_CHANGED, gf.event_listener) - ) def test_start(self): """Test the start.""" with mock.patch.object(self.gf, 'start') as mock_start: self.gf.start_listen('event') - self.assertEqual(mock_start.call_count, 1) - self.assertEqual(mock_start.call_args, mock.call()) + assert mock_start.call_count == 1 + assert mock_start.call_args == mock.call() def test_shutdown(self): """Test the shutdown.""" with mock.patch.object(self.gf, '_queue') as mock_queue: self.gf.shutdown('event') - self.assertEqual(mock_queue.put.call_count, 1) - self.assertEqual( - mock_queue.put.call_args, mock.call(self.gf._quit_object) - ) + assert mock_queue.put.call_count == 1 + assert mock_queue.put.call_args == mock.call(self.gf._quit_object) def test_event_listener(self): """Test the event listener.""" with mock.patch.object(self.gf, '_queue') as mock_queue: self.gf.event_listener('foo') - self.assertEqual(mock_queue.put.call_count, 1) - self.assertEqual(mock_queue.put.call_args, mock.call('foo')) + assert mock_queue.put.call_count == 1 + assert mock_queue.put.call_args == mock.call('foo') @patch('time.time') def test_report_attributes(self, mock_time): @@ -135,7 +123,7 @@ class TestGraphite(unittest.TestCase): with mock.patch.object(self.gf, '_send_to_graphite') as mock_send: self.gf._report_attributes('entity', state) actual = mock_send.call_args_list[0][0][0].split('\n') - self.assertEqual(sorted(expected), sorted(actual)) + assert sorted(expected) == sorted(actual) @patch('time.time') def test_report_with_string_state(self, mock_time): @@ -150,7 +138,7 @@ class TestGraphite(unittest.TestCase): with mock.patch.object(self.gf, '_send_to_graphite') as mock_send: self.gf._report_attributes('entity', state) actual = mock_send.call_args_list[0][0][0].split('\n') - self.assertEqual(sorted(expected), sorted(actual)) + assert sorted(expected) == sorted(actual) @patch('time.time') def test_report_with_binary_state(self, mock_time): @@ -162,7 +150,7 @@ class TestGraphite(unittest.TestCase): expected = ['ha.entity.foo 1.000000 12345', 'ha.entity.state 1.000000 12345'] actual = mock_send.call_args_list[0][0][0].split('\n') - self.assertEqual(sorted(expected), sorted(actual)) + assert sorted(expected) == sorted(actual) state.state = STATE_OFF with mock.patch.object(self.gf, '_send_to_graphite') as mock_send: @@ -170,7 +158,7 @@ class TestGraphite(unittest.TestCase): expected = ['ha.entity.foo 1.000000 12345', 'ha.entity.state 0.000000 12345'] actual = mock_send.call_args_list[0][0][0].split('\n') - self.assertEqual(sorted(expected), sorted(actual)) + assert sorted(expected) == sorted(actual) @patch('time.time') def test_send_to_graphite_errors(self, mock_time): @@ -187,32 +175,28 @@ class TestGraphite(unittest.TestCase): def test_send_to_graphite(self, mock_socket): """Test the sending of data.""" self.gf._send_to_graphite('foo') - self.assertEqual(mock_socket.call_count, 1) - self.assertEqual( - mock_socket.call_args, + assert mock_socket.call_count == 1 + assert mock_socket.call_args == \ mock.call(socket.AF_INET, socket.SOCK_STREAM) - ) sock = mock_socket.return_value - self.assertEqual(sock.connect.call_count, 1) - self.assertEqual(sock.connect.call_args, mock.call(('foo', 123))) - self.assertEqual(sock.sendall.call_count, 1) - self.assertEqual( - sock.sendall.call_args, mock.call('foo'.encode('ascii')) - ) - self.assertEqual(sock.send.call_count, 1) - self.assertEqual(sock.send.call_args, mock.call('\n'.encode('ascii'))) - self.assertEqual(sock.close.call_count, 1) - self.assertEqual(sock.close.call_args, mock.call()) + assert sock.connect.call_count == 1 + assert sock.connect.call_args == mock.call(('foo', 123)) + assert sock.sendall.call_count == 1 + assert sock.sendall.call_args == mock.call('foo'.encode('ascii')) + assert sock.send.call_count == 1 + assert sock.send.call_args == mock.call('\n'.encode('ascii')) + assert sock.close.call_count == 1 + assert sock.close.call_args == mock.call() def test_run_stops(self): """Test the stops.""" with mock.patch.object(self.gf, '_queue') as mock_queue: mock_queue.get.return_value = self.gf._quit_object - self.assertEqual(None, self.gf.run()) - self.assertEqual(mock_queue.get.call_count, 1) - self.assertEqual(mock_queue.get.call_args, mock.call()) - self.assertEqual(mock_queue.task_done.call_count, 1) - self.assertEqual(mock_queue.task_done.call_args, mock.call()) + assert self.gf.run() is None + assert mock_queue.get.call_count == 1 + assert mock_queue.get.call_args == mock.call() + assert mock_queue.task_done.call_count == 1 + assert mock_queue.task_done.call_args == mock.call() def test_run(self): """Test the running.""" @@ -236,9 +220,7 @@ class TestGraphite(unittest.TestCase): mock_queue.get.side_effect = fake_get self.gf.run() # Twice for two events, once for the stop - self.assertEqual(3, mock_queue.task_done.call_count) - self.assertEqual(mock_r.call_count, 1) - self.assertEqual( - mock_r.call_args, + assert 3 == mock_queue.task_done.call_count + assert mock_r.call_count == 1 + assert mock_r.call_args == \ mock.call('entity', event.data['new_state']) - ) diff --git a/tests/components/test_history.py b/tests/components/test_history.py index ef2f7a17a19..9764af1592c 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -47,7 +47,7 @@ class TestComponentHistory(unittest.TestCase): history.CONF_DOMAINS: ['thermostat'], history.CONF_ENTITIES: ['media_player.test']}}}) self.init_recorder() - self.assertTrue(setup_component(self.hass, history.DOMAIN, config)) + assert setup_component(self.hass, history.DOMAIN, config) def test_get_states(self): """Test getting states at a specific point in time.""" @@ -89,9 +89,8 @@ class TestComponentHistory(unittest.TestCase): assert state1 == state2 # Test get_state here because we have a DB setup - self.assertEqual( - states[0], history.get_state(self.hass, future, - states[0].entity_id)) + assert states[0] == \ + history.get_state(self.hass, future, states[0].entity_id) def test_state_changes_during_period(self): """Test state change during period.""" @@ -130,7 +129,7 @@ class TestComponentHistory(unittest.TestCase): hist = history.state_changes_during_period( self.hass, start, end, entity_id) - self.assertEqual(states, hist[entity_id]) + assert states == hist[entity_id] def test_get_last_state_changes(self): """Test number of state changes.""" @@ -163,7 +162,7 @@ class TestComponentHistory(unittest.TestCase): hist = history.get_last_state_changes( self.hass, 2, entity_id) - self.assertEqual(states, hist[entity_id]) + assert states == hist[entity_id] def test_get_significant_states(self): """Test that only significant states are returned. diff --git a/tests/components/test_history_graph.py b/tests/components/test_history_graph.py index 9b7733a7ec2..80d590939af 100644 --- a/tests/components/test_history_graph.py +++ b/tests/components/test_history_graph.py @@ -30,15 +30,15 @@ class TestGraph(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, 'history_graph', config)) - self.assertEqual( - dict(self.hass.states.get('history_graph.name_1').attributes), - { - 'entity_id': ['test.test'], - 'friendly_name': 'name_1', - 'hours_to_show': 24, - 'refresh': 0 - }) + assert setup_component(self.hass, 'history_graph', config) + assert dict( + self.hass.states.get('history_graph.name_1').attributes + ) == { + 'entity_id': ['test.test'], + 'friendly_name': 'name_1', + 'hours_to_show': 24, + 'refresh': 0 + } def init_recorder(self): """Initialize the recorder.""" diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index 7d1b7527612..5de6e164750 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -8,7 +8,7 @@ import influxdb as influx_client from homeassistant.setup import setup_component import homeassistant.components.influxdb as influxdb from homeassistant.const import EVENT_STATE_CHANGED, STATE_OFF, STATE_ON, \ - STATE_STANDBY + STATE_STANDBY from tests.common import get_test_home_assistant @@ -45,10 +45,10 @@ class TestInfluxDB(unittest.TestCase): } } assert setup_component(self.hass, influxdb.DOMAIN, config) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual( - EVENT_STATE_CHANGED, self.hass.bus.listen.call_args_list[0][0][0]) - self.assertTrue(mock_client.return_value.query.called) + assert self.hass.bus.listen.called + assert \ + EVENT_STATE_CHANGED == self.hass.bus.listen.call_args_list[0][0][0] + assert mock_client.return_value.query.called def test_setup_config_defaults(self, mock_client): """Test the setup with default configuration.""" @@ -60,9 +60,9 @@ class TestInfluxDB(unittest.TestCase): } } assert setup_component(self.hass, influxdb.DOMAIN, config) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual( - EVENT_STATE_CHANGED, self.hass.bus.listen.call_args_list[0][0][0]) + assert self.hass.bus.listen.called + assert \ + EVENT_STATE_CHANGED == self.hass.bus.listen.call_args_list[0][0][0] def test_setup_minimal_config(self, mock_client): """Test the setup with minimal configuration.""" @@ -169,13 +169,9 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_no_units(self, mock_client): @@ -204,13 +200,9 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_inf(self, mock_client): @@ -235,13 +227,9 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_states(self, mock_client): @@ -267,15 +255,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if state_state == 1: - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_blacklist(self, mock_client): @@ -301,15 +285,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if entity_id == 'ok': - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_blacklist_domain(self, mock_client): @@ -336,15 +316,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if domain == 'ok': - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_whitelist(self, mock_client): @@ -381,15 +357,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if entity_id == 'included': - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_whitelist_domain(self, mock_client): @@ -427,15 +399,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if domain == 'fake': - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_invalid_type(self, mock_client): @@ -482,13 +450,9 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_default_measurement(self, mock_client): @@ -526,15 +490,11 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() if entity_id == 'ok': - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) else: - self.assertFalse(mock_client.return_value.write_points.called) + assert not mock_client.return_value.write_points.called mock_client.return_value.write_points.reset_mock() def test_event_listener_unit_of_measurement_field(self, mock_client): @@ -571,13 +531,9 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_tags_attributes(self, mock_client): @@ -617,13 +573,9 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_event_listener_component_override_measurement(self, mock_client): @@ -678,13 +630,9 @@ class TestInfluxDB(unittest.TestCase): }] self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 1 - ) - self.assertEqual( - mock_client.return_value.write_points.call_args, + assert mock_client.return_value.write_points.call_count == 1 + assert mock_client.return_value.write_points.call_args == \ mock.call(body) - ) mock_client.return_value.write_points.reset_mock() def test_scheduled_write(self, mock_client): @@ -713,7 +661,7 @@ class TestInfluxDB(unittest.TestCase): self.hass.data[influxdb.DOMAIN].block_till_done() assert mock_sleep.called json_data = mock_client.return_value.write_points.call_args[0][0] - self.assertEqual(mock_client.return_value.write_points.call_count, 2) + assert mock_client.return_value.write_points.call_count == 2 mock_client.return_value.write_points.assert_called_with(json_data) # Write works again @@ -722,7 +670,7 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() assert not mock_sleep.called - self.assertEqual(mock_client.return_value.write_points.call_count, 3) + assert mock_client.return_value.write_points.call_count == 3 def test_queue_backlog_full(self, mock_client): """Test the event listener to drop old events.""" @@ -746,8 +694,6 @@ class TestInfluxDB(unittest.TestCase): self.handler_method(event) self.hass.data[influxdb.DOMAIN].block_till_done() - self.assertEqual( - mock_client.return_value.write_points.call_count, 0 - ) + assert mock_client.return_value.write_points.call_count == 0 mock_client.return_value.write_points.reset_mock() diff --git a/tests/components/test_init.py b/tests/components/test_init.py index b9152bbdd6a..768951d46b3 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -96,9 +96,9 @@ class TestComponentsCore(unittest.TestCase): def setUp(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(run_coroutine_threadsafe( + assert run_coroutine_threadsafe( comps.async_setup(self.hass, {}), self.hass.loop - ).result()) + ).result() self.hass.states.set('light.Bowl', STATE_ON) self.hass.states.set('light.Ceiling', STATE_OFF) @@ -110,38 +110,38 @@ class TestComponentsCore(unittest.TestCase): def test_is_on(self): """Test is_on method.""" - self.assertTrue(comps.is_on(self.hass, 'light.Bowl')) - self.assertFalse(comps.is_on(self.hass, 'light.Ceiling')) - self.assertTrue(comps.is_on(self.hass)) - self.assertFalse(comps.is_on(self.hass, 'non_existing.entity')) + assert comps.is_on(self.hass, 'light.Bowl') + assert not comps.is_on(self.hass, 'light.Ceiling') + assert comps.is_on(self.hass) + assert not comps.is_on(self.hass, 'non_existing.entity') def test_turn_on_without_entities(self): """Test turn_on method without entities.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) turn_on(self.hass) self.hass.block_till_done() - self.assertEqual(0, len(calls)) + assert 0 == len(calls) def test_turn_on(self): """Test turn_on method.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) turn_on(self.hass, 'light.Ceiling') self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) def test_turn_off(self): """Test turn_off method.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF) turn_off(self.hass, 'light.Bowl') self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) def test_toggle(self): """Test toggle method.""" calls = mock_service(self.hass, 'light', SERVICE_TOGGLE) toggle(self.hass, 'light.Bowl') self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) @patch('homeassistant.config.os.path.isfile', Mock(return_value=True)) def test_reload_core_conf(self): diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py index b947155e6b2..9fc9ceaefc1 100644 --- a/tests/components/test_input_boolean.py +++ b/tests/components/test_input_boolean.py @@ -69,38 +69,34 @@ class TestInputBoolean(unittest.TestCase): ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_methods(self): """Test is_on, turn_on, turn_off methods.""" - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': None, - }})) + }}) entity_id = 'input_boolean.test_1' - self.assertFalse( - is_on(self.hass, entity_id)) + assert not is_on(self.hass, entity_id) turn_on(self.hass, entity_id) self.hass.block_till_done() - self.assertTrue( - is_on(self.hass, entity_id)) + assert is_on(self.hass, entity_id) turn_off(self.hass, entity_id) self.hass.block_till_done() - self.assertFalse( - is_on(self.hass, entity_id)) + assert not is_on(self.hass, entity_id) toggle(self.hass, entity_id) self.hass.block_till_done() - self.assertTrue(is_on(self.hass, entity_id)) + assert is_on(self.hass, entity_id) def test_config_options(self): """Test configuration options.""" @@ -108,33 +104,33 @@ class TestInputBoolean(unittest.TestCase): _LOGGER.debug('ENTITIES @ start: %s', self.hass.states.entity_ids()) - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': None, 'test_2': { 'name': 'Hello World', 'icon': 'mdi:work', 'initial': True, }, - }})) + }}) _LOGGER.debug('ENTITIES: %s', self.hass.states.entity_ids()) - self.assertEqual(count_start + 2, len(self.hass.states.entity_ids())) + assert count_start + 2 == len(self.hass.states.entity_ids()) state_1 = self.hass.states.get('input_boolean.test_1') state_2 = self.hass.states.get('input_boolean.test_2') - self.assertIsNotNone(state_1) - self.assertIsNotNone(state_2) + assert state_1 is not None + assert state_2 is not None - self.assertEqual(STATE_OFF, state_1.state) - self.assertNotIn(ATTR_ICON, state_1.attributes) - self.assertNotIn(ATTR_FRIENDLY_NAME, state_1.attributes) + assert STATE_OFF == state_1.state + assert ATTR_ICON not in state_1.attributes + assert ATTR_FRIENDLY_NAME not in state_1.attributes - self.assertEqual(STATE_ON, state_2.state) - self.assertEqual('Hello World', - state_2.attributes.get(ATTR_FRIENDLY_NAME)) - self.assertEqual('mdi:work', state_2.attributes.get(ATTR_ICON)) + assert STATE_ON == state_2.state + assert 'Hello World' == \ + state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert 'mdi:work' == state_2.attributes.get(ATTR_ICON) @asyncio.coroutine diff --git a/tests/components/test_input_datetime.py b/tests/components/test_input_datetime.py index 9ced2aaa072..e7f6b50c43d 100644 --- a/tests/components/test_input_datetime.py +++ b/tests/components/test_input_datetime.py @@ -46,8 +46,7 @@ class TestInputDatetime(unittest.TestCase): }}, ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) @asyncio.coroutine diff --git a/tests/components/test_input_number.py b/tests/components/test_input_number.py index a53704e1d10..3129b4445c7 100644 --- a/tests/components/test_input_number.py +++ b/tests/components/test_input_number.py @@ -73,97 +73,95 @@ class TestInputNumber(unittest.TestCase): }}, ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_set_value(self): """Test set_value method.""" - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'initial': 50, 'min': 0, 'max': 100, }, - }})) + }}) entity_id = 'input_number.test_1' state = self.hass.states.get(entity_id) - self.assertEqual(50, float(state.state)) + assert 50 == float(state.state) set_value(self.hass, entity_id, '30.4') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(30.4, float(state.state)) + assert 30.4 == float(state.state) set_value(self.hass, entity_id, '70') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(70, float(state.state)) + assert 70 == float(state.state) set_value(self.hass, entity_id, '110') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(70, float(state.state)) + assert 70 == float(state.state) def test_increment(self): """Test increment method.""" - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_2': { 'initial': 50, 'min': 0, 'max': 51, }, - }})) + }}) entity_id = 'input_number.test_2' state = self.hass.states.get(entity_id) - self.assertEqual(50, float(state.state)) + assert 50 == float(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(51, float(state.state)) + assert 51 == float(state.state) increment(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(51, float(state.state)) + assert 51 == float(state.state) def test_decrement(self): """Test decrement method.""" - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_3': { 'initial': 50, 'min': 49, 'max': 100, }, - }})) + }}) entity_id = 'input_number.test_3' state = self.hass.states.get(entity_id) - self.assertEqual(50, float(state.state)) + assert 50 == float(state.state) decrement(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(49, float(state.state)) + assert 49 == float(state.state) decrement(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual(49, float(state.state)) + assert 49 == float(state.state) def test_mode(self): """Test mode settings.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_default_slider': { 'min': 0, 'max': 100, @@ -178,19 +176,19 @@ class TestInputNumber(unittest.TestCase): 'max': 100, 'mode': 'slider', }, - }})) + }}) state = self.hass.states.get('input_number.test_default_slider') assert state - self.assertEqual('slider', state.attributes['mode']) + assert 'slider' == state.attributes['mode'] state = self.hass.states.get('input_number.test_explicit_box') assert state - self.assertEqual('box', state.attributes['mode']) + assert 'box' == state.attributes['mode'] state = self.hass.states.get('input_number.test_explicit_slider') assert state - self.assertEqual('slider', state.attributes['mode']) + assert 'slider' == state.attributes['mode'] @asyncio.coroutine diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py index 25f6d1c7673..684c526cbeb 100644 --- a/tests/components/test_input_select.py +++ b/tests/components/test_input_select.py @@ -76,41 +76,38 @@ class TestInputSelect(unittest.TestCase): ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_select_option(self): """Test select_option methods.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'options': [ 'some option', 'another option', ], }, - }})) + }}) entity_id = 'input_select.test_1' state = self.hass.states.get(entity_id) - self.assertEqual('some option', state.state) + assert 'some option' == state.state select_option(self.hass, entity_id, 'another option') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('another option', state.state) + assert 'another option' == state.state select_option(self.hass, entity_id, 'non existing option') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('another option', state.state) + assert 'another option' == state.state def test_select_next(self): """Test select_next methods.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'options': [ 'first option', @@ -119,28 +116,27 @@ class TestInputSelect(unittest.TestCase): ], 'initial': 'middle option', }, - }})) + }}) entity_id = 'input_select.test_1' state = self.hass.states.get(entity_id) - self.assertEqual('middle option', state.state) + assert 'middle option' == state.state select_next(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('last option', state.state) + assert 'last option' == state.state select_next(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('first option', state.state) + assert 'first option' == state.state def test_select_previous(self): """Test select_previous methods.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'options': [ 'first option', @@ -149,23 +145,23 @@ class TestInputSelect(unittest.TestCase): ], 'initial': 'middle option', }, - }})) + }}) entity_id = 'input_select.test_1' state = self.hass.states.get(entity_id) - self.assertEqual('middle option', state.state) + assert 'middle option' == state.state select_previous(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('first option', state.state) + assert 'first option' == state.state select_previous(self.hass, entity_id) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('last option', state.state) + assert 'last option' == state.state def test_config_options(self): """Test configuration options.""" @@ -177,7 +173,7 @@ class TestInputSelect(unittest.TestCase): 'Best Option', ] - self.assertTrue(setup_component(self.hass, DOMAIN, { + assert setup_component(self.hass, DOMAIN, { DOMAIN: { 'test_1': { 'options': [ @@ -192,32 +188,31 @@ class TestInputSelect(unittest.TestCase): 'initial': 'Better Option', }, } - })) + }) - self.assertEqual(count_start + 2, len(self.hass.states.entity_ids())) + assert count_start + 2 == len(self.hass.states.entity_ids()) state_1 = self.hass.states.get('input_select.test_1') state_2 = self.hass.states.get('input_select.test_2') - self.assertIsNotNone(state_1) - self.assertIsNotNone(state_2) + assert state_1 is not None + assert state_2 is not None - self.assertEqual('1', state_1.state) - self.assertEqual(['1', '2'], - state_1.attributes.get(ATTR_OPTIONS)) - self.assertNotIn(ATTR_ICON, state_1.attributes) + assert '1' == state_1.state + assert ['1', '2'] == \ + state_1.attributes.get(ATTR_OPTIONS) + assert ATTR_ICON not in state_1.attributes - self.assertEqual('Better Option', state_2.state) - self.assertEqual(test_2_options, - state_2.attributes.get(ATTR_OPTIONS)) - self.assertEqual('Hello World', - state_2.attributes.get(ATTR_FRIENDLY_NAME)) - self.assertEqual('mdi:work', state_2.attributes.get(ATTR_ICON)) + assert 'Better Option' == state_2.state + assert test_2_options == \ + state_2.attributes.get(ATTR_OPTIONS) + assert 'Hello World' == \ + state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert 'mdi:work' == state_2.attributes.get(ATTR_ICON) def test_set_options_service(self): """Test set_options service.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'options': [ 'first option', @@ -226,28 +221,28 @@ class TestInputSelect(unittest.TestCase): ], 'initial': 'middle option', }, - }})) + }}) entity_id = 'input_select.test_1' state = self.hass.states.get(entity_id) - self.assertEqual('middle option', state.state) + assert 'middle option' == state.state data = {ATTR_OPTIONS: ["test1", "test2"], "entity_id": entity_id} self.hass.services.call(DOMAIN, SERVICE_SET_OPTIONS, data) self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('test1', state.state) + assert 'test1' == state.state select_option(self.hass, entity_id, 'first option') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('test1', state.state) + assert 'test1' == state.state select_option(self.hass, entity_id, 'test2') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('test2', state.state) + assert 'test2' == state.state @asyncio.coroutine diff --git a/tests/components/test_input_text.py b/tests/components/test_input_text.py index bea145390eb..110a3190b1f 100644 --- a/tests/components/test_input_text.py +++ b/tests/components/test_input_text.py @@ -50,39 +50,37 @@ class TestInputText(unittest.TestCase): }}, ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_set_value(self): """Test set_value method.""" - self.assertTrue(setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_1': { 'initial': 'test', 'min': 3, 'max': 10, }, - }})) + }}) entity_id = 'input_text.test_1' state = self.hass.states.get(entity_id) - self.assertEqual('test', str(state.state)) + assert 'test' == str(state.state) set_value(self.hass, entity_id, 'testing') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('testing', str(state.state)) + assert 'testing' == str(state.state) set_value(self.hass, entity_id, 'testing too long') self.hass.block_till_done() state = self.hass.states.get(entity_id) - self.assertEqual('testing', str(state.state)) + assert 'testing' == str(state.state) def test_mode(self): """Test mode settings.""" - self.assertTrue( - setup_component(self.hass, DOMAIN, {DOMAIN: { + assert setup_component(self.hass, DOMAIN, {DOMAIN: { 'test_default_text': { 'initial': 'test', 'min': 3, @@ -100,19 +98,19 @@ class TestInputText(unittest.TestCase): 'max': 10, 'mode': 'password', }, - }})) + }}) state = self.hass.states.get('input_text.test_default_text') assert state - self.assertEqual('text', state.attributes['mode']) + assert 'text' == state.attributes['mode'] state = self.hass.states.get('input_text.test_explicit_text') assert state - self.assertEqual('text', state.attributes['mode']) + assert 'text' == state.attributes['mode'] state = self.hass.states.get('input_text.test_explicit_password') assert state - self.assertEqual('password', state.attributes['mode']) + assert 'password' == state.attributes['mode'] @asyncio.coroutine diff --git a/tests/components/test_introduction.py b/tests/components/test_introduction.py index b7099d04878..c414ab97ae1 100644 --- a/tests/components/test_introduction.py +++ b/tests/components/test_introduction.py @@ -20,4 +20,4 @@ class TestIntroduction(unittest.TestCase): def test_setup(self): """Test introduction setup.""" - self.assertTrue(setup_component(self.hass, introduction.DOMAIN, {})) + assert setup_component(self.hass, introduction.DOMAIN, {}) diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 1100a16b381..89528c1772b 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -63,15 +63,15 @@ class TestComponentLogbook(unittest.TestCase): # scheduled. This means that they may not have been processed yet. self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) last_call = calls[-1] - self.assertEqual('Alarm', last_call.data.get(logbook.ATTR_NAME)) - self.assertEqual('is triggered', last_call.data.get( - logbook.ATTR_MESSAGE)) - self.assertEqual('switch', last_call.data.get(logbook.ATTR_DOMAIN)) - self.assertEqual('switch.test_switch', last_call.data.get( - logbook.ATTR_ENTITY_ID)) + assert 'Alarm' == last_call.data.get(logbook.ATTR_NAME) + assert 'is triggered' == last_call.data.get( + logbook.ATTR_MESSAGE) + assert 'switch' == last_call.data.get(logbook.ATTR_DOMAIN) + assert 'switch.test_switch' == last_call.data.get( + logbook.ATTR_ENTITY_ID) def test_service_call_create_log_book_entry_no_message(self): """Test if service call create log book entry without message.""" @@ -90,7 +90,7 @@ class TestComponentLogbook(unittest.TestCase): # scheduled. This means that they may not have been processed yet. self.hass.block_till_done() - self.assertEqual(0, len(calls)) + assert 0 == len(calls) def test_humanify_filter_sensor(self): """Test humanify filter too frequent sensor values.""" @@ -106,7 +106,7 @@ class TestComponentLogbook(unittest.TestCase): entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC))) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], pointB, 'bla', domain='sensor', entity_id=entity_id) @@ -123,7 +123,7 @@ class TestComponentLogbook(unittest.TestCase): entries = list(logbook.humanify(self.hass, (eventA,))) - self.assertEqual(0, len(entries)) + assert 0 == len(entries) def test_exclude_new_entities(self): """Test if events are excluded on first update.""" @@ -140,7 +140,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB), {}) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -162,7 +162,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB), {}) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -184,7 +184,7 @@ class TestComponentLogbook(unittest.TestCase): eventA, eventB), {}) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -210,7 +210,7 @@ class TestComponentLogbook(unittest.TestCase): config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -236,7 +236,7 @@ class TestComponentLogbook(unittest.TestCase): config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry(entries[0], name='Home Assistant', message='started', domain=ha.DOMAIN) self.assert_entry(entries[1], pointB, 'blu', domain='sensor', @@ -273,7 +273,7 @@ class TestComponentLogbook(unittest.TestCase): config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -299,7 +299,7 @@ class TestComponentLogbook(unittest.TestCase): config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='stopped', domain=ha.DOMAIN) @@ -325,7 +325,7 @@ class TestComponentLogbook(unittest.TestCase): config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry(entries[0], name='Home Assistant', message='started', domain=ha.DOMAIN) self.assert_entry(entries[1], pointB, 'blu', domain='sensor', @@ -359,7 +359,7 @@ class TestComponentLogbook(unittest.TestCase): eventB1, eventB2), config[logbook.DOMAIN]) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(3, len(entries)) + assert 3 == len(entries) self.assert_entry(entries[0], name='Home Assistant', message='started', domain=ha.DOMAIN) self.assert_entry(entries[1], pointA, 'blu', domain='sensor', @@ -380,7 +380,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((eventA, eventB), {}) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(1, len(entries)) + assert 1 == len(entries) self.assert_entry(entries[0], pointA, 'bla', domain='switch', entity_id=entity_id) @@ -398,7 +398,7 @@ class TestComponentLogbook(unittest.TestCase): events = logbook._exclude_events((eventA, eventB), {}) entries = list(logbook.humanify(self.hass, events)) - self.assertEqual(1, len(entries)) + assert 1 == len(entries) self.assert_entry(entries[0], pointA, 'bla', domain='switch', entity_id=entity_id) @@ -412,7 +412,7 @@ class TestComponentLogbook(unittest.TestCase): ha.Event(EVENT_HOMEASSISTANT_START), ))) - self.assertEqual(1, len(entries)) + assert 1 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='restarted', domain=ha.DOMAIN) @@ -427,7 +427,7 @@ class TestComponentLogbook(unittest.TestCase): self.create_state_changed_event(pointA, entity_id, 10) ))) - self.assertEqual(2, len(entries)) + assert 2 == len(entries) self.assert_entry( entries[0], name='Home Assistant', message='started', domain=ha.DOMAIN) @@ -445,21 +445,21 @@ class TestComponentLogbook(unittest.TestCase): eventA = self.create_state_changed_event(pointA, 'switch.bla', 10) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('changed to 10', message) + assert 'changed to 10' == message # message for a switch turned on eventA = self.create_state_changed_event(pointA, 'switch.bla', STATE_ON) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('turned on', message) + assert 'turned on' == message # message for a switch turned off eventA = self.create_state_changed_event(pointA, 'switch.bla', STATE_OFF) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('turned off', message) + assert 'turned off' == message def test_entry_message_from_state_device_tracker(self): """Test if logbook message is correctly created for device tracker.""" @@ -470,14 +470,14 @@ class TestComponentLogbook(unittest.TestCase): STATE_NOT_HOME) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('is away', message) + assert 'is away' == message # message for a device tracker "home" state eventA = self.create_state_changed_event(pointA, 'device_tracker.john', 'work') to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('is at work', message) + assert 'is at work' == message def test_entry_message_from_state_sun(self): """Test if logbook message is correctly created for sun.""" @@ -488,14 +488,14 @@ class TestComponentLogbook(unittest.TestCase): sun.STATE_ABOVE_HORIZON) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('has risen', message) + assert 'has risen' == message # message for a sun set eventA = self.create_state_changed_event(pointA, 'sun.sun', sun.STATE_BELOW_HORIZON) to_state = ha.State.from_dict(eventA.data.get('new_state')) message = logbook._entry_message_from_state(to_state.domain, to_state) - self.assertEqual('has set', message) + assert 'has set' == message def test_process_custom_logbook_entries(self): """Test if custom log book entries get added as an entry.""" @@ -511,7 +511,7 @@ class TestComponentLogbook(unittest.TestCase): }), ))) - self.assertEqual(1, len(entries)) + assert 1 == len(entries) self.assert_entry( entries[0], name=name, message=message, domain='sun', entity_id=entity_id) @@ -520,19 +520,19 @@ class TestComponentLogbook(unittest.TestCase): domain=None, entity_id=None): """Assert an entry is what is expected.""" if when: - self.assertEqual(when, entry['when']) + assert when == entry['when'] if name: - self.assertEqual(name, entry['name']) + assert name == entry['name'] if message: - self.assertEqual(message, entry['message']) + assert message == entry['message'] if domain: - self.assertEqual(domain, entry['domain']) + assert domain == entry['domain'] if entity_id: - self.assertEqual(entity_id, entry['entity_id']) + assert entity_id == entry['entity_id'] def create_state_changed_event(self, event_time_fired, entity_id, state, attributes=None, last_changed=None, diff --git a/tests/components/test_logentries.py b/tests/components/test_logentries.py index 843a043cee6..2de6be0fd6b 100644 --- a/tests/components/test_logentries.py +++ b/tests/components/test_logentries.py @@ -29,10 +29,10 @@ class TestLogentries(unittest.TestCase): } } self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, logentries.DOMAIN, config)) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + assert setup_component(self.hass, logentries.DOMAIN, config) + assert self.hass.bus.listen.called + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[0][0][0] def test_setup_config_defaults(self): """Test setup with defaults.""" @@ -42,10 +42,10 @@ class TestLogentries(unittest.TestCase): } } self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, logentries.DOMAIN, config)) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + assert setup_component(self.hass, logentries.DOMAIN, config) + assert self.hass.bus.listen.called + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[0][0][0] def _setup(self, mock_requests): """Test the setup.""" @@ -91,9 +91,7 @@ class TestLogentries(unittest.TestCase): 'logs/token', 'event': body} self.handler_method(event) - self.assertEqual(self.mock_post.call_count, 1) - self.assertEqual( - self.mock_post.call_args, + assert self.mock_post.call_count == 1 + assert self.mock_post.call_args == \ mock.call(payload['host'], data=payload, timeout=10) - ) self.mock_post.reset_mock() diff --git a/tests/components/test_logger.py b/tests/components/test_logger.py index f774af2169c..cb65d5b3f40 100644 --- a/tests/components/test_logger.py +++ b/tests/components/test_logger.py @@ -40,24 +40,24 @@ class TestUpdater(unittest.TestCase): def assert_logged(self, name, level): """Assert that a certain record was logged.""" - self.assertTrue(self.log_filter.filter(RECORD(name, level))) + assert self.log_filter.filter(RECORD(name, level)) def assert_not_logged(self, name, level): """Assert that a certain record was not logged.""" - self.assertFalse(self.log_filter.filter(RECORD(name, level))) + assert not self.log_filter.filter(RECORD(name, level)) def test_logger_setup(self): """Use logger to create a logging filter.""" self.setup_logger(TEST_CONFIG) - self.assertTrue(len(logging.root.handlers) > 0) + assert len(logging.root.handlers) > 0 handler = logging.root.handlers[-1] - self.assertEqual(len(handler.filters), 1) + assert len(handler.filters) == 1 log_filter = handler.filters[0].logfilter - self.assertEqual(log_filter['default'], logging.WARNING) - self.assertEqual(log_filter['logs']['test'], logging.INFO) + assert log_filter['default'] == logging.WARNING + assert log_filter['logs']['test'] == logging.INFO def test_logger_test_filters(self): """Test resulting filter operation.""" diff --git a/tests/components/test_melissa.py b/tests/components/test_melissa.py index e39ceb1add1..020d4f7db1e 100644 --- a/tests/components/test_melissa.py +++ b/tests/components/test_melissa.py @@ -31,8 +31,6 @@ class TestMelissa(unittest.TestCase): mocked_melissa.Melissa.assert_called_with( username="********", password="********") - self.assertIn(melissa.DATA_MELISSA, self.hass.data) - self.assertIsInstance( - self.hass.data[melissa.DATA_MELISSA], type( - mocked_melissa.Melissa()) - ) + assert melissa.DATA_MELISSA in self.hass.data + assert isinstance(self.hass.data[melissa.DATA_MELISSA], type( + mocked_melissa.Melissa())) diff --git a/tests/components/test_nuheat.py b/tests/components/test_nuheat.py index 2b0f249f4c9..eec93f5311c 100644 --- a/tests/components/test_nuheat.py +++ b/tests/components/test_nuheat.py @@ -34,12 +34,11 @@ class TestNuHeat(unittest.TestCase): nuheat.setup(self.hass, self.config) mocked_nuheat.NuHeat.assert_called_with("warm", "feet") - self.assertIn(nuheat.DOMAIN, self.hass.data) - self.assertEqual(2, len(self.hass.data[nuheat.DOMAIN])) - self.assertIsInstance( - self.hass.data[nuheat.DOMAIN][0], type(mocked_nuheat.NuHeat()) - ) - self.assertEqual(self.hass.data[nuheat.DOMAIN][1], "thermostat123") + assert nuheat.DOMAIN in self.hass.data + assert 2 == len(self.hass.data[nuheat.DOMAIN]) + assert isinstance(self.hass.data[nuheat.DOMAIN][0], + type(mocked_nuheat.NuHeat())) + assert self.hass.data[nuheat.DOMAIN][1] == "thermostat123" mocked_load.assert_called_with( self.hass, "climate", nuheat.DOMAIN, {}, self.config diff --git a/tests/components/test_pilight.py b/tests/components/test_pilight.py index e630a354f45..01582b364eb 100644 --- a/tests/components/test_pilight.py +++ b/tests/components/test_pilight.py @@ -85,11 +85,11 @@ class TestPilight(unittest.TestCase): with assert_setup_component(4): with patch('pilight.pilight.Client', side_effect=socket.error) as mock_client: - self.assertFalse(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert not setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) mock_client.assert_called_once_with(host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT) - self.assertEqual(1, mock_error.call_count) + assert 1 == mock_error.call_count @patch('homeassistant.components.pilight._LOGGER.error') def test_connection_timeout_error(self, mock_error): @@ -97,11 +97,11 @@ class TestPilight(unittest.TestCase): with assert_setup_component(4): with patch('pilight.pilight.Client', side_effect=socket.timeout) as mock_client: - self.assertFalse(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert not setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) mock_client.assert_called_once_with(host=pilight.DEFAULT_HOST, port=pilight.DEFAULT_PORT) - self.assertEqual(1, mock_error.call_count) + assert 1 == mock_error.call_count @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.error') @@ -109,8 +109,8 @@ class TestPilight(unittest.TestCase): def test_send_code_no_protocol(self, mock_pilight_error, mock_error): """Try to send data without protocol information, should give error.""" with assert_setup_component(4): - self.assertTrue(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Call without protocol info, should be ignored with error self.hass.services.call(pilight.DOMAIN, pilight.SERVICE_NAME, @@ -119,17 +119,16 @@ class TestPilight(unittest.TestCase): blocking=True) self.hass.block_till_done() error_log_call = mock_error.call_args_list[-1] - self.assertTrue( - 'required key not provided @ data[\'protocol\']' in - str(error_log_call)) + assert 'required key not provided @ data[\'protocol\']' in \ + str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('tests.components.test_pilight._LOGGER.error') def test_send_code(self, mock_pilight_error): """Try to send proper data.""" with assert_setup_component(4): - self.assertTrue(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Call with protocol info, should not give error service_data = {'protocol': 'test', @@ -140,7 +139,7 @@ class TestPilight(unittest.TestCase): self.hass.block_till_done() error_log_call = mock_pilight_error.call_args_list[-1] service_data['protocol'] = [service_data['protocol']] - self.assertTrue(str(service_data) in str(error_log_call)) + assert str(service_data) in str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.components.pilight._LOGGER.error') @@ -149,8 +148,8 @@ class TestPilight(unittest.TestCase): with assert_setup_component(4): with patch('pilight.pilight.Client.send_code', side_effect=IOError): - self.assertTrue(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Call with protocol info, should not give error service_data = {'protocol': 'test', @@ -160,16 +159,16 @@ class TestPilight(unittest.TestCase): blocking=True) self.hass.block_till_done() error_log_call = mock_pilight_error.call_args_list[-1] - self.assertTrue('Pilight send failed' in str(error_log_call)) + assert 'Pilight send failed' in str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('tests.components.test_pilight._LOGGER.error') def test_send_code_delay(self, mock_pilight_error): """Try to send proper data with delay afterwards.""" with assert_setup_component(4): - self.assertTrue(setup_component( + assert setup_component( self.hass, pilight.DOMAIN, - {pilight.DOMAIN: {pilight.CONF_SEND_DELAY: 5.0}})) + {pilight.DOMAIN: {pilight.CONF_SEND_DELAY: 5.0}}) # Call with protocol info, should not give error service_data1 = {'protocol': 'test11', @@ -189,47 +188,44 @@ class TestPilight(unittest.TestCase): {ha.ATTR_NOW: dt_util.utcnow()}) self.hass.block_till_done() error_log_call = mock_pilight_error.call_args_list[-1] - self.assertTrue(str(service_data1) in str(error_log_call)) + assert str(service_data1) in str(error_log_call) new_time = dt_util.utcnow() + timedelta(seconds=5) self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: new_time}) self.hass.block_till_done() error_log_call = mock_pilight_error.call_args_list[-1] - self.assertTrue(str(service_data2) in str(error_log_call)) + assert str(service_data2) in str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('tests.components.test_pilight._LOGGER.error') def test_start_stop(self, mock_pilight_error): """Check correct startup and stop of pilight daemon.""" with assert_setup_component(4): - self.assertTrue(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Test startup self.hass.start() self.hass.block_till_done() error_log_call = mock_pilight_error.call_args_list[-2] - self.assertTrue( - 'PilightDaemonSim callback' in str(error_log_call)) + assert 'PilightDaemonSim callback' in str(error_log_call) error_log_call = mock_pilight_error.call_args_list[-1] - self.assertTrue( - 'PilightDaemonSim start' in str(error_log_call)) + assert 'PilightDaemonSim start' in str(error_log_call) # Test stop self.skip_teardown_stop = True self.hass.stop() error_log_call = mock_pilight_error.call_args_list[-1] - self.assertTrue( - 'PilightDaemonSim stop' in str(error_log_call)) + assert 'PilightDaemonSim stop' in str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.info') def test_receive_code(self, mock_info): """Check if code receiving via pilight daemon works.""" with assert_setup_component(4): - self.assertTrue(setup_component( - self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}})) + assert setup_component( + self.hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Test startup self.hass.start() @@ -243,8 +239,8 @@ class TestPilight(unittest.TestCase): # Check if all message parts are put on event bus for key, value in expected_message.items(): - self.assertTrue(str(key) in str(error_log_call)) - self.assertTrue(str(value) in str(error_log_call)) + assert str(key) in str(error_log_call) + assert str(value) in str(error_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.info') @@ -256,9 +252,9 @@ class TestPilight(unittest.TestCase): 'uuid': [PilightDaemonSim.test_message['uuid']], 'id': [PilightDaemonSim.test_message['message']['id']], 'unit': [PilightDaemonSim.test_message['message']['unit']]} - self.assertTrue(setup_component( + assert setup_component( self.hass, pilight.DOMAIN, - {pilight.DOMAIN: {"whitelist": whitelist}})) + {pilight.DOMAIN: {"whitelist": whitelist}}) self.hass.start() self.hass.block_till_done() @@ -271,8 +267,8 @@ class TestPilight(unittest.TestCase): # Check if all message parts are put on event bus for key, value in expected_message.items(): - self.assertTrue(str(key) in str(info_log_call)) - self.assertTrue(str(value) in str(info_log_call)) + assert str(key) in str(info_log_call) + assert str(value) in str(info_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.info') @@ -282,9 +278,9 @@ class TestPilight(unittest.TestCase): whitelist = { 'protocol': [PilightDaemonSim.test_message['protocol']], 'id': [PilightDaemonSim.test_message['message']['id']]} - self.assertTrue(setup_component( + assert setup_component( self.hass, pilight.DOMAIN, - {pilight.DOMAIN: {"whitelist": whitelist}})) + {pilight.DOMAIN: {"whitelist": whitelist}}) self.hass.start() self.hass.block_till_done() @@ -297,8 +293,8 @@ class TestPilight(unittest.TestCase): # Check if all message parts are put on event bus for key, value in expected_message.items(): - self.assertTrue(str(key) in str(info_log_call)) - self.assertTrue(str(value) in str(info_log_call)) + assert str(key) in str(info_log_call) + assert str(value) in str(info_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.info') @@ -309,9 +305,9 @@ class TestPilight(unittest.TestCase): 'protocol': [PilightDaemonSim.test_message['protocol'], 'other_protocol'], 'id': [PilightDaemonSim.test_message['message']['id']]} - self.assertTrue(setup_component( + assert setup_component( self.hass, pilight.DOMAIN, - {pilight.DOMAIN: {"whitelist": whitelist}})) + {pilight.DOMAIN: {"whitelist": whitelist}}) self.hass.start() self.hass.block_till_done() @@ -324,8 +320,8 @@ class TestPilight(unittest.TestCase): # Check if all message parts are put on event bus for key, value in expected_message.items(): - self.assertTrue(str(key) in str(info_log_call)) - self.assertTrue(str(value) in str(info_log_call)) + assert str(key) in str(info_log_call) + assert str(value) in str(info_log_call) @patch('pilight.pilight.Client', PilightDaemonSim) @patch('homeassistant.core._LOGGER.info') @@ -335,16 +331,16 @@ class TestPilight(unittest.TestCase): whitelist = { 'protocol': ['wrong_protocol'], 'id': [PilightDaemonSim.test_message['message']['id']]} - self.assertTrue(setup_component( + assert setup_component( self.hass, pilight.DOMAIN, - {pilight.DOMAIN: {"whitelist": whitelist}})) + {pilight.DOMAIN: {"whitelist": whitelist}}) self.hass.start() self.hass.block_till_done() info_log_call = mock_info.call_args_list[-1] - self.assertFalse('Event pilight_received' in info_log_call) + assert not ('Event pilight_received' in info_log_call) class TestPilightCallrateThrottler(unittest.TestCase): @@ -368,7 +364,7 @@ class TestPilightCallrateThrottler(unittest.TestCase): for i in range(3): action(i) - self.assertEqual(runs, [0, 1, 2]) + assert runs == [0, 1, 2] def test_call_rate_delay_throttle_enabled(self): """Test that throttling actually work.""" @@ -381,7 +377,7 @@ class TestPilightCallrateThrottler(unittest.TestCase): for i in range(3): action(i) - self.assertEqual(runs, []) + assert runs == [] exp = [] now = dt_util.utcnow() @@ -391,4 +387,4 @@ class TestPilightCallrateThrottler(unittest.TestCase): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: shifted_time}) self.hass.block_till_done() - self.assertEqual(runs, exp) + assert runs == exp diff --git a/tests/components/test_plant.py b/tests/components/test_plant.py index 95167dd181b..6118e30925e 100644 --- a/tests/components/test_plant.py +++ b/tests/components/test_plant.py @@ -101,8 +101,8 @@ class TestPlant(unittest.TestCase): {ATTR_UNIT_OF_MEASUREMENT: 'us/cm'}) self.hass.block_till_done() state = self.hass.states.get('plant.'+plant_name) - self.assertEqual(STATE_PROBLEM, state.state) - self.assertEqual(5, state.attributes[plant.READING_MOISTURE]) + assert STATE_PROBLEM == state.state + assert 5 == state.attributes[plant.READING_MOISTURE] @pytest.mark.skipif(plant.ENABLE_LOAD_HISTORY is False, reason="tests for loading from DB are unstable, thus" @@ -132,10 +132,10 @@ class TestPlant(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get('plant.'+plant_name) - self.assertEqual(STATE_UNKNOWN, state.state) + assert STATE_UNKNOWN == state.state max_brightness = state.attributes.get( plant.ATTR_MAX_BRIGHTNESS_HISTORY) - self.assertEqual(30, max_brightness) + assert 30 == max_brightness def test_brightness_history(self): """Test the min_brightness check.""" @@ -149,19 +149,19 @@ class TestPlant(unittest.TestCase): {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) self.hass.block_till_done() state = self.hass.states.get('plant.'+plant_name) - self.assertEqual(STATE_PROBLEM, state.state) + assert STATE_PROBLEM == state.state self.hass.states.set(BRIGHTNESS_ENTITY, 600, {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) self.hass.block_till_done() state = self.hass.states.get('plant.'+plant_name) - self.assertEqual(STATE_OK, state.state) + assert STATE_OK == state.state self.hass.states.set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: 'lux'}) self.hass.block_till_done() state = self.hass.states.get('plant.'+plant_name) - self.assertEqual(STATE_OK, state.state) + assert STATE_OK == state.state class TestDailyHistory(unittest.TestCase): @@ -170,7 +170,7 @@ class TestDailyHistory(unittest.TestCase): def test_no_data(self): """Test with empty history.""" dh = plant.DailyHistory(3) - self.assertIsNone(dh.max) + assert dh.max is None def test_one_day(self): """Test storing data for the same day.""" @@ -179,8 +179,8 @@ class TestDailyHistory(unittest.TestCase): for i in range(len(values)): dh.add_measurement(values[i]) max_value = max(values[0:i+1]) - self.assertEqual(1, len(dh._days)) - self.assertEqual(dh.max, max_value) + assert 1 == len(dh._days) + assert dh.max == max_value def test_multiple_days(self): """Test storing data for different days.""" @@ -195,4 +195,4 @@ class TestDailyHistory(unittest.TestCase): for i in range(len(days)): dh.add_measurement(values[i], days[i]) - self.assertEqual(max_values[i], dh.max) + assert max_values[i] == dh.max diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index f69ace46014..ca8915cd08f 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -58,7 +58,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) proximities = ['home', 'work'] @@ -93,7 +93,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) def test_proximity(self): """Test the proximity.""" @@ -112,7 +112,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) state = self.hass.states.get('proximity.home') assert state.state == 'not set' @@ -140,7 +140,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'home', @@ -172,7 +172,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'home', @@ -212,7 +212,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -243,7 +243,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -285,7 +285,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -327,7 +327,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'work', @@ -356,7 +356,7 @@ class TestProximity(unittest.TestCase): } } - self.assertTrue(setup_component(self.hass, DOMAIN, config)) + assert setup_component(self.hass, DOMAIN, config) self.hass.states.set( 'device_tracker.test1', 'not_home', diff --git a/tests/components/test_remember_the_milk.py b/tests/components/test_remember_the_milk.py index d9db61efd40..89f19f624a9 100644 --- a/tests/components/test_remember_the_milk.py +++ b/tests/components/test_remember_the_milk.py @@ -42,14 +42,14 @@ class TestConfiguration(unittest.TestCase): patch.object(rtm.RememberTheMilkConfiguration, 'save_config'): config = rtm.RememberTheMilkConfiguration(self.hass) config.set_token(self.profile, self.token) - self.assertEqual(config.get_token(self.profile), self.token) + assert config.get_token(self.profile) == self.token def test_load_config(self): """Test loading an existing token from the file.""" with patch("builtins.open", mock_open(read_data=self.json_string)), \ patch("os.path.isfile", Mock(return_value=True)): config = rtm.RememberTheMilkConfiguration(self.hass) - self.assertEqual(config.get_token(self.profile), self.token) + assert config.get_token(self.profile) == self.token def test_invalid_data(self): """Test starts with invalid data and should not raise an exception.""" @@ -57,7 +57,7 @@ class TestConfiguration(unittest.TestCase): mock_open(read_data='random characters')),\ patch("os.path.isfile", Mock(return_value=True)): config = rtm.RememberTheMilkConfiguration(self.hass) - self.assertIsNotNone(config) + assert config is not None def test_id_map(self): """Test the hass to rtm task is mapping.""" @@ -70,18 +70,18 @@ class TestConfiguration(unittest.TestCase): patch.object(rtm.RememberTheMilkConfiguration, 'save_config'): config = rtm.RememberTheMilkConfiguration(self.hass) - self.assertEqual(None, config.get_rtm_id(self.profile, hass_id)) + assert config.get_rtm_id(self.profile, hass_id) is None config.set_rtm_id(self.profile, hass_id, list_id, timeseries_id, rtm_id) - self.assertEqual((list_id, timeseries_id, rtm_id), - config.get_rtm_id(self.profile, hass_id)) + assert (list_id, timeseries_id, rtm_id) == \ + config.get_rtm_id(self.profile, hass_id) config.delete_rtm_id(self.profile, hass_id) - self.assertEqual(None, config.get_rtm_id(self.profile, hass_id)) + assert config.get_rtm_id(self.profile, hass_id) is None def test_load_key_map(self): """Test loading an existing key map from the file.""" with patch("builtins.open", mock_open(read_data=self.json_string)), \ patch("os.path.isfile", Mock(return_value=True)): config = rtm.RememberTheMilkConfiguration(self.hass) - self.assertEqual(('0', '1', '2',), - config.get_rtm_id(self.profile, "1234")) + assert ('0', '1', '2',) == \ + config.get_rtm_id(self.profile, "1234") diff --git a/tests/components/test_rfxtrx.py b/tests/components/test_rfxtrx.py index 93bf0b16dc5..d5c877e42e4 100644 --- a/tests/components/test_rfxtrx.py +++ b/tests/components/test_rfxtrx.py @@ -28,65 +28,65 @@ class TestRFXTRX(unittest.TestCase): def test_default_config(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'rfxtrx', { + assert setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', 'dummy': True} - })) + }) - self.assertTrue(setup_component(self.hass, 'sensor', { + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'automatic_add': True, - 'devices': {}}})) + 'devices': {}}}) - self.assertEqual(len(rfxtrx.RFXOBJECT.sensors()), 2) + assert len(rfxtrx.RFXOBJECT.sensors()) == 2 def test_valid_config(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'rfxtrx', { + assert setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', - 'dummy': True}})) + 'dummy': True}}) def test_valid_config2(self): """Test configuration.""" - self.assertTrue(setup_component(self.hass, 'rfxtrx', { + assert setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', 'dummy': True, - 'debug': True}})) + 'debug': True}}) def test_invalid_config(self): """Test configuration.""" - self.assertFalse(setup_component(self.hass, 'rfxtrx', { + assert not setup_component(self.hass, 'rfxtrx', { 'rfxtrx': {} - })) + }) - self.assertFalse(setup_component(self.hass, 'rfxtrx', { + assert not setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', - 'invalid_key': True}})) + 'invalid_key': True}}) def test_fire_event(self): """Test fire event.""" - self.assertTrue(setup_component(self.hass, 'rfxtrx', { + assert setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', 'dummy': True} - })) - self.assertTrue(setup_component(self.hass, 'switch', { + }) + assert setup_component(self.hass, 'switch', { 'switch': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'0b1100cd0213c7f210010f51': { 'name': 'Test', rfxtrx.ATTR_FIREEVENT: True} - }}})) + }}}) calls = [] @@ -99,9 +99,9 @@ class TestRFXTRX(unittest.TestCase): self.hass.block_till_done() entity = rfxtrx.RFX_DEVICES['213c7f216'] - self.assertEqual('Test', entity.name) - self.assertEqual('off', entity.state) - self.assertTrue(entity.should_fire_event) + assert 'Test' == entity.name + assert 'off' == entity.state + assert entity.should_fire_event event = rfxtrx.get_rfx_object('0b1100cd0213c7f210010f51') event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18, @@ -109,29 +109,29 @@ class TestRFXTRX(unittest.TestCase): rfxtrx.RECEIVED_EVT_SUBSCRIBERS[0](event) self.hass.block_till_done() - self.assertEqual(event.values['Command'], "On") - self.assertEqual('on', entity.state) - self.assertEqual(self.hass.states.get('switch.test').state, 'on') - self.assertEqual(1, len(calls)) - self.assertEqual(calls[0].data, - {'entity_id': 'switch.test', 'state': 'on'}) + assert event.values['Command'] == "On" + assert 'on' == entity.state + assert self.hass.states.get('switch.test').state == 'on' + assert 1 == len(calls) + assert calls[0].data == \ + {'entity_id': 'switch.test', 'state': 'on'} def test_fire_event_sensor(self): """Test fire event.""" - self.assertTrue(setup_component(self.hass, 'rfxtrx', { + assert setup_component(self.hass, 'rfxtrx', { 'rfxtrx': { 'device': '/dev/serial/by-id/usb' + '-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0', 'dummy': True} - })) - self.assertTrue(setup_component(self.hass, 'sensor', { + }) + assert setup_component(self.hass, 'sensor', { 'sensor': {'platform': 'rfxtrx', 'automatic_add': True, 'devices': {'0a520802060100ff0e0269': { 'name': 'Test', rfxtrx.ATTR_FIREEVENT: True} - }}})) + }}}) calls = [] @@ -147,6 +147,6 @@ class TestRFXTRX(unittest.TestCase): rfxtrx.RECEIVED_EVT_SUBSCRIBERS[0](event) self.hass.block_till_done() - self.assertEqual(1, len(calls)) - self.assertEqual(calls[0].data, - {'entity_id': 'sensor.test'}) + assert 1 == len(calls) + assert calls[0].data == \ + {'entity_id': 'sensor.test'} diff --git a/tests/components/test_ring.py b/tests/components/test_ring.py index 7b974686a4e..223f3df7077 100644 --- a/tests/components/test_ring.py +++ b/tests/components/test_ring.py @@ -47,7 +47,7 @@ class TestRing(unittest.TestCase): mock.post('https://api.ring.com/clients_api/session', text=load_fixture('ring_session.json')) response = ring.setup(self.hass, self.config) - self.assertTrue(response) + assert response @requests_mock.Mocker() def test_setup_component_no_login(self, mock): diff --git a/tests/components/test_script.py b/tests/components/test_script.py index 43727b6d559..5b7d0dfb70f 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -90,7 +90,7 @@ class TestScriptComponent(unittest.TestCase): 'script': value }), 'Script loaded with wrong config {}'.format(value) - self.assertEqual(0, len(self.hass.states.entity_ids('script'))) + assert 0 == len(self.hass.states.entity_ids('script')) def test_turn_on_service(self): """Verify that the turn_on service.""" @@ -120,18 +120,18 @@ class TestScriptComponent(unittest.TestCase): turn_on(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertTrue(script.is_on(self.hass, ENTITY_ID)) - self.assertEqual(0, len(events)) + assert script.is_on(self.hass, ENTITY_ID) + assert 0 == len(events) # Calling turn_on a second time should not advance the script turn_on(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertEqual(0, len(events)) + assert 0 == len(events) turn_off(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertFalse(script.is_on(self.hass, ENTITY_ID)) - self.assertEqual(0, len(events)) + assert not script.is_on(self.hass, ENTITY_ID) + assert 0 == len(events) state = self.hass.states.get('group.all_scripts') assert state is not None @@ -165,13 +165,13 @@ class TestScriptComponent(unittest.TestCase): toggle(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertTrue(script.is_on(self.hass, ENTITY_ID)) - self.assertEqual(0, len(events)) + assert script.is_on(self.hass, ENTITY_ID) + assert 0 == len(events) toggle(self.hass, ENTITY_ID) self.hass.block_till_done() - self.assertFalse(script.is_on(self.hass, ENTITY_ID)) - self.assertEqual(0, len(events)) + assert not script.is_on(self.hass, ENTITY_ID) + assert 0 == len(events) def test_passing_variables(self): """Test different ways of passing in variables.""" diff --git a/tests/components/test_shell_command.py b/tests/components/test_shell_command.py index e945befbb84..bab891c07e4 100644 --- a/tests/components/test_shell_command.py +++ b/tests/components/test_shell_command.py @@ -61,23 +61,21 @@ class TestShellCommand(unittest.TestCase): self.hass.services.call('shell_command', 'test_service', blocking=True) self.hass.block_till_done() - self.assertTrue(os.path.isfile(path)) + assert os.path.isfile(path) def test_config_not_dict(self): """Test that setup fails if config is not a dict.""" - self.assertFalse( - setup_component(self.hass, shell_command.DOMAIN, { + assert not setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: ['some', 'weird', 'list'] - })) + }) def test_config_not_valid_service_names(self): """Test that setup fails if config contains invalid service names.""" - self.assertFalse( - setup_component(self.hass, shell_command.DOMAIN, { + assert not setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'this is invalid because space': 'touch bla.txt' } - })) + }) @patch('homeassistant.components.shell_command.asyncio.subprocess' '.create_subprocess_shell') @@ -85,14 +83,13 @@ class TestShellCommand(unittest.TestCase): """Ensure shell_commands without templates get rendered properly.""" mock_call.return_value = mock_process_creator(error=False) - self.assertTrue( - setup_component( + assert setup_component( self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'test_service': "ls /bin" } - })) + }) self.hass.services.call('shell_command', 'test_service', blocking=True) @@ -100,8 +97,8 @@ class TestShellCommand(unittest.TestCase): self.hass.block_till_done() cmd = mock_call.mock_calls[0][1][0] - self.assertEqual(1, mock_call.call_count) - self.assertEqual('ls /bin', cmd) + assert 1 == mock_call.call_count + assert 'ls /bin' == cmd @patch('homeassistant.components.shell_command.asyncio.subprocess' '.create_subprocess_exec') @@ -109,13 +106,12 @@ class TestShellCommand(unittest.TestCase): """Ensure shell_commands with templates get rendered properly.""" self.hass.states.set('sensor.test_state', 'Works') mock_call.return_value = mock_process_creator(error=False) - self.assertTrue( - setup_component(self.hass, shell_command.DOMAIN, { + assert setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'test_service': ("ls /bin {{ states.sensor" ".test_state.state }}") } - })) + }) self.hass.services.call('shell_command', 'test_service', blocking=True) @@ -123,8 +119,8 @@ class TestShellCommand(unittest.TestCase): self.hass.block_till_done() cmd = mock_call.mock_calls[0][1] - self.assertEqual(1, mock_call.call_count) - self.assertEqual(('ls', '/bin', 'Works'), cmd) + assert 1 == mock_call.call_count + assert ('ls', '/bin', 'Works') == cmd @patch('homeassistant.components.shell_command.asyncio.subprocess' '.create_subprocess_shell') @@ -134,55 +130,52 @@ class TestShellCommand(unittest.TestCase): mock_call.return_value = mock_process_creator(error=True) with tempfile.TemporaryDirectory() as tempdirname: path = os.path.join(tempdirname, 'called.txt') - self.assertTrue( - setup_component(self.hass, shell_command.DOMAIN, { + assert setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'test_service': "touch {}".format(path) } - })) + }) self.hass.services.call('shell_command', 'test_service', blocking=True) self.hass.block_till_done() - self.assertEqual(1, mock_call.call_count) - self.assertEqual(1, mock_error.call_count) - self.assertFalse(os.path.isfile(path)) + assert 1 == mock_call.call_count + assert 1 == mock_error.call_count + assert not os.path.isfile(path) @patch('homeassistant.components.shell_command._LOGGER.debug') def test_stdout_captured(self, mock_output): """Test subprocess that has stdout.""" test_phrase = "I have output" - self.assertTrue( - setup_component(self.hass, shell_command.DOMAIN, { + assert setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'test_service': "echo {}".format(test_phrase) } - })) + }) self.hass.services.call('shell_command', 'test_service', blocking=True) self.hass.block_till_done() - self.assertEqual(1, mock_output.call_count) - self.assertEqual(test_phrase.encode() + b'\n', - mock_output.call_args_list[0][0][-1]) + assert 1 == mock_output.call_count + assert test_phrase.encode() + b'\n' == \ + mock_output.call_args_list[0][0][-1] @patch('homeassistant.components.shell_command._LOGGER.debug') def test_stderr_captured(self, mock_output): """Test subprocess that has stderr.""" test_phrase = "I have error" - self.assertTrue( - setup_component(self.hass, shell_command.DOMAIN, { + assert setup_component(self.hass, shell_command.DOMAIN, { shell_command.DOMAIN: { 'test_service': ">&2 echo {}".format(test_phrase) } - })) + }) self.hass.services.call('shell_command', 'test_service', blocking=True) self.hass.block_till_done() - self.assertEqual(1, mock_output.call_count) - self.assertEqual(test_phrase.encode() + b'\n', - mock_output.call_args_list[0][0][-1]) + assert 1 == mock_output.call_count + assert test_phrase.encode() + b'\n' == \ + mock_output.call_args_list[0][0][-1] diff --git a/tests/components/test_sleepiq.py b/tests/components/test_sleepiq.py index becf897a8e9..d3235cbd8b9 100644 --- a/tests/components/test_sleepiq.py +++ b/tests/components/test_sleepiq.py @@ -66,7 +66,7 @@ class TestSleepIQ(unittest.TestCase): json=load_fixture('sleepiq-login-failed.json')) response = sleepiq.setup(self.hass, self.config) - self.assertFalse(response) + assert not response def test_setup_component_no_login(self): """Test the setup when no login is configured.""" diff --git a/tests/components/test_splunk.py b/tests/components/test_splunk.py index 173c822ddb6..a39143f14bb 100644 --- a/tests/components/test_splunk.py +++ b/tests/components/test_splunk.py @@ -36,10 +36,10 @@ class TestSplunk(unittest.TestCase): } self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, splunk.DOMAIN, config)) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + assert setup_component(self.hass, splunk.DOMAIN, config) + assert self.hass.bus.listen.called + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[0][0][0] def test_setup_config_defaults(self): """Test setup with defaults.""" @@ -51,10 +51,10 @@ class TestSplunk(unittest.TestCase): } self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, splunk.DOMAIN, config)) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + assert setup_component(self.hass, splunk.DOMAIN, config) + assert self.hass.bus.listen.called + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[0][0][0] def _setup(self, mock_requests): """Test the setup.""" @@ -113,13 +113,11 @@ class TestSplunk(unittest.TestCase): payload = {'host': 'http://host:8088/services/collector/event', 'event': body} self.handler_method(event) - self.assertEqual(self.mock_post.call_count, 1) - self.assertEqual( - self.mock_post.call_args, + assert self.mock_post.call_count == 1 + assert self.mock_post.call_args == \ mock.call( payload['host'], data=json.dumps(payload), headers={'Authorization': 'Splunk secret'}, timeout=10 ) - ) self.mock_post.reset_mock() diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py index 6bd00e50646..c267f335b6e 100644 --- a/tests/components/test_statsd.py +++ b/tests/components/test_statsd.py @@ -10,6 +10,7 @@ import homeassistant.components.statsd as statsd from homeassistant.const import (STATE_ON, STATE_OFF, EVENT_STATE_CHANGED) from tests.common import get_test_home_assistant +import pytest class TestStatsd(unittest.TestCase): @@ -31,9 +32,9 @@ class TestStatsd(unittest.TestCase): } } - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): statsd.CONFIG_SCHEMA(None) - with self.assertRaises(vol.Invalid): + with pytest.raises(vol.Invalid): statsd.CONFIG_SCHEMA(config) @mock.patch('statsd.StatsClient') @@ -48,16 +49,14 @@ class TestStatsd(unittest.TestCase): } } self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, statsd.DOMAIN, config)) - self.assertEqual(mock_connection.call_count, 1) - self.assertEqual( - mock_connection.call_args, + assert setup_component(self.hass, statsd.DOMAIN, config) + assert mock_connection.call_count == 1 + assert mock_connection.call_args == \ mock.call(host='host', port=123, prefix='foo') - ) - self.assertTrue(self.hass.bus.listen.called) - self.assertEqual(EVENT_STATE_CHANGED, - self.hass.bus.listen.call_args_list[0][0][0]) + assert self.hass.bus.listen.called + assert EVENT_STATE_CHANGED == \ + self.hass.bus.listen.call_args_list[0][0][0] @mock.patch('statsd.StatsClient') def test_statsd_setup_defaults(self, mock_connection): @@ -72,13 +71,11 @@ class TestStatsd(unittest.TestCase): config['statsd'][statsd.CONF_PREFIX] = statsd.DEFAULT_PREFIX self.hass.bus.listen = mock.MagicMock() - self.assertTrue(setup_component(self.hass, statsd.DOMAIN, config)) - self.assertEqual(mock_connection.call_count, 1) - self.assertEqual( - mock_connection.call_args, + assert setup_component(self.hass, statsd.DOMAIN, config) + assert mock_connection.call_count == 1 + assert mock_connection.call_args == \ mock.call(host='host', port=8125, prefix='hass') - ) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called @mock.patch('statsd.StatsClient') def test_event_listener_defaults(self, mock_client): @@ -94,7 +91,7 @@ class TestStatsd(unittest.TestCase): self.hass.bus.listen = mock.MagicMock() setup_component(self.hass, statsd.DOMAIN, config) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called handler_method = self.hass.bus.listen.call_args_list[0][0][1] valid = {'1': 1, @@ -112,18 +109,16 @@ class TestStatsd(unittest.TestCase): mock_client.return_value.gauge.reset_mock() - self.assertEqual(mock_client.return_value.incr.call_count, 1) - self.assertEqual( - mock_client.return_value.incr.call_args, + assert mock_client.return_value.incr.call_count == 1 + assert mock_client.return_value.incr.call_args == \ mock.call(state.entity_id, rate=statsd.DEFAULT_RATE) - ) mock_client.return_value.incr.reset_mock() for invalid in ('foo', '', object): handler_method(mock.MagicMock(data={ 'new_state': ha.State('domain.test', invalid, {})})) - self.assertFalse(mock_client.return_value.gauge.called) - self.assertTrue(mock_client.return_value.incr.called) + assert not mock_client.return_value.gauge.called + assert mock_client.return_value.incr.called @mock.patch('statsd.StatsClient') def test_event_listener_attr_details(self, mock_client): @@ -139,7 +134,7 @@ class TestStatsd(unittest.TestCase): self.hass.bus.listen = mock.MagicMock() setup_component(self.hass, statsd.DOMAIN, config) - self.assertTrue(self.hass.bus.listen.called) + assert self.hass.bus.listen.called handler_method = self.hass.bus.listen.call_args_list[0][0][1] valid = {'1': 1, @@ -159,15 +154,13 @@ class TestStatsd(unittest.TestCase): mock_client.return_value.gauge.reset_mock() - self.assertEqual(mock_client.return_value.incr.call_count, 1) - self.assertEqual( - mock_client.return_value.incr.call_args, + assert mock_client.return_value.incr.call_count == 1 + assert mock_client.return_value.incr.call_args == \ mock.call(state.entity_id, rate=statsd.DEFAULT_RATE) - ) mock_client.return_value.incr.reset_mock() for invalid in ('foo', '', object): handler_method(mock.MagicMock(data={ 'new_state': ha.State('domain.test', invalid, {})})) - self.assertFalse(mock_client.return_value.gauge.called) - self.assertTrue(mock_client.return_value.incr.called) + assert not mock_client.return_value.gauge.called + assert mock_client.return_value.incr.called diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index aa94bf2bdd3..2833efa62c4 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -91,18 +91,18 @@ class TestSun(unittest.TestCase): break mod += 1 - self.assertEqual(next_dawn, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_DAWN])) - self.assertEqual(next_dusk, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_DUSK])) - self.assertEqual(next_midnight, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT])) - self.assertEqual(next_noon, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_NOON])) - self.assertEqual(next_rising, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_RISING])) - self.assertEqual(next_setting, dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_SETTING])) + assert next_dawn == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_DAWN]) + assert next_dusk == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_DUSK]) + assert next_midnight == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT]) + assert next_noon == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_NOON]) + assert next_rising == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_RISING]) + assert next_setting == dt_util.parse_datetime( + state.attributes[sun.STATE_ATTR_NEXT_SETTING]) def test_state_change(self): """Test if the state changes at next setting/rising.""" @@ -117,18 +117,18 @@ class TestSun(unittest.TestCase): test_time = dt_util.parse_datetime( self.hass.states.get(sun.ENTITY_ID) .attributes[sun.STATE_ATTR_NEXT_RISING]) - self.assertIsNotNone(test_time) + assert test_time is not None - self.assertEqual(sun.STATE_BELOW_HORIZON, - self.hass.states.get(sun.ENTITY_ID).state) + assert sun.STATE_BELOW_HORIZON == \ + self.hass.states.get(sun.ENTITY_ID).state self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: test_time + timedelta(seconds=5)}) self.hass.block_till_done() - self.assertEqual(sun.STATE_ABOVE_HORIZON, - self.hass.states.get(sun.ENTITY_ID).state) + assert sun.STATE_ABOVE_HORIZON == \ + self.hass.states.get(sun.ENTITY_ID).state def test_norway_in_june(self): """Test location in Norway where the sun doesn't set in summer.""" diff --git a/tests/components/test_vultr.py b/tests/components/test_vultr.py index 725768f938b..15e9864f2be 100644 --- a/tests/components/test_vultr.py +++ b/tests/components/test_vultr.py @@ -39,7 +39,7 @@ class TestVultr(unittest.TestCase): return_value=json.loads( load_fixture('vultr_server_list.json'))): response = vultr.setup(self.hass, self.config) - self.assertTrue(response) + assert response def test_setup_no_api_key(self): """Test failed setup with missing API Key.""" diff --git a/tests/components/test_weblink.py b/tests/components/test_weblink.py index 8e71c89cdd6..727db5c0127 100644 --- a/tests/components/test_weblink.py +++ b/tests/components/test_weblink.py @@ -20,15 +20,15 @@ class TestComponentWeblink(unittest.TestCase): def test_bad_config(self): """Test if new entity is created.""" - self.assertFalse(setup_component(self.hass, 'weblink', { + assert not setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [{}], } - })) + }) def test_bad_config_relative_url(self): """Test if new entity is created.""" - self.assertFalse(setup_component(self.hass, 'weblink', { + assert not setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -37,11 +37,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_bad_config_relative_file(self): """Test if new entity is created.""" - self.assertFalse(setup_component(self.hass, 'weblink', { + assert not setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -50,11 +50,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_good_config_absolute_path(self): """Test if new entity is created.""" - self.assertTrue(setup_component(self.hass, 'weblink', { + assert setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -63,11 +63,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_good_config_path_short(self): """Test if new entity is created.""" - self.assertTrue(setup_component(self.hass, 'weblink', { + assert setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -76,11 +76,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_good_config_path_directory(self): """Test if new entity is created.""" - self.assertTrue(setup_component(self.hass, 'weblink', { + assert setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -89,11 +89,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_good_config_ftp_link(self): """Test if new entity is created.""" - self.assertTrue(setup_component(self.hass, 'weblink', { + assert setup_component(self.hass, 'weblink', { 'weblink': { 'entities': [ { @@ -102,11 +102,11 @@ class TestComponentWeblink(unittest.TestCase): }, ], } - })) + }) def test_entities_get_created(self): """Test if new entity is created.""" - self.assertTrue(setup_component(self.hass, weblink.DOMAIN, { + assert setup_component(self.hass, weblink.DOMAIN, { weblink.DOMAIN: { 'entities': [ { @@ -115,7 +115,7 @@ class TestComponentWeblink(unittest.TestCase): }, ] } - })) + }) state = self.hass.states.get('weblink.my_router') diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 5b36273f046..afd2b1412dc 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -43,8 +43,7 @@ class TestTimer(unittest.TestCase): ] for cfg in invalid_configs: - self.assertFalse( - setup_component(self.hass, DOMAIN, {DOMAIN: cfg})) + assert not setup_component(self.hass, DOMAIN, {DOMAIN: cfg}) def test_config_options(self): """Test configuration options.""" @@ -66,24 +65,24 @@ class TestTimer(unittest.TestCase): assert setup_component(self.hass, 'timer', config) self.hass.block_till_done() - self.assertEqual(count_start + 2, len(self.hass.states.entity_ids())) + assert count_start + 2 == len(self.hass.states.entity_ids()) self.hass.block_till_done() state_1 = self.hass.states.get('timer.test_1') state_2 = self.hass.states.get('timer.test_2') - self.assertIsNotNone(state_1) - self.assertIsNotNone(state_2) + assert state_1 is not None + assert state_2 is not None - self.assertEqual(STATUS_IDLE, state_1.state) - self.assertNotIn(ATTR_ICON, state_1.attributes) - self.assertNotIn(ATTR_FRIENDLY_NAME, state_1.attributes) + assert STATUS_IDLE == state_1.state + assert ATTR_ICON not in state_1.attributes + assert ATTR_FRIENDLY_NAME not in state_1.attributes - self.assertEqual(STATUS_IDLE, state_2.state) - self.assertEqual('Hello World', - state_2.attributes.get(ATTR_FRIENDLY_NAME)) - self.assertEqual('mdi:work', state_2.attributes.get(ATTR_ICON)) - self.assertEqual('0:00:10', state_2.attributes.get(ATTR_DURATION)) + assert STATUS_IDLE == state_2.state + assert 'Hello World' == \ + state_2.attributes.get(ATTR_FRIENDLY_NAME) + assert 'mdi:work' == state_2.attributes.get(ATTR_ICON) + assert '0:00:10' == state_2.attributes.get(ATTR_DURATION) @asyncio.coroutine diff --git a/tests/components/vacuum/test_demo.py b/tests/components/vacuum/test_demo.py index f88908ecc41..e0b560bbb48 100644 --- a/tests/components/vacuum/test_demo.py +++ b/tests/components/vacuum/test_demo.py @@ -34,8 +34,8 @@ class TestVacuumDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(setup_component( - self.hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: 'demo'}})) + assert setup_component( + self.hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: 'demo'}}) def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" @@ -44,243 +44,243 @@ class TestVacuumDemo(unittest.TestCase): def test_supported_features(self): """Test vacuum supported features.""" state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertEqual(2047, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual("Charging", state.attributes.get(ATTR_STATUS)) - self.assertEqual(100, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual("medium", state.attributes.get(ATTR_FAN_SPEED)) - self.assertListEqual(FAN_SPEEDS, - state.attributes.get(ATTR_FAN_SPEED_LIST)) - self.assertEqual(STATE_OFF, state.state) + assert 2047 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert "Charging" == state.attributes.get(ATTR_STATUS) + assert 100 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert "medium" == state.attributes.get(ATTR_FAN_SPEED) + assert FAN_SPEEDS == \ + state.attributes.get(ATTR_FAN_SPEED_LIST) + assert STATE_OFF == state.state state = self.hass.states.get(ENTITY_VACUUM_MOST) - self.assertEqual(219, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual("Charging", state.attributes.get(ATTR_STATUS)) - self.assertEqual(100, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) - self.assertEqual(STATE_OFF, state.state) + assert 219 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert "Charging" == state.attributes.get(ATTR_STATUS) + assert 100 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert state.attributes.get(ATTR_FAN_SPEED) is None + assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None + assert STATE_OFF == state.state state = self.hass.states.get(ENTITY_VACUUM_BASIC) - self.assertEqual(195, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual("Charging", state.attributes.get(ATTR_STATUS)) - self.assertEqual(100, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) - self.assertEqual(STATE_OFF, state.state) + assert 195 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert "Charging" == state.attributes.get(ATTR_STATUS) + assert 100 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert state.attributes.get(ATTR_FAN_SPEED) is None + assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None + assert STATE_OFF == state.state state = self.hass.states.get(ENTITY_VACUUM_MINIMAL) - self.assertEqual(3, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual(None, state.attributes.get(ATTR_STATUS)) - self.assertEqual(None, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) - self.assertEqual(STATE_OFF, state.state) + assert 3 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get(ATTR_STATUS) is None + assert state.attributes.get(ATTR_BATTERY_LEVEL) is None + assert state.attributes.get(ATTR_FAN_SPEED) is None + assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None + assert STATE_OFF == state.state state = self.hass.states.get(ENTITY_VACUUM_NONE) - self.assertEqual(0, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual(None, state.attributes.get(ATTR_STATUS)) - self.assertEqual(None, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED)) - self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) - self.assertEqual(STATE_OFF, state.state) + assert 0 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get(ATTR_STATUS) is None + assert state.attributes.get(ATTR_BATTERY_LEVEL) is None + assert state.attributes.get(ATTR_FAN_SPEED) is None + assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None + assert STATE_OFF == state.state state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(13436, state.attributes.get(ATTR_SUPPORTED_FEATURES)) - self.assertEqual(STATE_DOCKED, state.state) - self.assertEqual(100, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual("medium", state.attributes.get(ATTR_FAN_SPEED)) - self.assertListEqual(FAN_SPEEDS, - state.attributes.get(ATTR_FAN_SPEED_LIST)) + assert 13436 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert STATE_DOCKED == state.state + assert 100 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert "medium" == state.attributes.get(ATTR_FAN_SPEED) + assert FAN_SPEEDS == \ + state.attributes.get(ATTR_FAN_SPEED_LIST) def test_methods(self): """Test if methods call the services as expected.""" self.hass.states.set(ENTITY_VACUUM_BASIC, STATE_ON) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC) self.hass.states.set(ENTITY_VACUUM_BASIC, STATE_OFF) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC) self.hass.states.set(ENTITY_ID_ALL_VACUUMS, STATE_ON) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass)) + assert vacuum.is_on(self.hass) self.hass.states.set(ENTITY_ID_ALL_VACUUMS, STATE_OFF) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass)) + assert not vacuum.is_on(self.hass) common.turn_on(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.turn_off(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.toggle(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.start_pause(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.start_pause(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.stop(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertLess(state.attributes.get(ATTR_BATTERY_LEVEL), 100) - self.assertNotEqual("Charging", state.attributes.get(ATTR_STATUS)) + assert state.attributes.get(ATTR_BATTERY_LEVEL) < 100 + assert "Charging" != state.attributes.get(ATTR_STATUS) common.locate(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertIn("I'm over here", state.attributes.get(ATTR_STATUS)) + assert "I'm over here" in state.attributes.get(ATTR_STATUS) common.return_to_base(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertIn("Returning home", state.attributes.get(ATTR_STATUS)) + assert "Returning home" in state.attributes.get(ATTR_STATUS) common.set_fan_speed(self.hass, FAN_SPEEDS[-1], entity_id=ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED)) + assert FAN_SPEEDS[-1] == state.attributes.get(ATTR_FAN_SPEED) common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertIn("spot", state.attributes.get(ATTR_STATUS)) - self.assertEqual(STATE_ON, state.state) + assert "spot" in state.attributes.get(ATTR_STATUS) + assert STATE_ON == state.state common.start(self.hass, ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(STATE_CLEANING, state.state) + assert STATE_CLEANING == state.state common.pause(self.hass, ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(STATE_PAUSED, state.state) + assert STATE_PAUSED == state.state common.stop(self.hass, ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(STATE_IDLE, state.state) + assert STATE_IDLE == state.state state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertLess(state.attributes.get(ATTR_BATTERY_LEVEL), 100) - self.assertNotEqual(STATE_DOCKED, state.state) + assert state.attributes.get(ATTR_BATTERY_LEVEL) < 100 + assert STATE_DOCKED != state.state common.return_to_base(self.hass, ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(STATE_RETURNING, state.state) + assert STATE_RETURNING == state.state common.set_fan_speed(self.hass, FAN_SPEEDS[-1], entity_id=ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED)) + assert FAN_SPEEDS[-1] == state.attributes.get(ATTR_FAN_SPEED) common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(STATE_CLEANING, state.state) + assert STATE_CLEANING == state.state def test_unsupported_methods(self): """Test service calls for unsupported vacuums.""" self.hass.states.set(ENTITY_VACUUM_NONE, STATE_ON) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) common.turn_off(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) common.stop(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) self.hass.states.set(ENTITY_VACUUM_NONE, STATE_OFF) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) common.turn_on(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) common.toggle(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) # Non supported methods: common.start_pause(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_NONE) common.locate(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_NONE) - self.assertIsNone(state.attributes.get(ATTR_STATUS)) + assert state.attributes.get(ATTR_STATUS) is None common.return_to_base(self.hass, ENTITY_VACUUM_NONE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_NONE) - self.assertIsNone(state.attributes.get(ATTR_STATUS)) + assert state.attributes.get(ATTR_STATUS) is None common.set_fan_speed(self.hass, FAN_SPEEDS[-1], entity_id=ENTITY_VACUUM_NONE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_NONE) - self.assertNotEqual(FAN_SPEEDS[-1], - state.attributes.get(ATTR_FAN_SPEED)) + assert FAN_SPEEDS[-1] != \ + state.attributes.get(ATTR_FAN_SPEED) common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_BASIC) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_BASIC) - self.assertNotIn("spot", state.attributes.get(ATTR_STATUS)) - self.assertEqual(STATE_OFF, state.state) + assert "spot" not in state.attributes.get(ATTR_STATUS) + assert STATE_OFF == state.state # VacuumDevice should not support start and pause methods. self.hass.states.set(ENTITY_VACUUM_COMPLETE, STATE_ON) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.pause(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.states.set(ENTITY_VACUUM_COMPLETE, STATE_OFF) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) common.start(self.hass, ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE)) + assert not vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE) # StateVacuumDevice does not support on/off common.turn_on(self.hass, entity_id=ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertNotEqual(STATE_CLEANING, state.state) + assert STATE_CLEANING != state.state common.turn_off(self.hass, entity_id=ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertNotEqual(STATE_RETURNING, state.state) + assert STATE_RETURNING != state.state common.toggle(self.hass, entity_id=ENTITY_VACUUM_STATE) self.hass.block_till_done() state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertNotEqual(STATE_CLEANING, state.state) + assert STATE_CLEANING != state.state def test_services(self): """Test vacuum services.""" @@ -294,14 +294,14 @@ class TestVacuumDemo(unittest.TestCase): params=params) self.hass.block_till_done() - self.assertEqual(1, len(send_command_calls)) + assert 1 == len(send_command_calls) call = send_command_calls[-1] - self.assertEqual(DOMAIN, call.domain) - self.assertEqual(SERVICE_SEND_COMMAND, call.service) - self.assertEqual(ENTITY_VACUUM_BASIC, call.data[ATTR_ENTITY_ID]) - self.assertEqual('test_command', call.data[ATTR_COMMAND]) - self.assertEqual(params, call.data[ATTR_PARAMS]) + assert DOMAIN == call.domain + assert SERVICE_SEND_COMMAND == call.service + assert ENTITY_VACUUM_BASIC == call.data[ATTR_ENTITY_ID] + assert 'test_command' == call.data[ATTR_COMMAND] + assert params == call.data[ATTR_PARAMS] # Test set fan speed set_fan_speed_calls = mock_service( @@ -311,13 +311,13 @@ class TestVacuumDemo(unittest.TestCase): self.hass, FAN_SPEEDS[0], entity_id=ENTITY_VACUUM_COMPLETE) self.hass.block_till_done() - self.assertEqual(1, len(set_fan_speed_calls)) + assert 1 == len(set_fan_speed_calls) call = set_fan_speed_calls[-1] - self.assertEqual(DOMAIN, call.domain) - self.assertEqual(SERVICE_SET_FAN_SPEED, call.service) - self.assertEqual(ENTITY_VACUUM_COMPLETE, call.data[ATTR_ENTITY_ID]) - self.assertEqual(FAN_SPEEDS[0], call.data[ATTR_FAN_SPEED]) + assert DOMAIN == call.domain + assert SERVICE_SET_FAN_SPEED == call.service + assert ENTITY_VACUUM_COMPLETE == call.data[ATTR_ENTITY_ID] + assert FAN_SPEEDS[0] == call.data[ATTR_FAN_SPEED] def test_set_fan_speed(self): """Test vacuum service to set the fan speed.""" @@ -336,20 +336,20 @@ class TestVacuumDemo(unittest.TestCase): new_state_complete = self.hass.states.get(ENTITY_VACUUM_COMPLETE) new_state_state = self.hass.states.get(ENTITY_VACUUM_STATE) - self.assertEqual(old_state_basic, new_state_basic) - self.assertNotIn(ATTR_FAN_SPEED, new_state_basic.attributes) + assert old_state_basic == new_state_basic + assert ATTR_FAN_SPEED not in new_state_basic.attributes - self.assertNotEqual(old_state_complete, new_state_complete) - self.assertEqual(FAN_SPEEDS[1], - old_state_complete.attributes[ATTR_FAN_SPEED]) - self.assertEqual(FAN_SPEEDS[0], - new_state_complete.attributes[ATTR_FAN_SPEED]) + assert old_state_complete != new_state_complete + assert FAN_SPEEDS[1] == \ + old_state_complete.attributes[ATTR_FAN_SPEED] + assert FAN_SPEEDS[0] == \ + new_state_complete.attributes[ATTR_FAN_SPEED] - self.assertNotEqual(old_state_state, new_state_state) - self.assertEqual(FAN_SPEEDS[1], - old_state_state.attributes[ATTR_FAN_SPEED]) - self.assertEqual(FAN_SPEEDS[0], - new_state_state.attributes[ATTR_FAN_SPEED]) + assert old_state_state != new_state_state + assert FAN_SPEEDS[1] == \ + old_state_state.attributes[ATTR_FAN_SPEED] + assert FAN_SPEEDS[0] == \ + new_state_state.attributes[ATTR_FAN_SPEED] def test_send_command(self): """Test vacuum service to send a command.""" @@ -366,8 +366,8 @@ class TestVacuumDemo(unittest.TestCase): new_state_basic = self.hass.states.get(ENTITY_VACUUM_BASIC) new_state_complete = self.hass.states.get(ENTITY_VACUUM_COMPLETE) - self.assertEqual(old_state_basic, new_state_basic) - self.assertNotEqual(old_state_complete, new_state_complete) - self.assertEqual(STATE_ON, new_state_complete.state) - self.assertEqual("Executing test_command({'p1': 3})", - new_state_complete.attributes[ATTR_STATUS]) + assert old_state_basic == new_state_basic + assert old_state_complete != new_state_complete + assert STATE_ON == new_state_complete.state + assert "Executing test_command({'p1': 3})" == \ + new_state_complete.attributes[ATTR_STATUS] diff --git a/tests/components/vacuum/test_dyson.py b/tests/components/vacuum/test_dyson.py index e9e6aaa1b35..0bdaa619c7b 100644 --- a/tests/components/vacuum/test_dyson.py +++ b/tests/components/vacuum/test_dyson.py @@ -100,13 +100,13 @@ class DysonTest(unittest.TestCase): component.entity_id = "entity_id" component.schedule_update_ha_state = mock.Mock() component.on_message(mock.Mock()) - self.assertTrue(component.schedule_update_ha_state.called) + assert component.schedule_update_ha_state.called def test_should_poll(self): """Test polling is disable.""" device = _get_vacuum_device_cleaning() component = Dyson360EyeDevice(device) - self.assertFalse(component.should_poll) + assert not component.should_poll def test_properties(self): """Test component properties.""" @@ -116,45 +116,45 @@ class DysonTest(unittest.TestCase): component = Dyson360EyeDevice(device1) component2 = Dyson360EyeDevice(device2) component3 = Dyson360EyeDevice(device3) - self.assertEqual(component.name, "Device_Vacuum") - self.assertTrue(component.is_on) - self.assertEqual(component.status, "Cleaning") - self.assertEqual(component2.status, "Unknown") - self.assertEqual(component.battery_level, 85) - self.assertEqual(component.fan_speed, "Quiet") - self.assertEqual(component.fan_speed_list, ["Quiet", "Max"]) - self.assertEqual(component.device_state_attributes['position'], - '(0, 0)') - self.assertTrue(component.available) - self.assertEqual(component.supported_features, 255) - self.assertEqual(component.battery_icon, "mdi:battery-80") - self.assertEqual(component3.battery_icon, "mdi:battery-charging-40") + assert component.name == "Device_Vacuum" + assert component.is_on + assert component.status == "Cleaning" + assert component2.status == "Unknown" + assert component.battery_level == 85 + assert component.fan_speed == "Quiet" + assert component.fan_speed_list == ["Quiet", "Max"] + assert component.device_state_attributes['position'] == \ + '(0, 0)' + assert component.available + assert component.supported_features == 255 + assert component.battery_icon == "mdi:battery-80" + assert component3.battery_icon == "mdi:battery-charging-40" def test_turn_on(self): """Test turn on vacuum.""" device1 = _get_vacuum_device_charging() component1 = Dyson360EyeDevice(device1) component1.turn_on() - self.assertTrue(device1.start.called) + assert device1.start.called device2 = _get_vacuum_device_pause() component2 = Dyson360EyeDevice(device2) component2.turn_on() - self.assertTrue(device2.resume.called) + assert device2.resume.called def test_turn_off(self): """Test turn off vacuum.""" device1 = _get_vacuum_device_cleaning() component1 = Dyson360EyeDevice(device1) component1.turn_off() - self.assertTrue(device1.pause.called) + assert device1.pause.called def test_stop(self): """Test stop vacuum.""" device1 = _get_vacuum_device_cleaning() component1 = Dyson360EyeDevice(device1) component1.stop() - self.assertTrue(device1.pause.called) + assert device1.pause.called def test_set_fan_speed(self): """Test set fan speed vacuum.""" @@ -168,21 +168,21 @@ class DysonTest(unittest.TestCase): device1 = _get_vacuum_device_charging() component1 = Dyson360EyeDevice(device1) component1.start_pause() - self.assertTrue(device1.start.called) + assert device1.start.called device2 = _get_vacuum_device_pause() component2 = Dyson360EyeDevice(device2) component2.start_pause() - self.assertTrue(device2.resume.called) + assert device2.resume.called device3 = _get_vacuum_device_cleaning() component3 = Dyson360EyeDevice(device3) component3.start_pause() - self.assertTrue(device3.pause.called) + assert device3.pause.called def test_return_to_base(self): """Test return to base.""" device = _get_vacuum_device_pause() component = Dyson360EyeDevice(device) component.return_to_base() - self.assertTrue(device.abort.called) + assert device.abort.called diff --git a/tests/components/vacuum/test_mqtt.py b/tests/components/vacuum/test_mqtt.py index ddd4289c24d..ba2c1866807 100644 --- a/tests/components/vacuum/test_mqtt.py +++ b/tests/components/vacuum/test_mqtt.py @@ -51,25 +51,25 @@ class TestVacuumMQTT(unittest.TestCase): def test_default_supported_features(self): """Test that the correct supported features.""" - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) entity = self.hass.states.get('vacuum.mqtttest') entity_features = \ entity.attributes.get(mqtt.CONF_SUPPORTED_FEATURES, 0) - self.assertListEqual(sorted(mqtt.services_to_strings(entity_features)), - sorted(['turn_on', 'turn_off', 'stop', - 'return_home', 'battery', 'status', - 'clean_spot'])) + assert sorted(mqtt.services_to_strings(entity_features)) == \ + sorted(['turn_on', 'turn_off', 'stop', + 'return_home', 'battery', 'status', + 'clean_spot']) def test_all_commands(self): """Test simple commands to the vacuum.""" self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \ mqtt.services_to_strings(mqtt.ALL_SERVICES) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) common.turn_on(self.hass, 'vacuum.mqtttest') self.hass.block_till_done() @@ -129,9 +129,9 @@ class TestVacuumMQTT(unittest.TestCase): self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \ mqtt.services_to_strings(mqtt.ALL_SERVICES) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) message = """{ "battery_level": 54, @@ -143,13 +143,11 @@ class TestVacuumMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'vacuum/state', message) self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_ON, state.state) - self.assertEqual( - 'mdi:battery-50', + assert STATE_ON == state.state + assert 'mdi:battery-50' == \ state.attributes.get(ATTR_BATTERY_ICON) - ) - self.assertEqual(54, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual('max', state.attributes.get(ATTR_FAN_SPEED)) + assert 54 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert 'max' == state.attributes.get(ATTR_FAN_SPEED) message = """{ "battery_level": 61, @@ -162,13 +160,11 @@ class TestVacuumMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'vacuum/state', message) self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual( - 'mdi:battery-charging-60', + assert STATE_OFF == state.state + assert 'mdi:battery-charging-60' == \ state.attributes.get(ATTR_BATTERY_ICON) - ) - self.assertEqual(61, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual('min', state.attributes.get(ATTR_FAN_SPEED)) + assert 61 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert 'min' == state.attributes.get(ATTR_FAN_SPEED) def test_battery_template(self): """Test that you can use non-default templates for battery_level.""" @@ -179,31 +175,31 @@ class TestVacuumMQTT(unittest.TestCase): mqtt.CONF_BATTERY_LEVEL_TEMPLATE: "{{ value }}" }) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) fire_mqtt_message(self.hass, 'retroroomba/battery_level', '54') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(54, state.attributes.get(ATTR_BATTERY_LEVEL)) - self.assertEqual(state.attributes.get(ATTR_BATTERY_ICON), - 'mdi:battery-50') + assert 54 == state.attributes.get(ATTR_BATTERY_LEVEL) + assert state.attributes.get(ATTR_BATTERY_ICON) == \ + 'mdi:battery-50' def test_status_invalid_json(self): """Test to make sure nothing breaks if the vacuum sends bad JSON.""" self.default_config[mqtt.CONF_SUPPORTED_FEATURES] = \ mqtt.services_to_strings(mqtt.ALL_SERVICES) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) fire_mqtt_message(self.hass, 'vacuum/state', '{"asdfasas false}') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual("Stopped", state.attributes.get(ATTR_STATUS)) + assert STATE_OFF == state.state + assert "Stopped" == state.attributes.get(ATTR_STATUS) def test_default_availability_payload(self): """Test availability by default payload with defined topic.""" @@ -211,24 +207,24 @@ class TestVacuumMQTT(unittest.TestCase): 'availability_topic': 'availability-topic' }) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'online') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'offline') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state def test_custom_availability_payload(self): """Test availability by custom payload with defined topic.""" @@ -238,21 +234,21 @@ class TestVacuumMQTT(unittest.TestCase): 'payload_not_available': 'nogood' }) - self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, { + assert setup_component(self.hass, vacuum.DOMAIN, { vacuum.DOMAIN: self.default_config, - })) + }) state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state fire_mqtt_message(self.hass, 'availability-topic', 'good') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE != state.state fire_mqtt_message(self.hass, 'availability-topic', 'nogood') self.hass.block_till_done() state = self.hass.states.get('vacuum.mqtttest') - self.assertEqual(STATE_UNAVAILABLE, state.state) + assert STATE_UNAVAILABLE == state.state diff --git a/tests/components/water_heater/test_demo.py b/tests/components/water_heater/test_demo.py index 14fe57de99c..66116db8cda 100644 --- a/tests/components/water_heater/test_demo.py +++ b/tests/components/water_heater/test_demo.py @@ -22,10 +22,10 @@ class TestDemowater_heater(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = IMPERIAL_SYSTEM - self.assertTrue(setup_component(self.hass, water_heater.DOMAIN, { + assert setup_component(self.hass, water_heater.DOMAIN, { 'water_heater': { 'platform': 'demo', - }})) + }}) def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" @@ -34,32 +34,32 @@ class TestDemowater_heater(unittest.TestCase): def test_setup_params(self): """Test the initial parameters.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual(119, state.attributes.get('temperature')) - self.assertEqual('off', state.attributes.get('away_mode')) - self.assertEqual("eco", state.attributes.get('operation_mode')) + assert 119 == state.attributes.get('temperature') + assert 'off' == state.attributes.get('away_mode') + assert "eco" == state.attributes.get('operation_mode') def test_default_setup_params(self): """Test the setup with default parameters.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual(110, state.attributes.get('min_temp')) - self.assertEqual(140, state.attributes.get('max_temp')) + assert 110 == state.attributes.get('min_temp') + assert 140 == state.attributes.get('max_temp') def test_set_only_target_temp_bad_attr(self): """Test setting the target temperature without required attribute.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual(119, state.attributes.get('temperature')) + assert 119 == state.attributes.get('temperature') common.set_temperature(self.hass, None, ENTITY_WATER_HEATER) self.hass.block_till_done() - self.assertEqual(119, state.attributes.get('temperature')) + assert 119 == state.attributes.get('temperature') def test_set_only_target_temp(self): """Test the setting of the target temperature.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual(119, state.attributes.get('temperature')) + assert 119 == state.attributes.get('temperature') common.set_temperature(self.hass, 110, ENTITY_WATER_HEATER) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual(110, state.attributes.get('temperature')) + assert 110 == state.attributes.get('temperature') def test_set_operation_bad_attr_and_state(self): """Test setting operation mode without required attribute. @@ -67,52 +67,52 @@ class TestDemowater_heater(unittest.TestCase): Also check the state. """ state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual("eco", state.attributes.get('operation_mode')) - self.assertEqual("eco", state.state) + assert "eco" == state.attributes.get('operation_mode') + assert "eco" == state.state common.set_operation_mode(self.hass, None, ENTITY_WATER_HEATER) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual("eco", state.attributes.get('operation_mode')) - self.assertEqual("eco", state.state) + assert "eco" == state.attributes.get('operation_mode') + assert "eco" == state.state def test_set_operation(self): """Test setting of new operation mode.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual("eco", state.attributes.get('operation_mode')) - self.assertEqual("eco", state.state) + assert "eco" == state.attributes.get('operation_mode') + assert "eco" == state.state common.set_operation_mode(self.hass, "electric", ENTITY_WATER_HEATER) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual("electric", state.attributes.get('operation_mode')) - self.assertEqual("electric", state.state) + assert "electric" == state.attributes.get('operation_mode') + assert "electric" == state.state def test_set_away_mode_bad_attr(self): """Test setting the away mode without required attribute.""" state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') common.set_away_mode(self.hass, None, ENTITY_WATER_HEATER) self.hass.block_till_done() - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') def test_set_away_mode_on(self): """Test setting the away mode on/true.""" common.set_away_mode(self.hass, True, ENTITY_WATER_HEATER) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER) - self.assertEqual('on', state.attributes.get('away_mode')) + assert 'on' == state.attributes.get('away_mode') def test_set_away_mode_off(self): """Test setting the away mode off/false.""" common.set_away_mode(self.hass, False, ENTITY_WATER_HEATER_CELSIUS) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER_CELSIUS) - self.assertEqual('off', state.attributes.get('away_mode')) + assert 'off' == state.attributes.get('away_mode') def test_set_only_target_temp_with_convert(self): """Test the setting of the target temperature.""" state = self.hass.states.get(ENTITY_WATER_HEATER_CELSIUS) - self.assertEqual(113, state.attributes.get('temperature')) + assert 113 == state.attributes.get('temperature') common.set_temperature(self.hass, 114, ENTITY_WATER_HEATER_CELSIUS) self.hass.block_till_done() state = self.hass.states.get(ENTITY_WATER_HEATER_CELSIUS) - self.assertEqual(114, state.attributes.get('temperature')) + assert 114 == state.attributes.get('temperature') diff --git a/tests/components/weather/test_darksky.py b/tests/components/weather/test_darksky.py index 5423943e6fd..8530b2ff4f1 100644 --- a/tests/components/weather/test_darksky.py +++ b/tests/components/weather/test_darksky.py @@ -36,16 +36,16 @@ class TestDarkSky(unittest.TestCase): mock_req.get(re.compile(uri), text=load_fixture('darksky.json')) - self.assertTrue(setup_component(self.hass, weather.DOMAIN, { + assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'name': 'test', 'platform': 'darksky', 'api_key': 'foo', } - })) + }) - self.assertTrue(mock_get_forecast.called) - self.assertEqual(mock_get_forecast.call_count, 1) + assert mock_get_forecast.called + assert mock_get_forecast.call_count == 1 state = self.hass.states.get('weather.test') - self.assertEqual(state.state, 'sunny') + assert state.state == 'sunny' diff --git a/tests/components/weather/test_ipma.py b/tests/components/weather/test_ipma.py index d438e118573..c7c89ecdbdb 100644 --- a/tests/components/weather/test_ipma.py +++ b/tests/components/weather/test_ipma.py @@ -66,20 +66,20 @@ class TestIPMA(unittest.TestCase): @patch("pyipma.Station", new=MockStation) def test_setup(self, mock_pyipma): """Test for successfully setting up the IPMA platform.""" - self.assertTrue(setup_component(self.hass, weather.DOMAIN, { + assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'name': 'HomeTown', 'platform': 'ipma', } - })) + }) state = self.hass.states.get('weather.hometown') - self.assertEqual(state.state, 'rainy') + assert state.state == 'rainy' data = state.attributes - self.assertEqual(data.get(ATTR_WEATHER_TEMPERATURE), 18.0) - self.assertEqual(data.get(ATTR_WEATHER_HUMIDITY), 71) - self.assertEqual(data.get(ATTR_WEATHER_PRESSURE), 1000.0) - self.assertEqual(data.get(ATTR_WEATHER_WIND_SPEED), 3.94) - self.assertEqual(data.get(ATTR_WEATHER_WIND_BEARING), 'NW') - self.assertEqual(state.attributes.get('friendly_name'), 'HomeTown') + assert data.get(ATTR_WEATHER_TEMPERATURE) == 18.0 + assert data.get(ATTR_WEATHER_HUMIDITY) == 71 + assert data.get(ATTR_WEATHER_PRESSURE) == 1000.0 + assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94 + assert data.get(ATTR_WEATHER_WIND_BEARING) == 'NW' + assert state.attributes.get('friendly_name') == 'HomeTown' diff --git a/tests/components/weather/test_weather.py b/tests/components/weather/test_weather.py index 42b1dacc5f8..ba8caee049c 100644 --- a/tests/components/weather/test_weather.py +++ b/tests/components/weather/test_weather.py @@ -20,11 +20,11 @@ class TestWeather(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - self.assertTrue(setup_component(self.hass, weather.DOMAIN, { + assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'platform': 'demo', } - })) + }) def tearDown(self): """Stop down everything that was started.""" diff --git a/tests/components/weather/test_yweather.py b/tests/components/weather/test_yweather.py index a808eb4e468..6738d1cd92e 100644 --- a/tests/components/weather/test_yweather.py +++ b/tests/components/weather/test_yweather.py @@ -97,12 +97,11 @@ class TestWeather(unittest.TestCase): @patch('yahooweather.YahooWeather', new=YahooWeatherMock) def test_setup(self, mock_yahooweather): """Test for typical weather data attributes.""" - self.assertTrue( - setup_component(self.hass, 'weather', { + assert setup_component(self.hass, 'weather', { 'weather': { 'platform': 'yweather', } - })) + }) state = self.hass.states.get('weather.yweather') assert state is not None @@ -110,12 +109,12 @@ class TestWeather(unittest.TestCase): assert state.state == 'cloudy' data = state.attributes - self.assertEqual(data.get(ATTR_WEATHER_TEMPERATURE), 18.0) - self.assertEqual(data.get(ATTR_WEATHER_HUMIDITY), 71) - self.assertEqual(data.get(ATTR_WEATHER_PRESSURE), 1000.0) - self.assertEqual(data.get(ATTR_WEATHER_WIND_SPEED), 3.94) - self.assertEqual(data.get(ATTR_WEATHER_WIND_BEARING), 0) - self.assertEqual(state.attributes.get('friendly_name'), 'Yweather') + assert data.get(ATTR_WEATHER_TEMPERATURE) == 18.0 + assert data.get(ATTR_WEATHER_HUMIDITY) == 71 + assert data.get(ATTR_WEATHER_PRESSURE) == 1000.0 + assert data.get(ATTR_WEATHER_WIND_SPEED) == 3.94 + assert data.get(ATTR_WEATHER_WIND_BEARING) == 0 + assert state.attributes.get('friendly_name') == 'Yweather' @MockDependency('yahooweather') @patch('yahooweather._yql_query', new=_yql_queryMock) @@ -123,13 +122,12 @@ class TestWeather(unittest.TestCase): @patch('yahooweather.YahooWeather', new=YahooWeatherMock) def test_setup_no_data(self, mock_yahooweather): """Test for note receiving data.""" - self.assertTrue( - setup_component(self.hass, 'weather', { + assert setup_component(self.hass, 'weather', { 'weather': { 'platform': 'yweather', 'woeid': '12345', } - })) + }) state = self.hass.states.get('weather.yweather') assert state is not None @@ -140,13 +138,12 @@ class TestWeather(unittest.TestCase): @patch('yahooweather.YahooWeather', new=YahooWeatherMock) def test_setup_bad_data(self, mock_yahooweather): """Test for bad forecast data.""" - self.assertTrue( - setup_component(self.hass, 'weather', { + assert setup_component(self.hass, 'weather', { 'weather': { 'platform': 'yweather', 'woeid': '123123', } - })) + }) state = self.hass.states.get('weather.yweather') assert state is None @@ -157,13 +154,12 @@ class TestWeather(unittest.TestCase): @patch('yahooweather.YahooWeather', new=YahooWeatherMock) def test_setup_condition_error(self, mock_yahooweather): """Test for bad forecast data.""" - self.assertTrue( - setup_component(self.hass, 'weather', { + assert setup_component(self.hass, 'weather', { 'weather': { 'platform': 'yweather', 'woeid': '111', } - })) + }) state = self.hass.states.get('weather.yweather') assert state is None diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index b06dfb683b7..eb28dcc0246 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -566,10 +566,8 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert values.primary is self.primary assert len(list(values)) == 3 - self.assertEqual(sorted(list(values), - key=lambda a: id(a)), - sorted([self.primary, None, None], - key=lambda a: id(a))) + assert sorted(list(values), key=lambda a: id(a)) == \ + sorted([self.primary, None, None], key=lambda a: id(a)) assert not discovery.async_load_platform.called values.check_value(self.secondary) @@ -577,10 +575,8 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert values.secondary is self.secondary assert len(list(values)) == 3 - self.assertEqual(sorted(list(values), - key=lambda a: id(a)), - sorted([self.primary, self.secondary, None], - key=lambda a: id(a))) + assert sorted(list(values), key=lambda a: id(a)) == \ + sorted([self.primary, self.secondary, None], key=lambda a: id(a)) assert discovery.async_load_platform.called assert len(discovery.async_load_platform.mock_calls) == 1 @@ -599,10 +595,9 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert values.optional is self.optional assert len(list(values)) == 3 - self.assertEqual(sorted(list(values), - key=lambda a: id(a)), - sorted([self.primary, self.secondary, self.optional], - key=lambda a: id(a))) + assert sorted(list(values), key=lambda a: id(a)) == \ + sorted([self.primary, self.secondary, self.optional], + key=lambda a: id(a)) assert not discovery.async_load_platform.called assert values._entity.value_added.called @@ -641,10 +636,9 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert values.secondary is self.secondary assert values.optional is self.optional assert len(list(values)) == 3 - self.assertEqual(sorted(list(values), - key=lambda a: id(a)), - sorted([self.primary, self.secondary, self.optional], - key=lambda a: id(a))) + assert sorted(list(values), key=lambda a: id(a)) == \ + sorted([self.primary, self.secondary, self.optional], + key=lambda a: id(a)) assert discovery.async_load_platform.called assert len(discovery.async_load_platform.mock_calls) == 1 @@ -869,8 +863,7 @@ class TestZwave(unittest.TestCase): """Test that device_config_glob preserves order.""" conf = CONFIG_SCHEMA( {'zwave': {CONF_DEVICE_CONFIG_GLOB: OrderedDict()}}) - self.assertIsInstance( - conf['zwave'][CONF_DEVICE_CONFIG_GLOB], OrderedDict) + assert isinstance(conf['zwave'][CONF_DEVICE_CONFIG_GLOB], OrderedDict) class TestZWaveServices(unittest.TestCase): @@ -1228,7 +1221,7 @@ class TestZWaveServices(unittest.TestCase): }) self.hass.block_till_done() - self.assertIn("FOUND NODE ", mock_logger.output[1]) + assert "FOUND NODE " in mock_logger.output[1] def test_set_wakeup(self): """Test zwave set_wakeup service.""" @@ -1385,9 +1378,9 @@ class TestZWaveServices(unittest.TestCase): assert node.refresh_value.called assert len(node.refresh_value.mock_calls) == 2 - self.assertEqual(sorted([node.refresh_value.mock_calls[0][1][0], - node.refresh_value.mock_calls[1][1][0]]), - sorted([value.value_id, power_value.value_id])) + assert sorted([node.refresh_value.mock_calls[0][1][0], + node.refresh_value.mock_calls[1][1][0]]) == \ + sorted([value.value_id, power_value.value_id]) def test_refresh_node(self): """Test zwave refresh_node service.""" diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index b91245d5a12..034360c6b3e 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -203,7 +203,7 @@ class TestZWaveNodeEntity(unittest.TestCase): with patch.object(self.entity, 'maybe_schedule_update') as mock: node = mock_zwave.MockNode(node_id=1024) mock_zwave.node_changed(node) - self.assertFalse(mock.called) + assert not mock.called def test_network_node_changed_from_notification(self): """Test for network_node_changed.""" @@ -215,17 +215,17 @@ class TestZWaveNodeEntity(unittest.TestCase): """Test for network_node_changed.""" with patch.object(self.entity, 'maybe_schedule_update') as mock: mock_zwave.notification(node_id=1024) - self.assertFalse(mock.called) + assert not mock.called def test_node_changed(self): """Test node_changed function.""" self.maxDiff = None - self.assertEqual( - {'node_id': self.node.node_id, - 'node_name': 'Mock Node', - 'manufacturer_name': 'Test Manufacturer', - 'product_name': 'Test Product'}, - self.entity.device_state_attributes) + assert { + 'node_id': self.node.node_id, + 'node_name': 'Mock Node', + 'manufacturer_name': 'Test Manufacturer', + 'product_name': 'Test Product' + } == self.entity.device_state_attributes self.node.get_values.return_value = { 1: mock_zwave.MockValue(data=1800) @@ -278,87 +278,86 @@ class TestZWaveNodeEntity(unittest.TestCase): "averageResponseRTT": 2443, "receivedTS": "2017-03-27 15:38:19:298 "} self.entity.node_changed() - self.assertEqual( - {'node_id': self.node.node_id, - 'node_name': 'Mock Node', - 'manufacturer_name': 'Test Manufacturer', - 'product_name': 'Test Product', - 'query_stage': 'Dynamic', - 'is_awake': True, - 'is_ready': False, - 'is_failed': False, - 'is_info_received': True, - 'max_baud_rate': 40000, - 'is_zwave_plus': False, - 'battery_level': 42, - 'wake_up_interval': 1800, - 'averageRequestRTT': 2462, - 'averageResponseRTT': 2443, - 'lastRequestRTT': 1591, - 'lastResponseRTT': 3679, - 'receivedCnt': 4, - 'receivedDups': 1, - 'receivedTS': '2017-03-27 15:38:19:298 ', - 'receivedUnsolicited': 0, - 'retries': 0, - 'sentCnt': 7, - 'sentFailed': 1, - 'sentTS': '2017-03-27 15:38:15:620 '}, - self.entity.device_state_attributes) + assert { + 'node_id': self.node.node_id, + 'node_name': 'Mock Node', + 'manufacturer_name': 'Test Manufacturer', + 'product_name': 'Test Product', + 'query_stage': 'Dynamic', + 'is_awake': True, + 'is_ready': False, + 'is_failed': False, + 'is_info_received': True, + 'max_baud_rate': 40000, + 'is_zwave_plus': False, + 'battery_level': 42, + 'wake_up_interval': 1800, + 'averageRequestRTT': 2462, + 'averageResponseRTT': 2443, + 'lastRequestRTT': 1591, + 'lastResponseRTT': 3679, + 'receivedCnt': 4, + 'receivedDups': 1, + 'receivedTS': '2017-03-27 15:38:19:298 ', + 'receivedUnsolicited': 0, + 'retries': 0, + 'sentCnt': 7, + 'sentFailed': 1, + 'sentTS': '2017-03-27 15:38:15:620 ' + } == self.entity.device_state_attributes self.node.can_wake_up_value = False self.entity.node_changed() - self.assertNotIn( - 'wake_up_interval', self.entity.device_state_attributes) + assert 'wake_up_interval' not in self.entity.device_state_attributes def test_name(self): """Test name property.""" - self.assertEqual('Mock Node', self.entity.name) + assert 'Mock Node' == self.entity.name def test_state_before_update(self): """Test state before update was called.""" - self.assertIsNone(self.entity.state) + assert self.entity.state is None def test_state_not_ready(self): """Test state property.""" self.node.is_ready = False self.entity.node_changed() - self.assertEqual('initializing', self.entity.state) + assert 'initializing' == self.entity.state self.node.is_failed = True self.node.query_stage = 'Complete' self.entity.node_changed() - self.assertEqual('dead', self.entity.state) + assert 'dead' == self.entity.state self.node.is_failed = False self.node.is_awake = False self.entity.node_changed() - self.assertEqual('sleeping', self.entity.state) + assert 'sleeping' == self.entity.state def test_state_ready(self): """Test state property.""" self.node.query_stage = 'Complete' self.node.is_ready = True self.entity.node_changed() - self.assertEqual('ready', self.entity.state) + assert 'ready' == self.entity.state self.node.is_failed = True self.entity.node_changed() - self.assertEqual('dead', self.entity.state) + assert 'dead' == self.entity.state self.node.is_failed = False self.node.is_awake = False self.entity.node_changed() - self.assertEqual('sleeping', self.entity.state) + assert 'sleeping' == self.entity.state def test_not_polled(self): """Test should_poll property.""" - self.assertFalse(self.entity.should_poll) + assert not self.entity.should_poll def test_unique_id(self): """Test unique_id.""" - self.assertEqual('node-567', self.entity.unique_id) + assert 'node-567' == self.entity.unique_id def test_unique_id_missing_data(self): """Test unique_id.""" @@ -366,4 +365,4 @@ class TestZWaveNodeEntity(unittest.TestCase): self.node.name = None entity = node_entity.ZWaveNodeEntity(self.node, self.zwave_network) - self.assertIsNone(entity.unique_id) + assert entity.unique_id is None diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index cb586698302..dd5744bbb52 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -59,23 +59,23 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(before_birthday) self.hass.block_till_done() - self.assertEqual(0, len(runs)) + assert 0 == len(runs) self._send_time_changed(birthday_paulus) self.hass.block_till_done() - self.assertEqual(1, len(runs)) + assert 1 == len(runs) # A point in time tracker will only fire once, this should do nothing self._send_time_changed(birthday_paulus) self.hass.block_till_done() - self.assertEqual(1, len(runs)) + assert 1 == len(runs) track_point_in_time( self.hass, callback(lambda x: runs.append(1)), birthday_paulus) self._send_time_changed(after_birthday) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + assert 2 == len(runs) unsub = track_point_in_time( self.hass, callback(lambda x: runs.append(1)), birthday_paulus) @@ -83,7 +83,7 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(after_birthday) self.hass.block_till_done() - self.assertEqual(2, len(runs)) + assert 2 == len(runs) def test_track_state_change(self): """Test track_state_change.""" @@ -113,56 +113,56 @@ class TestEventHelpers(unittest.TestCase): # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) - self.assertIsNone(wildcard_runs[-1][0]) - self.assertIsNotNone(wildcard_runs[-1][1]) + assert 0 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) + assert wildcard_runs[-1][0] is None + assert wildcard_runs[-1][1] is not None # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) + assert 0 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) # State change off -> on self.hass.states.set('light.Bowl', 'off') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(2, len(wildcard_runs)) - self.assertEqual(2, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 2 == len(wildcard_runs) + assert 2 == len(wildercard_runs) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(3, len(wildcard_runs)) - self.assertEqual(3, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 3 == len(wildcard_runs) + assert 3 == len(wildercard_runs) # State change off -> on self.hass.states.set('light.Bowl', 'on') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(4, len(wildcard_runs)) - self.assertEqual(4, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 4 == len(wildcard_runs) + assert 4 == len(wildercard_runs) self.hass.states.remove('light.bowl') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(5, len(wildcard_runs)) - self.assertEqual(5, len(wildercard_runs)) - self.assertIsNotNone(wildcard_runs[-1][0]) - self.assertIsNone(wildcard_runs[-1][1]) - self.assertIsNotNone(wildercard_runs[-1][0]) - self.assertIsNone(wildercard_runs[-1][1]) + assert 1 == len(specific_runs) + assert 5 == len(wildcard_runs) + assert 5 == len(wildercard_runs) + assert wildcard_runs[-1][0] is not None + assert wildcard_runs[-1][1] is None + assert wildercard_runs[-1][0] is not None + assert wildercard_runs[-1][1] is None # Set state for different entity id self.hass.states.set('switch.kitchen', 'on') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(5, len(wildcard_runs)) - self.assertEqual(6, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 5 == len(wildcard_runs) + assert 6 == len(wildercard_runs) def test_track_template(self): """Test tracking template.""" @@ -203,37 +203,37 @@ class TestEventHelpers(unittest.TestCase): self.hass.states.set('switch.test', 'on') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) self.hass.states.set('switch.test', 'on') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) self.hass.states.set('switch.test', 'off') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) self.hass.states.set('switch.test', 'off') self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) - self.assertEqual(1, len(wildercard_runs)) + assert 1 == len(specific_runs) + assert 1 == len(wildcard_runs) + assert 1 == len(wildercard_runs) self.hass.states.set('switch.test', 'on') self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) - self.assertEqual(2, len(wildcard_runs)) - self.assertEqual(2, len(wildercard_runs)) + assert 2 == len(specific_runs) + assert 2 == len(wildcard_runs) + assert 2 == len(wildercard_runs) def test_track_same_state_simple_trigger(self): """Test track_same_change with trigger simple.""" @@ -270,17 +270,17 @@ class TestEventHelpers(unittest.TestCase): # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.block_till_done() - self.assertEqual(0, len(thread_runs)) - self.assertEqual(0, len(callback_runs)) - self.assertEqual(0, len(coroutine_runs)) + assert 0 == len(thread_runs) + assert 0 == len(callback_runs) + assert 0 == len(coroutine_runs) # change time to track and see if they trigger future = dt_util.utcnow() + period fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(1, len(thread_runs)) - self.assertEqual(1, len(callback_runs)) - self.assertEqual(1, len(coroutine_runs)) + assert 1 == len(thread_runs) + assert 1 == len(callback_runs) + assert 1 == len(coroutine_runs) def test_track_same_state_simple_no_trigger(self): """Test track_same_change with no trigger.""" @@ -299,18 +299,18 @@ class TestEventHelpers(unittest.TestCase): # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.block_till_done() - self.assertEqual(0, len(callback_runs)) + assert 0 == len(callback_runs) # Change state on state machine self.hass.states.set("light.Bowl", "off") self.hass.block_till_done() - self.assertEqual(0, len(callback_runs)) + assert 0 == len(callback_runs) # change time to track and see if they trigger future = dt_util.utcnow() + period fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(0, len(callback_runs)) + assert 0 == len(callback_runs) def test_track_same_state_simple_trigger_check_funct(self): """Test track_same_change with trigger and check funct.""" @@ -334,15 +334,15 @@ class TestEventHelpers(unittest.TestCase): # Adding state to state machine self.hass.states.set("light.Bowl", "on") self.hass.block_till_done() - self.assertEqual(0, len(callback_runs)) - self.assertEqual('on', check_func[-1][2].state) - self.assertEqual('light.bowl', check_func[-1][0]) + assert 0 == len(callback_runs) + assert 'on' == check_func[-1][2].state + assert 'light.bowl' == check_func[-1][0] # change time to track and see if they trigger future = dt_util.utcnow() + period fire_time_changed(self.hass, future) self.hass.block_till_done() - self.assertEqual(1, len(callback_runs)) + assert 1 == len(callback_runs) def test_track_time_interval(self): """Test tracking time interval.""" @@ -356,21 +356,21 @@ class TestEventHelpers(unittest.TestCase): self._send_time_changed(utc_now + timedelta(seconds=5)) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) self._send_time_changed(utc_now + timedelta(seconds=13)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(utc_now + timedelta(minutes=20)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) unsub() self._send_time_changed(utc_now + timedelta(seconds=30)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) def test_track_sunrise(self): """Test track the sunrise.""" @@ -410,26 +410,26 @@ class TestEventHelpers(unittest.TestCase): # run tests self._send_time_changed(next_rising - offset) self.hass.block_till_done() - self.assertEqual(0, len(runs)) - self.assertEqual(0, len(offset_runs)) + assert 0 == len(runs) + assert 0 == len(offset_runs) self._send_time_changed(next_rising) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(0, len(offset_runs)) + assert 1 == len(runs) + assert 0 == len(offset_runs) self._send_time_changed(next_rising + offset) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(1, len(offset_runs)) + assert 1 == len(runs) + assert 1 == len(offset_runs) unsub() unsub2() self._send_time_changed(next_rising + offset) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(1, len(offset_runs)) + assert 1 == len(runs) + assert 1 == len(offset_runs) def test_track_sunset(self): """Test track the sunset.""" @@ -469,26 +469,26 @@ class TestEventHelpers(unittest.TestCase): # Run tests self._send_time_changed(next_setting - offset) self.hass.block_till_done() - self.assertEqual(0, len(runs)) - self.assertEqual(0, len(offset_runs)) + assert 0 == len(runs) + assert 0 == len(offset_runs) self._send_time_changed(next_setting) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(0, len(offset_runs)) + assert 1 == len(runs) + assert 0 == len(offset_runs) self._send_time_changed(next_setting + offset) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(1, len(offset_runs)) + assert 1 == len(runs) + assert 1 == len(offset_runs) unsub() unsub2() self._send_time_changed(next_setting + offset) self.hass.block_till_done() - self.assertEqual(1, len(runs)) - self.assertEqual(1, len(offset_runs)) + assert 1 == len(runs) + assert 1 == len(offset_runs) def _send_time_changed(self, now): """Send a time changed event.""" @@ -524,26 +524,26 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(1, len(wildcard_runs)) + assert 1 == len(specific_runs) + assert 1 == len(wildcard_runs) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) - self.assertEqual(2, len(wildcard_runs)) + assert 1 == len(specific_runs) + assert 2 == len(wildcard_runs) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) - self.assertEqual(3, len(wildcard_runs)) + assert 2 == len(specific_runs) + assert 3 == len(wildcard_runs) unsub() unsub_utc() self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) - self.assertEqual(3, len(wildcard_runs)) + assert 2 == len(specific_runs) + assert 3 == len(wildcard_runs) def test_periodic_task_minute(self): """Test periodic tasks per minute.""" @@ -555,21 +555,21 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 12, 3, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) unsub() self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) def test_periodic_task_hour(self): """Test periodic tasks per hour.""" @@ -581,29 +581,29 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 23, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 25, 0, 0, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 25, 1, 0, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) self.hass.block_till_done() - self.assertEqual(3, len(specific_runs)) + assert 3 == len(specific_runs) unsub() self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) self.hass.block_till_done() - self.assertEqual(3, len(specific_runs)) + assert 3 == len(specific_runs) def test_periodic_task_wrong_input(self): """Test periodic tasks with wrong input.""" @@ -615,7 +615,7 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) def test_periodic_task_clock_rollback(self): """Test periodic tasks with the time rolling backwards.""" @@ -627,29 +627,29 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 23, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 0, 0, 0)) self.hass.block_till_done() - self.assertEqual(3, len(specific_runs)) + assert 3 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) self.hass.block_till_done() - self.assertEqual(4, len(specific_runs)) + assert 4 == len(specific_runs) unsub() self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) self.hass.block_till_done() - self.assertEqual(4, len(specific_runs)) + assert 4 == len(specific_runs) def test_periodic_task_duplicate_time(self): """Test periodic tasks not triggering on duplicate time.""" @@ -661,15 +661,15 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed(datetime(2014, 5, 25, 0, 0, 0)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) unsub() @@ -686,22 +686,22 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed( tz.localize(datetime(2018, 3, 25, 1, 50, 0))) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 3, 25, 3, 50, 0))) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 3, 26, 1, 50, 0))) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 3, 26, 2, 50, 0))) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) unsub() @@ -718,22 +718,22 @@ class TestTrackTimeChange(unittest.TestCase): self._send_time_changed( tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False)) self.hass.block_till_done() - self.assertEqual(0, len(specific_runs)) + assert 0 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True)) self.hass.block_till_done() - self.assertEqual(1, len(specific_runs)) + assert 1 == len(specific_runs) self._send_time_changed( tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True)) self.hass.block_till_done() - self.assertEqual(2, len(specific_runs)) + assert 2 == len(specific_runs) unsub() diff --git a/tests/helpers/test_icon.py b/tests/helpers/test_icon.py index 29507b25cb7..417dded790a 100644 --- a/tests/helpers/test_icon.py +++ b/tests/helpers/test_icon.py @@ -9,20 +9,20 @@ class TestIconUtil(unittest.TestCase): """Test icon generator for battery sensor.""" from homeassistant.helpers.icon import icon_for_battery_level - self.assertEqual('mdi:battery-unknown', - icon_for_battery_level(None, True)) - self.assertEqual('mdi:battery-unknown', - icon_for_battery_level(None, False)) + assert 'mdi:battery-unknown' == \ + icon_for_battery_level(None, True) + assert 'mdi:battery-unknown' == \ + icon_for_battery_level(None, False) - self.assertEqual('mdi:battery-outline', - icon_for_battery_level(5, True)) - self.assertEqual('mdi:battery-alert', - icon_for_battery_level(5, False)) + assert 'mdi:battery-outline' == \ + icon_for_battery_level(5, True) + assert 'mdi:battery-alert' == \ + icon_for_battery_level(5, False) - self.assertEqual('mdi:battery-charging-100', - icon_for_battery_level(100, True)) - self.assertEqual('mdi:battery', - icon_for_battery_level(100, False)) + assert 'mdi:battery-charging-100' == \ + icon_for_battery_level(100, True) + assert 'mdi:battery' == \ + icon_for_battery_level(100, False) iconbase = 'mdi:battery' for level in range(0, 100, 5): @@ -47,7 +47,7 @@ class TestIconUtil(unittest.TestCase): postfix = '-alert' else: postfix = '' - self.assertEqual(iconbase + postfix, - icon_for_battery_level(level, False)) - self.assertEqual(iconbase + postfix_charging, - icon_for_battery_level(level, True)) + assert iconbase + postfix == \ + icon_for_battery_level(level, False) + assert iconbase + postfix_charging == \ + icon_for_battery_level(level, True) diff --git a/tests/helpers/test_init.py b/tests/helpers/test_init.py index f702c1a5dc7..6af28e686f0 100644 --- a/tests/helpers/test_init.py +++ b/tests/helpers/test_init.py @@ -31,8 +31,8 @@ class TestHelpers(unittest.TestCase): 'zone 100': None, } - self.assertEqual(set(['zone', 'zone Hallo', 'zone 100']), - set(helpers.extract_domain_configs(config, 'zone'))) + assert set(['zone', 'zone Hallo', 'zone 100']) == \ + set(helpers.extract_domain_configs(config, 'zone')) def test_config_per_platform(self): """Test config per platform method.""" diff --git a/tests/helpers/test_intent.py b/tests/helpers/test_intent.py index 707129ae531..1a5b63fbab9 100644 --- a/tests/helpers/test_intent.py +++ b/tests/helpers/test_intent.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant.core import State from homeassistant.helpers import (intent, config_validation as cv) +import pytest class MockIntentHandler(intent.IntentHandler): @@ -33,12 +34,12 @@ class TestIntentHandler(unittest.TestCase): vol.Required('name'): cv.string, }) - self.assertRaises(vol.error.MultipleInvalid, - handler1.async_validate_slots, {}) - self.assertRaises(vol.error.MultipleInvalid, - handler1.async_validate_slots, {'name': 1}) - self.assertRaises(vol.error.MultipleInvalid, - handler1.async_validate_slots, {'name': 'kitchen'}) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({}) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({'name': 1}) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({'name': 'kitchen'}) handler1.async_validate_slots({'name': {'value': 'kitchen'}}) handler1.async_validate_slots({ 'name': {'value': 'kitchen'}, diff --git a/tests/helpers/test_location.py b/tests/helpers/test_location.py index 22f69c18326..5ff7abdbcdd 100644 --- a/tests/helpers/test_location.py +++ b/tests/helpers/test_location.py @@ -12,7 +12,7 @@ class TestHelpersLocation(unittest.TestCase): def test_has_location_with_invalid_states(self): """Set up the tests.""" for state in (None, 1, "hello", object): - self.assertFalse(location.has_location(state)) + assert not location.has_location(state) def test_has_location_with_states_with_invalid_locations(self): """Set up the tests.""" @@ -20,7 +20,7 @@ class TestHelpersLocation(unittest.TestCase): ATTR_LATITUDE: 'no number', ATTR_LONGITUDE: 123.12 }) - self.assertFalse(location.has_location(state)) + assert not location.has_location(state) def test_has_location_with_states_with_valid_location(self): """Set up the tests.""" @@ -28,7 +28,7 @@ class TestHelpersLocation(unittest.TestCase): ATTR_LATITUDE: 123.12, ATTR_LONGITUDE: 123.12 }) - self.assertTrue(location.has_location(state)) + assert location.has_location(state) def test_closest_with_no_states_with_location(self): """Set up the tests.""" @@ -41,8 +41,8 @@ class TestHelpersLocation(unittest.TestCase): ATTR_LONGITUDE: 123.45, }) - self.assertIsNone( - location.closest(123.45, 123.45, [state, state2, state3])) + assert \ + location.closest(123.45, 123.45, [state, state2, state3]) is None def test_closest_returns_closest(self): """Test .""" @@ -55,5 +55,4 @@ class TestHelpersLocation(unittest.TestCase): ATTR_LONGITUDE: 125.45, }) - self.assertEqual( - state, location.closest(123.45, 123.45, [state, state2])) + assert state == location.closest(123.45, 123.45, [state, state2]) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 529804bd307..71775574c28 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -45,10 +45,10 @@ class TestServiceHelpers(unittest.TestCase): service.call_from_config(self.hass, config) self.hass.block_till_done() - self.assertEqual('goodbye', self.calls[0].data['hello']) - self.assertEqual('complex', self.calls[0].data['data']['value']) - self.assertEqual('simple', self.calls[0].data['data']['simple']) - self.assertEqual('list', self.calls[0].data['list'][0]) + assert 'goodbye' == self.calls[0].data['hello'] + assert 'complex' == self.calls[0].data['data']['value'] + assert 'simple' == self.calls[0].data['data']['simple'] + assert 'list' == self.calls[0].data['list'][0] def test_passing_variables_to_templates(self): """Test passing variables to templates.""" @@ -66,7 +66,7 @@ class TestServiceHelpers(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual('goodbye', self.calls[0].data['hello']) + assert 'goodbye' == self.calls[0].data['hello'] def test_bad_template(self): """Test passing bad template.""" @@ -84,7 +84,7 @@ class TestServiceHelpers(unittest.TestCase): }) self.hass.block_till_done() - self.assertEqual(len(self.calls), 0) + assert len(self.calls) == 0 def test_split_entity_string(self): """Test splitting of entity string.""" @@ -93,8 +93,8 @@ class TestServiceHelpers(unittest.TestCase): 'entity_id': 'hello.world, sensor.beer' }) self.hass.block_till_done() - self.assertEqual(['hello.world', 'sensor.beer'], - self.calls[-1].data.get('entity_id')) + assert ['hello.world', 'sensor.beer'] == \ + self.calls[-1].data.get('entity_id') def test_not_mutate_input(self): """Test for immutable input.""" @@ -122,15 +122,15 @@ class TestServiceHelpers(unittest.TestCase): def test_fail_silently_if_no_service(self, mock_log): """Test failing if service is missing.""" service.call_from_config(self.hass, None) - self.assertEqual(1, mock_log.call_count) + assert 1 == mock_log.call_count service.call_from_config(self.hass, {}) - self.assertEqual(2, mock_log.call_count) + assert 2 == mock_log.call_count service.call_from_config(self.hass, { 'service': 'invalid' }) - self.assertEqual(3, mock_log.call_count) + assert 3 == mock_log.call_count def test_extract_entity_ids(self): """Test extract_entity_ids method.""" @@ -144,17 +144,17 @@ class TestServiceHelpers(unittest.TestCase): call = ha.ServiceCall('light', 'turn_on', {ATTR_ENTITY_ID: 'light.Bowl'}) - self.assertEqual(['light.bowl'], - service.extract_entity_ids(self.hass, call)) + assert ['light.bowl'] == \ + service.extract_entity_ids(self.hass, call) call = ha.ServiceCall('light', 'turn_on', {ATTR_ENTITY_ID: 'group.test'}) - self.assertEqual(['light.ceiling', 'light.kitchen'], - service.extract_entity_ids(self.hass, call)) + assert ['light.ceiling', 'light.kitchen'] == \ + service.extract_entity_ids(self.hass, call) - self.assertEqual(['group.test'], service.extract_entity_ids( - self.hass, call, expand_group=False)) + assert ['group.test'] == service.extract_entity_ids( + self.hass, call, expand_group=False) @asyncio.coroutine diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index f230d03e51e..07654744492 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -21,6 +21,7 @@ from homeassistant.components.sun import (STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) from tests.common import get_test_home_assistant, mock_service +import pytest @asyncio.coroutine @@ -80,9 +81,8 @@ class TestStateHelpers(unittest.TestCase): self.hass.states.set('light.test3', 'on') state3 = self.hass.states.get('light.test3') - self.assertEqual( - [state2, state3], - state.get_changed_since([state1, state2, state3], point2)) + assert [state2, state3] == \ + state.get_changed_since([state1, state2, state3], point2) def test_reproduce_with_no_entity(self): """Test reproduce_state with no entity.""" @@ -92,8 +92,8 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) == 0) - self.assertEqual(None, self.hass.states.get('light.test')) + assert len(calls) == 0 + assert self.hass.states.get('light.test') is None def test_reproduce_turn_on(self): """Test reproduce_state with SERVICE_TURN_ON.""" @@ -105,11 +105,11 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert len(calls) > 0 last_call = calls[-1] - self.assertEqual('light', last_call.domain) - self.assertEqual(SERVICE_TURN_ON, last_call.service) - self.assertEqual(['light.test'], last_call.data.get('entity_id')) + assert 'light' == last_call.domain + assert SERVICE_TURN_ON == last_call.service + assert ['light.test'] == last_call.data.get('entity_id') def test_reproduce_turn_off(self): """Test reproduce_state with SERVICE_TURN_OFF.""" @@ -121,11 +121,11 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert 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')) + assert 'light' == last_call.domain + assert SERVICE_TURN_OFF == last_call.service + assert ['light.test'] == last_call.data.get('entity_id') def test_reproduce_complex_data(self): """Test reproduce_state with complex service data.""" @@ -141,11 +141,11 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert len(calls) > 0 last_call = calls[-1] - self.assertEqual('light', last_call.domain) - self.assertEqual(SERVICE_TURN_ON, last_call.service) - self.assertEqual(complex_data, last_call.data.get('complex')) + assert 'light' == last_call.domain + assert SERVICE_TURN_ON == last_call.service + assert complex_data == last_call.data.get('complex') def test_reproduce_media_data(self): """Test reproduce_state with SERVICE_PLAY_MEDIA.""" @@ -161,12 +161,12 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert 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')) + assert 'media_player' == last_call.domain + assert SERVICE_PLAY_MEDIA == last_call.service + assert 'movie' == last_call.data.get('media_content_type') + assert 'batman' == last_call.data.get('media_content_id') def test_reproduce_media_play(self): """Test reproduce_state with SERVICE_MEDIA_PLAY.""" @@ -179,12 +179,12 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert 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')) + assert 'media_player' == last_call.domain + assert SERVICE_MEDIA_PLAY == last_call.service + assert ['media_player.test'] == \ + last_call.data.get('entity_id') def test_reproduce_media_pause(self): """Test reproduce_state with SERVICE_MEDIA_PAUSE.""" @@ -197,12 +197,12 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) > 0) + assert 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')) + assert 'media_player' == last_call.domain + assert SERVICE_MEDIA_PAUSE == last_call.service + assert ['media_player.test'] == \ + last_call.data.get('entity_id') def test_reproduce_bad_state(self): """Test reproduce_state with bad state.""" @@ -214,8 +214,8 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(len(calls) == 0) - self.assertEqual('off', self.hass.states.get('light.test').state) + assert len(calls) == 0 + assert 'off' == self.hass.states.get('light.test').state def test_reproduce_group(self): """Test reproduce_state with group.""" @@ -228,12 +228,12 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(light_calls)) + assert 1 == len(light_calls) last_call = light_calls[-1] - self.assertEqual('light', last_call.domain) - self.assertEqual(SERVICE_TURN_ON, last_call.service) - self.assertEqual(['light.test1', 'light.test2'], - last_call.data.get('entity_id')) + assert 'light' == last_call.domain + assert SERVICE_TURN_ON == last_call.service + assert ['light.test1', 'light.test2'] == \ + last_call.data.get('entity_id') def test_reproduce_group_same_data(self): """Test reproduce_state with group with same domain and data.""" @@ -248,13 +248,13 @@ class TestStateHelpers(unittest.TestCase): self.hass.block_till_done() - self.assertEqual(1, len(light_calls)) + assert 1 == len(light_calls) last_call = light_calls[-1] - self.assertEqual('light', last_call.domain) - self.assertEqual(SERVICE_TURN_ON, last_call.service) - self.assertEqual(['light.test1', 'light.test2'], - last_call.data.get('entity_id')) - self.assertEqual(95, last_call.data.get('brightness')) + assert 'light' == last_call.domain + assert SERVICE_TURN_ON == last_call.service + assert ['light.test1', 'light.test2'] == \ + last_call.data.get('entity_id') + assert 95 == last_call.data.get('brightness') def test_as_number_states(self): """Test state_as_number with states.""" @@ -263,27 +263,24 @@ class TestStateHelpers(unittest.TestCase): one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON, STATE_HOME) for _state in zero_states: - self.assertEqual(0, state.state_as_number( - ha.State('domain.test', _state, {}))) + assert 0 == state.state_as_number( + ha.State('domain.test', _state, {})) for _state in one_states: - self.assertEqual(1, state.state_as_number( - ha.State('domain.test', _state, {}))) + assert 1 == state.state_as_number( + ha.State('domain.test', _state, {})) def test_as_number_coercion(self): """Test state_as_number with number.""" for _state in ('0', '0.0', 0, 0.0): - self.assertEqual( - 0.0, state.state_as_number( - ha.State('domain.test', _state, {}))) + assert 0.0 == state.state_as_number( + ha.State('domain.test', _state, {})) for _state in ('1', '1.0', 1, 1.0): - self.assertEqual( - 1.0, state.state_as_number( - ha.State('domain.test', _state, {}))) + assert 1.0 == state.state_as_number( + 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, - state.state_as_number, - ha.State('domain.test', _state, {})) + with pytest.raises(ValueError): + state.state_as_number(ha.State('domain.test', _state, {})) diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py index b1c7f62e776..8fb4e44b0ed 100644 --- a/tests/helpers/test_sun.py +++ b/tests/helpers/test_sun.py @@ -83,18 +83,18 @@ class TestSun(unittest.TestCase): with patch('homeassistant.helpers.condition.dt_util.utcnow', return_value=utc_now): - self.assertEqual(next_dawn, sun.get_astral_event_next( - self.hass, 'dawn')) - self.assertEqual(next_dusk, sun.get_astral_event_next( - self.hass, 'dusk')) - self.assertEqual(next_midnight, sun.get_astral_event_next( - self.hass, 'solar_midnight')) - self.assertEqual(next_noon, sun.get_astral_event_next( - self.hass, 'solar_noon')) - self.assertEqual(next_rising, sun.get_astral_event_next( - self.hass, 'sunrise')) - self.assertEqual(next_setting, sun.get_astral_event_next( - self.hass, 'sunset')) + assert next_dawn == sun.get_astral_event_next( + self.hass, 'dawn') + assert next_dusk == sun.get_astral_event_next( + self.hass, 'dusk') + assert next_midnight == sun.get_astral_event_next( + self.hass, 'solar_midnight') + assert next_noon == sun.get_astral_event_next( + self.hass, 'solar_noon') + assert next_rising == sun.get_astral_event_next( + self.hass, 'sunrise') + assert next_setting == sun.get_astral_event_next( + self.hass, 'sunset') def test_date_events(self): """Test retrieving next sun events.""" @@ -114,18 +114,18 @@ class TestSun(unittest.TestCase): sunrise = astral.sunrise_utc(utc_today, latitude, longitude) sunset = astral.sunset_utc(utc_today, latitude, longitude) - self.assertEqual(dawn, sun.get_astral_event_date( - self.hass, 'dawn', utc_today)) - self.assertEqual(dusk, sun.get_astral_event_date( - self.hass, 'dusk', utc_today)) - self.assertEqual(midnight, sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_today)) - self.assertEqual(noon, sun.get_astral_event_date( - self.hass, 'solar_noon', utc_today)) - self.assertEqual(sunrise, sun.get_astral_event_date( - self.hass, 'sunrise', utc_today)) - self.assertEqual(sunset, sun.get_astral_event_date( - self.hass, 'sunset', utc_today)) + assert dawn == sun.get_astral_event_date( + self.hass, 'dawn', utc_today) + assert dusk == sun.get_astral_event_date( + self.hass, 'dusk', utc_today) + assert midnight == sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_today) + assert noon == sun.get_astral_event_date( + self.hass, 'solar_noon', utc_today) + assert sunrise == sun.get_astral_event_date( + self.hass, 'sunrise', utc_today) + assert sunset == sun.get_astral_event_date( + self.hass, 'sunset', utc_today) def test_date_events_default_date(self): """Test retrieving next sun events.""" @@ -146,18 +146,18 @@ class TestSun(unittest.TestCase): sunset = astral.sunset_utc(utc_today, latitude, longitude) with patch('homeassistant.util.dt.now', return_value=utc_now): - self.assertEqual(dawn, sun.get_astral_event_date( - self.hass, 'dawn', utc_today)) - self.assertEqual(dusk, sun.get_astral_event_date( - self.hass, 'dusk', utc_today)) - self.assertEqual(midnight, sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_today)) - self.assertEqual(noon, sun.get_astral_event_date( - self.hass, 'solar_noon', utc_today)) - self.assertEqual(sunrise, sun.get_astral_event_date( - self.hass, 'sunrise', utc_today)) - self.assertEqual(sunset, sun.get_astral_event_date( - self.hass, 'sunset', utc_today)) + assert dawn == sun.get_astral_event_date( + self.hass, 'dawn', utc_today) + assert dusk == sun.get_astral_event_date( + self.hass, 'dusk', utc_today) + assert midnight == sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_today) + assert noon == sun.get_astral_event_date( + self.hass, 'solar_noon', utc_today) + assert sunrise == sun.get_astral_event_date( + self.hass, 'sunrise', utc_today) + assert sunset == sun.get_astral_event_date( + self.hass, 'sunset', utc_today) def test_date_events_accepts_datetime(self): """Test retrieving next sun events.""" @@ -177,30 +177,30 @@ class TestSun(unittest.TestCase): sunrise = astral.sunrise_utc(utc_today, latitude, longitude) sunset = astral.sunset_utc(utc_today, latitude, longitude) - self.assertEqual(dawn, sun.get_astral_event_date( - self.hass, 'dawn', utc_now)) - self.assertEqual(dusk, sun.get_astral_event_date( - self.hass, 'dusk', utc_now)) - self.assertEqual(midnight, sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_now)) - self.assertEqual(noon, sun.get_astral_event_date( - self.hass, 'solar_noon', utc_now)) - self.assertEqual(sunrise, sun.get_astral_event_date( - self.hass, 'sunrise', utc_now)) - self.assertEqual(sunset, sun.get_astral_event_date( - self.hass, 'sunset', utc_now)) + assert dawn == sun.get_astral_event_date( + self.hass, 'dawn', utc_now) + assert dusk == sun.get_astral_event_date( + self.hass, 'dusk', utc_now) + assert midnight == sun.get_astral_event_date( + self.hass, 'solar_midnight', utc_now) + assert noon == sun.get_astral_event_date( + self.hass, 'solar_noon', utc_now) + assert sunrise == sun.get_astral_event_date( + self.hass, 'sunrise', utc_now) + assert sunset == sun.get_astral_event_date( + self.hass, 'sunset', utc_now) def test_is_up(self): """Test retrieving next sun events.""" utc_now = datetime(2016, 11, 1, 12, 0, 0, tzinfo=dt_util.UTC) with patch('homeassistant.helpers.condition.dt_util.utcnow', return_value=utc_now): - self.assertFalse(sun.is_up(self.hass)) + assert not sun.is_up(self.hass) utc_now = datetime(2016, 11, 1, 18, 0, 0, tzinfo=dt_util.UTC) with patch('homeassistant.helpers.condition.dt_util.utcnow', return_value=utc_now): - self.assertTrue(sun.is_up(self.hass)) + assert sun.is_up(self.hass) def test_norway_in_june(self): """Test location in Norway where the sun doesn't set in summer.""" diff --git a/tests/helpers/test_temperature.py b/tests/helpers/test_temperature.py index e2366d8866a..f6bee7b12ae 100644 --- a/tests/helpers/test_temperature.py +++ b/tests/helpers/test_temperature.py @@ -8,6 +8,7 @@ from homeassistant.const import ( PRECISION_TENTHS) from homeassistant.helpers.temperature import display_temp from homeassistant.util.unit_system import METRIC_SYSTEM +import pytest TEMP = 24.636626 @@ -27,23 +28,23 @@ class TestHelpersTemperature(unittest.TestCase): def test_temperature_not_a_number(self): """Test that temperature is a number.""" temp = "Temperature" - with self.assertRaises(Exception) as context: + with pytest.raises(Exception) as exception: display_temp(self.hass, temp, TEMP_CELSIUS, PRECISION_HALVES) - self.assertTrue("Temperature is not a number: {}".format(temp) - in str(context.exception)) + assert "Temperature is not a number: {}".format(temp) \ + in str(exception) def test_celsius_halves(self): """Test temperature to celsius rounding to halves.""" - self.assertEqual(24.5, display_temp( - self.hass, TEMP, TEMP_CELSIUS, PRECISION_HALVES)) + assert 24.5 == display_temp( + self.hass, TEMP, TEMP_CELSIUS, PRECISION_HALVES) def test_celsius_tenths(self): """Test temperature to celsius rounding to tenths.""" - self.assertEqual(24.6, display_temp( - self.hass, TEMP, TEMP_CELSIUS, PRECISION_TENTHS)) + assert 24.6 == display_temp( + self.hass, TEMP, TEMP_CELSIUS, PRECISION_TENTHS) def test_fahrenheit_wholes(self): """Test temperature to fahrenheit rounding to wholes.""" - self.assertEqual(-4, display_temp( - self.hass, TEMP, TEMP_FAHRENHEIT, PRECISION_WHOLE)) + assert -4 == display_temp( + self.hass, TEMP, TEMP_FAHRENHEIT, PRECISION_WHOLE) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 2ead38ba345..802138898a4 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -20,6 +20,7 @@ from homeassistant.const import ( import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant +import pytest class TestHelpersTemplate(unittest.TestCase): @@ -41,21 +42,19 @@ class TestHelpersTemplate(unittest.TestCase): def test_referring_states_by_entity_id(self): """Test referring states by entity id.""" self.hass.states.set('test.object', 'happy') - self.assertEqual( - 'happy', + assert 'happy' == \ template.Template( - '{{ states.test.object.state }}', self.hass).render()) + '{{ states.test.object.state }}', self.hass).render() def test_iterating_all_states(self): """Test iterating all states.""" self.hass.states.set('test.object', 'happy') self.hass.states.set('sensor.temperature', 10) - self.assertEqual( - '10happy', + assert '10happy' == \ template.Template( '{% for state in states %}{{ state.state }}{% endfor %}', - self.hass).render()) + self.hass).render() def test_iterating_domain_states(self): """Test iterating domain states.""" @@ -63,54 +62,47 @@ class TestHelpersTemplate(unittest.TestCase): self.hass.states.set('sensor.back_door', 'open') self.hass.states.set('sensor.temperature', 10) - self.assertEqual( - 'open10', + assert 'open10' == \ template.Template(""" {% for state in states.sensor %}{{ state.state }}{% endfor %} - """, self.hass).render()) + """, self.hass).render() def test_float(self): """Test float.""" self.hass.states.set('sensor.temperature', '12') - self.assertEqual( - '12.0', + assert '12.0' == \ template.Template( '{{ float(states.sensor.temperature.state) }}', - self.hass).render()) + self.hass).render() - self.assertEqual( - 'True', + assert 'True' == \ template.Template( '{{ float(states.sensor.temperature.state) > 11 }}', - self.hass).render()) + self.hass).render() def test_rounding_value(self): """Test rounding value.""" self.hass.states.set('sensor.temperature', 12.78) - self.assertEqual( - '12.8', + assert '12.8' == \ template.Template( '{{ states.sensor.temperature.state | round(1) }}', - self.hass).render()) + self.hass).render() - self.assertEqual( - '128', + assert '128' == \ template.Template( '{{ states.sensor.temperature.state | multiply(10) | round }}', - self.hass).render()) + self.hass).render() def test_rounding_value_get_original_value_on_error(self): """Test rounding value get original value on error.""" - self.assertEqual( - 'None', - template.Template('{{ None | round }}', self.hass).render()) + assert 'None' == \ + template.Template('{{ None | round }}', self.hass).render() - self.assertEqual( - 'no_number', + assert 'no_number' == \ template.Template( - '{{ "no_number" | round }}', self.hass).render()) + '{{ "no_number" | round }}', self.hass).render() def test_multiply(self): """Test multiply.""" @@ -121,10 +113,9 @@ class TestHelpersTemplate(unittest.TestCase): } for inp, out in tests.items(): - self.assertEqual( - out, + assert out == \ template.Template('{{ %s | multiply(10) | round }}' % inp, - self.hass).render()) + self.hass).render() def test_logarithm(self): """Test logarithm.""" @@ -137,17 +128,15 @@ class TestHelpersTemplate(unittest.TestCase): ] for value, base, expected in tests: - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ %s | log(%s) | round(1) }}' % (value, base), - self.hass).render()) + self.hass).render() - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ log(%s, %s) | round(1) }}' % (value, base), - self.hass).render()) + self.hass).render() def test_sine(self): """Test sine.""" @@ -160,11 +149,10 @@ class TestHelpersTemplate(unittest.TestCase): ] for value, expected in tests: - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ %s | sin | round(3) }}' % value, - self.hass).render()) + self.hass).render() def test_cos(self): """Test cosine.""" @@ -177,11 +165,10 @@ class TestHelpersTemplate(unittest.TestCase): ] for value, expected in tests: - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ %s | cos | round(3) }}' % value, - self.hass).render()) + self.hass).render() def test_tan(self): """Test tangent.""" @@ -194,11 +181,10 @@ class TestHelpersTemplate(unittest.TestCase): ] for value, expected in tests: - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ %s | tan | round(3) }}' % value, - self.hass).render()) + self.hass).render() def test_sqrt(self): """Test square root.""" @@ -211,11 +197,10 @@ class TestHelpersTemplate(unittest.TestCase): ] for value, expected in tests: - self.assertEqual( - expected, + assert expected == \ template.Template( '{{ %s | sqrt | round(3) }}' % value, - self.hass).render()) + self.hass).render() def test_strptime(self): """Test the parse timestamp method.""" @@ -239,9 +224,8 @@ class TestHelpersTemplate(unittest.TestCase): temp = '{{ strptime(\'%s\', \'%s\') }}' % (inp, fmt) - self.assertEqual( - str(expected), - template.Template(temp, self.hass).render()) + assert str(expected) == \ + template.Template(temp, self.hass).render() def test_timestamp_custom(self): """Test the timestamps to custom filter.""" @@ -263,10 +247,8 @@ class TestHelpersTemplate(unittest.TestCase): else: fil = 'timestamp_custom' - self.assertEqual( - out, - template.Template('{{ %s | %s }}' % (inp, fil), - self.hass).render()) + assert out == template.Template( + '{{ %s | %s }}' % (inp, fil), self.hass).render() def test_timestamp_local(self): """Test the timestamps to local filter.""" @@ -276,24 +258,21 @@ class TestHelpersTemplate(unittest.TestCase): } for inp, out in tests.items(): - self.assertEqual( - out, + assert out == \ template.Template('{{ %s | timestamp_local }}' % inp, - self.hass).render()) + self.hass).render() def test_min(self): """Test the min filter.""" - self.assertEqual( - '1', + assert '1' == \ template.Template('{{ [1, 2, 3] | min }}', - self.hass).render()) + self.hass).render() def test_max(self): """Test the max filter.""" - self.assertEqual( - '3', + assert '3' == \ template.Template('{{ [1, 2, 3] | max }}', - self.hass).render()) + self.hass).render() def test_timestamp_utc(self): """Test the timestamps to local filter.""" @@ -306,129 +285,115 @@ class TestHelpersTemplate(unittest.TestCase): } for inp, out in tests.items(): - self.assertEqual( - out, + assert out == \ template.Template('{{ %s | timestamp_utc }}' % inp, - self.hass).render()) + self.hass).render() def test_as_timestamp(self): """Test the as_timestamp function.""" - self.assertEqual("None", - template.Template('{{ as_timestamp("invalid") }}', - self.hass).render()) + assert "None" == \ + template.Template( + '{{ as_timestamp("invalid") }}', self.hass).render() self.hass.mock = None - self.assertEqual("None", - template.Template('{{ as_timestamp(states.mock) }}', - self.hass).render()) + assert "None" == \ + template.Template('{{ as_timestamp(states.mock) }}', + self.hass).render() tpl = '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' \ '"%Y-%m-%dT%H:%M:%S%z")) }}' - self.assertEqual("1706951424.0", - template.Template(tpl, self.hass).render()) + assert "1706951424.0" == \ + template.Template(tpl, self.hass).render() @patch.object(random, 'choice') def test_random_every_time(self, test_choice): """Ensure the random filter runs every time, not just once.""" tpl = template.Template('{{ [1,2] | random }}', self.hass) test_choice.return_value = 'foo' - self.assertEqual('foo', tpl.render()) + assert 'foo' == tpl.render() test_choice.return_value = 'bar' - self.assertEqual('bar', tpl.render()) + assert 'bar' == tpl.render() def test_passing_vars_as_keywords(self): """Test passing variables as keywords.""" - self.assertEqual( - '127', - template.Template('{{ hello }}', self.hass).render(hello=127)) + assert '127' == \ + template.Template('{{ hello }}', self.hass).render(hello=127) def test_passing_vars_as_vars(self): """Test passing variables as variables.""" - self.assertEqual( - '127', - template.Template('{{ hello }}', self.hass).render({'hello': 127})) + assert '127' == \ + template.Template('{{ hello }}', self.hass).render({'hello': 127}) def test_passing_vars_as_list(self): """Test passing variables as list.""" - self.assertEqual( - "['foo', 'bar']", + assert "['foo', 'bar']" == \ template.render_complex(template.Template('{{ hello }}', - self.hass), {'hello': ['foo', 'bar']})) + self.hass), {'hello': ['foo', 'bar']}) def test_passing_vars_as_list_element(self): """Test passing variables as list.""" - self.assertEqual( - 'bar', + assert 'bar' == \ template.render_complex(template.Template('{{ hello[1] }}', self.hass), - {'hello': ['foo', 'bar']})) + {'hello': ['foo', 'bar']}) def test_passing_vars_as_dict_element(self): """Test passing variables as list.""" - self.assertEqual( - 'bar', + assert 'bar' == \ template.render_complex(template.Template('{{ hello.foo }}', self.hass), - {'hello': {'foo': 'bar'}})) + {'hello': {'foo': 'bar'}}) def test_passing_vars_as_dict(self): """Test passing variables as list.""" - self.assertEqual( - "{'foo': 'bar'}", + assert "{'foo': 'bar'}" == \ template.render_complex(template.Template('{{ hello }}', - self.hass), {'hello': {'foo': 'bar'}})) + self.hass), {'hello': {'foo': 'bar'}}) def test_render_with_possible_json_value_with_valid_json(self): """Render with possible JSON value with valid JSON.""" tpl = template.Template('{{ value_json.hello }}', self.hass) - self.assertEqual( - 'world', - tpl.render_with_possible_json_value('{"hello": "world"}')) + assert 'world' == \ + tpl.render_with_possible_json_value('{"hello": "world"}') def test_render_with_possible_json_value_with_invalid_json(self): """Render with possible JSON value with invalid JSON.""" tpl = template.Template('{{ value_json }}', self.hass) - self.assertEqual( - '', - tpl.render_with_possible_json_value('{ I AM NOT JSON }')) + assert '' == \ + tpl.render_with_possible_json_value('{ I AM NOT JSON }') def test_render_with_possible_json_value_with_template_error_value(self): """Render with possible JSON value with template error value.""" tpl = template.Template('{{ non_existing.variable }}', self.hass) - self.assertEqual( - '-', - tpl.render_with_possible_json_value('hello', '-')) + assert '-' == \ + tpl.render_with_possible_json_value('hello', '-') def test_render_with_possible_json_value_with_missing_json_value(self): """Render with possible JSON value with unknown JSON object.""" tpl = template.Template('{{ value_json.goodbye }}', self.hass) - self.assertEqual( - '', - tpl.render_with_possible_json_value('{"hello": "world"}')) + assert '' == \ + tpl.render_with_possible_json_value('{"hello": "world"}') def test_render_with_possible_json_value_valid_with_is_defined(self): """Render with possible JSON value with known JSON object.""" tpl = template.Template('{{ value_json.hello|is_defined }}', self.hass) - self.assertEqual( - 'world', - tpl.render_with_possible_json_value('{"hello": "world"}')) + assert 'world' == \ + tpl.render_with_possible_json_value('{"hello": "world"}') def test_render_with_possible_json_value_undefined_json(self): """Render with possible JSON value with unknown JSON object.""" tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) - self.assertEqual( - '{"hello": "world"}', - tpl.render_with_possible_json_value('{"hello": "world"}')) + assert '{"hello": "world"}' == \ + tpl.render_with_possible_json_value('{"hello": "world"}') def test_render_with_possible_json_value_undefined_json_error_value(self): """Render with possible JSON value with unknown JSON object.""" tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) - self.assertEqual( - '', - tpl.render_with_possible_json_value('{"hello": "world"}', '')) + assert '' == \ + tpl.render_with_possible_json_value('{"hello": "world"}', '') def test_raise_exception_on_error(self): """Test raising an exception on error.""" - with self.assertRaises(TemplateError): + with pytest.raises(TemplateError): template.Template('{{ invalid_syntax').ensure_valid() def test_if_state_exists(self): @@ -437,7 +402,7 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template( '{% if states.test.object %}exists{% else %}not exists{% endif %}', self.hass) - self.assertEqual('exists', tpl.render()) + assert 'exists' == tpl.render() def test_is_state(self): """Test is_state method.""" @@ -445,12 +410,12 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template(""" {% if is_state("test.object", "available") %}yes{% else %}no{% endif %} """, self.hass) - self.assertEqual('yes', tpl.render()) + assert 'yes' == tpl.render() tpl = template.Template(""" {{ is_state("test.noobject", "available") }} """, self.hass) - self.assertEqual('False', tpl.render()) + assert 'False' == tpl.render() def test_is_state_attr(self): """Test is_state_attr method.""" @@ -458,12 +423,12 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template(""" {% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %} """, self.hass) - self.assertEqual('yes', tpl.render()) + assert 'yes' == tpl.render() tpl = template.Template(""" {{ is_state_attr("test.noobject", "mode", "on") }} """, self.hass) - self.assertEqual('False', tpl.render()) + assert 'False' == tpl.render() def test_state_attr(self): """Test state_attr method.""" @@ -471,21 +436,21 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template(""" {% if state_attr("test.object", "mode") == "on" %}yes{% else %}no{% endif %} """, self.hass) - self.assertEqual('yes', tpl.render()) + assert 'yes' == tpl.render() tpl = template.Template(""" {{ state_attr("test.noobject", "mode") == None }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() def test_states_function(self): """Test using states as a function.""" self.hass.states.set('test.object', 'available') tpl = template.Template('{{ states("test.object") }}', self.hass) - self.assertEqual('available', tpl.render()) + assert 'available' == tpl.render() tpl2 = template.Template('{{ states("test.object2") }}', self.hass) - self.assertEqual('unknown', tpl2.render()) + assert 'unknown' == tpl2.render() @patch('homeassistant.helpers.template.TemplateEnvironment.' 'is_safe_callable', return_value=True) @@ -493,10 +458,9 @@ class TestHelpersTemplate(unittest.TestCase): """Test now method.""" now = dt_util.now() with patch.dict(template.ENV.globals, {'now': lambda: now}): - self.assertEqual( - now.isoformat(), + assert now.isoformat() == \ template.Template('{{ now().isoformat() }}', - self.hass).render()) + self.hass).render() @patch('homeassistant.helpers.template.TemplateEnvironment.' 'is_safe_callable', return_value=True) @@ -504,93 +468,92 @@ class TestHelpersTemplate(unittest.TestCase): """Test utcnow method.""" now = dt_util.utcnow() with patch.dict(template.ENV.globals, {'utcnow': lambda: now}): - self.assertEqual( - now.isoformat(), + assert now.isoformat() == \ template.Template('{{ utcnow().isoformat() }}', - self.hass).render()) + self.hass).render() def test_regex_match(self): """Test regex_match method.""" tpl = template.Template(r""" {{ '123-456-7890' | regex_match('(\\d{3})-(\\d{3})-(\\d{4})') }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() tpl = template.Template(""" {{ 'home assistant test' | regex_match('Home', True) }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() tpl = template.Template(""" {{ 'Another home assistant test' | regex_match('home') }} """, self.hass) - self.assertEqual('False', tpl.render()) + assert 'False' == tpl.render() def test_regex_search(self): """Test regex_search method.""" tpl = template.Template(r""" {{ '123-456-7890' | regex_search('(\\d{3})-(\\d{3})-(\\d{4})') }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() tpl = template.Template(""" {{ 'home assistant test' | regex_search('Home', True) }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() tpl = template.Template(""" {{ 'Another home assistant test' | regex_search('home') }} """, self.hass) - self.assertEqual('True', tpl.render()) + assert 'True' == tpl.render() def test_regex_replace(self): """Test regex_replace method.""" tpl = template.Template(r""" {{ 'Hello World' | regex_replace('(Hello\\s)',) }} """, self.hass) - self.assertEqual('World', tpl.render()) + assert 'World' == tpl.render() def test_regex_findall_index(self): """Test regex_findall_index method.""" tpl = template.Template(""" {{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 0) }} """, self.hass) - self.assertEqual('JFK', tpl.render()) + assert 'JFK' == tpl.render() tpl = template.Template(""" {{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 1) }} """, self.hass) - self.assertEqual('LHR', tpl.render()) + assert 'LHR' == tpl.render() def test_bitwise_and(self): """Test bitwise_and method.""" tpl = template.Template(""" {{ 8 | bitwise_and(8) }} """, self.hass) - self.assertEqual(str(8 & 8), tpl.render()) + assert str(8 & 8) == tpl.render() tpl = template.Template(""" {{ 10 | bitwise_and(2) }} """, self.hass) - self.assertEqual(str(10 & 2), tpl.render()) + assert str(10 & 2) == tpl.render() tpl = template.Template(""" {{ 8 | bitwise_and(2) }} """, self.hass) - self.assertEqual(str(8 & 2), tpl.render()) + assert str(8 & 2) == tpl.render() def test_bitwise_or(self): """Test bitwise_or method.""" tpl = template.Template(""" {{ 8 | bitwise_or(8) }} """, self.hass) - self.assertEqual(str(8 | 8), tpl.render()) + assert str(8 | 8) == tpl.render() tpl = template.Template(""" {{ 10 | bitwise_or(2) }} """, self.hass) - self.assertEqual(str(10 | 2), tpl.render()) + assert str(10 | 2) == tpl.render() tpl = template.Template(""" {{ 8 | bitwise_or(2) }} """, self.hass) - self.assertEqual(str(8 | 2), tpl.render()) + assert str(8 | 2) == tpl.render() def test_distance_function_with_1_state(self): """Test distance function with 1 state.""" @@ -600,7 +563,7 @@ class TestHelpersTemplate(unittest.TestCase): }) tpl = template.Template('{{ distance(states.test.object) | round }}', self.hass) - self.assertEqual('187', tpl.render()) + assert '187' == tpl.render() def test_distance_function_with_2_states(self): """Test distance function with 2 states.""" @@ -615,24 +578,22 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template( '{{ distance(states.test.object, states.test.object_2) | round }}', self.hass) - self.assertEqual('187', tpl.render()) + assert '187' == tpl.render() def test_distance_function_with_1_coord(self): """Test distance function with 1 coord.""" tpl = template.Template( '{{ distance("32.87336", "-117.22943") | round }}', self.hass) - self.assertEqual( - '187', - tpl.render()) + assert '187' == \ + tpl.render() def test_distance_function_with_2_coords(self): """Test distance function with 2 coords.""" - self.assertEqual( - '187', + assert '187' == \ template.Template( '{{ distance("32.87336", "-117.22943", %s, %s) | round }}' % (self.hass.config.latitude, self.hass.config.longitude), - self.hass).render()) + self.hass).render() def test_distance_function_with_1_state_1_coord(self): """Test distance function with 1 state 1 coord.""" @@ -643,12 +604,12 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template( '{{ distance("32.87336", "-117.22943", states.test.object_2) ' '| round }}', self.hass) - self.assertEqual('187', tpl.render()) + assert '187' == tpl.render() tpl2 = template.Template( '{{ distance(states.test.object_2, "32.87336", "-117.22943") ' '| round }}', self.hass) - self.assertEqual('187', tpl2.render()) + assert '187' == tpl2.render() def test_distance_function_return_None_if_invalid_state(self): """Test distance function return None if invalid state.""" @@ -657,20 +618,17 @@ class TestHelpersTemplate(unittest.TestCase): }) tpl = template.Template('{{ distance(states.test.object_2) | round }}', self.hass) - self.assertEqual( - 'None', - tpl.render()) + assert 'None' == \ + tpl.render() def test_distance_function_return_None_if_invalid_coord(self): """Test distance function return None if invalid coord.""" - self.assertEqual( - 'None', + assert 'None' == \ template.Template( - '{{ distance("123", "abc") }}', self.hass).render()) + '{{ distance("123", "abc") }}', self.hass).render() - self.assertEqual( - 'None', - template.Template('{{ distance("123") }}', self.hass).render()) + assert 'None' == \ + template.Template('{{ distance("123") }}', self.hass).render() self.hass.states.set('test.object_2', 'happy', { 'latitude': self.hass.config.latitude, @@ -678,9 +636,8 @@ class TestHelpersTemplate(unittest.TestCase): }) tpl = template.Template('{{ distance("123", states.test_object_2) }}', self.hass) - self.assertEqual( - 'None', - tpl.render()) + assert 'None' == \ + tpl.render() def test_distance_function_with_2_entity_ids(self): """Test distance function with 2 entity ids.""" @@ -695,7 +652,7 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template( '{{ distance("test.object", "test.object_2") | round }}', self.hass) - self.assertEqual('187', tpl.render()) + assert '187' == tpl.render() def test_distance_function_with_1_entity_1_coord(self): """Test distance function with 1 entity_id and 1 coord.""" @@ -706,7 +663,7 @@ class TestHelpersTemplate(unittest.TestCase): tpl = template.Template( '{{ distance("test.object", "32.87336", "-117.22943") | round }}', self.hass) - self.assertEqual('187', tpl.render()) + assert '187' == tpl.render() def test_closest_function_home_vs_domain(self): """Test closest function home vs domain.""" @@ -720,10 +677,9 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude, }) - self.assertEqual( - 'test_domain.object', + assert 'test_domain.object' == \ template.Template('{{ closest(states.test_domain).entity_id }}', - self.hass).render()) + self.hass).render() def test_closest_function_home_vs_all_states(self): """Test closest function home vs all states.""" @@ -737,10 +693,9 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude, }) - self.assertEqual( - 'test_domain_2.and_closer', + assert 'test_domain_2.and_closer' == \ template.Template('{{ closest(states).entity_id }}', - self.hass).render()) + self.hass).render() def test_closest_function_home_vs_group_entity_id(self): """Test closest function home vs group entity id.""" @@ -757,11 +712,10 @@ class TestHelpersTemplate(unittest.TestCase): group.Group.create_group( self.hass, 'location group', ['test_domain.object']) - self.assertEqual( - 'test_domain.object', + assert 'test_domain.object' == \ template.Template( '{{ closest("group.location_group").entity_id }}', - self.hass).render()) + self.hass).render() def test_closest_function_home_vs_group_state(self): """Test closest function home vs group state.""" @@ -778,11 +732,10 @@ class TestHelpersTemplate(unittest.TestCase): group.Group.create_group( self.hass, 'location group', ['test_domain.object']) - self.assertEqual( - 'test_domain.object', + assert 'test_domain.object' == \ template.Template( '{{ closest(states.group.location_group).entity_id }}', - self.hass).render()) + self.hass).render() def test_closest_function_to_coord(self): """Test closest function to coord.""" @@ -806,9 +759,8 @@ class TestHelpersTemplate(unittest.TestCase): % (self.hass.config.latitude + 0.3, self.hass.config.longitude + 0.3), self.hass) - self.assertEqual( - 'test_domain.closest_zone', - tpl.render()) + assert 'test_domain.closest_zone' == \ + tpl.render() def test_closest_function_to_entity_id(self): """Test closest function to entity id.""" @@ -827,11 +779,10 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude + 0.3, }) - self.assertEqual( - 'test_domain.closest_zone', + assert 'test_domain.closest_zone' == \ template.Template( '{{ closest("zone.far_away", ' - 'states.test_domain).entity_id }}', self.hass).render()) + 'states.test_domain).entity_id }}', self.hass).render() def test_closest_function_to_state(self): """Test closest function to state.""" @@ -850,11 +801,10 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude + 0.3, }) - self.assertEqual( - 'test_domain.closest_zone', + assert 'test_domain.closest_zone' == \ template.Template( '{{ closest(states.zone.far_away, ' - 'states.test_domain).entity_id }}', self.hass).render()) + 'states.test_domain).entity_id }}', self.hass).render() def test_closest_function_invalid_state(self): """Test closest function invalid state.""" @@ -864,10 +814,9 @@ class TestHelpersTemplate(unittest.TestCase): }) for state in ('states.zone.non_existing', '"zone.non_existing"'): - self.assertEqual( - 'None', + assert 'None' == \ template.Template('{{ closest(%s, states) }}' % state, - self.hass).render()) + self.hass).render() def test_closest_function_state_with_invalid_location(self): """Test closest function state with invalid location.""" @@ -876,11 +825,10 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude + 0.1, }) - self.assertEqual( - 'None', + assert 'None' == \ template.Template( '{{ closest(states.test_domain.closest_home, ' - 'states) }}', self.hass).render()) + 'states) }}', self.hass).render() def test_closest_function_invalid_coordinates(self): """Test closest function invalid coordinates.""" @@ -889,148 +837,129 @@ class TestHelpersTemplate(unittest.TestCase): 'longitude': self.hass.config.longitude + 0.1, }) - self.assertEqual( - 'None', + assert 'None' == \ template.Template('{{ closest("invalid", "coord", states) }}', - self.hass).render()) + self.hass).render() def test_closest_function_no_location_states(self): """Test closest function without location states.""" - self.assertEqual( - '', + assert '' == \ template.Template('{{ closest(states).entity_id }}', - self.hass).render()) + self.hass).render() def test_extract_entities_none_exclude_stuff(self): """Test extract entities function with none or exclude stuff.""" - self.assertEqual(MATCH_ALL, template.extract_entities(None)) + assert MATCH_ALL == template.extract_entities(None) - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities( '{{ closest(states.zone.far_away, ' - 'states.test_domain).entity_id }}')) + 'states.test_domain).entity_id }}') - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities( - '{{ distance("123", states.test_object_2) }}')) + '{{ distance("123", states.test_object_2) }}') def test_extract_entities_no_match_entities(self): """Test extract entities function with none entities stuff.""" - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities( - "{{ value_json.tst | timestamp_custom('%Y' True) }}")) + "{{ value_json.tst | timestamp_custom('%Y' True) }}") - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities(""" {% for state in states.sensor %} {{ state.entity_id }}={{ state.state }},d {% endfor %} - """)) + """) def test_extract_entities_match_entities(self): """Test extract entities function with entities stuff.""" - self.assertListEqual( - ['device_tracker.phone_1'], + assert ['device_tracker.phone_1'] == \ template.extract_entities(""" {% if is_state('device_tracker.phone_1', 'home') %} Ha, Hercules is home! {% else %} Hercules is at {{ states('device_tracker.phone_1') }}. {% endif %} - """)) + """) - self.assertListEqual( - ['binary_sensor.garage_door'], + assert ['binary_sensor.garage_door'] == \ template.extract_entities(""" {{ as_timestamp(states.binary_sensor.garage_door.last_changed) }} - """)) + """) - self.assertListEqual( - ['binary_sensor.garage_door'], + assert ['binary_sensor.garage_door'] == \ template.extract_entities(""" {{ states("binary_sensor.garage_door") }} - """)) + """) - self.assertListEqual( - ['device_tracker.phone_2'], + assert ['device_tracker.phone_2'] == \ template.extract_entities(""" is_state_attr('device_tracker.phone_2', 'battery', 40) - """)) + """) - self.assertListEqual( - sorted([ + assert sorted([ 'device_tracker.phone_1', 'device_tracker.phone_2', - ]), + ]) == \ sorted(template.extract_entities(""" {% if is_state('device_tracker.phone_1', 'home') %} Ha, Hercules is home! {% elif states.device_tracker.phone_2.attributes.battery < 40 %} Hercules you power goes done!. {% endif %} - """))) + """)) - self.assertListEqual( - sorted([ + assert sorted([ 'sensor.pick_humidity', 'sensor.pick_temperature', - ]), + ]) == \ sorted(template.extract_entities(""" {{ states.sensor.pick_temperature.state ~ „°C (“ ~ states.sensor.pick_humidity.state ~ „ %“ }} - """))) + """)) - self.assertListEqual( - sorted([ + assert sorted([ 'sensor.luftfeuchtigkeit_mean', 'input_number.luftfeuchtigkeit', - ]), + ]) == \ sorted(template.extract_entities( "{% if (states('sensor.luftfeuchtigkeit_mean') | int)" " > (states('input_number.luftfeuchtigkeit') | int +1.5)" " %}true{% endif %}" - ))) + )) def test_extract_entities_with_variables(self): """Test extract entities function with variables and entities stuff.""" - self.assertEqual( - ['input_boolean.switch'], + assert ['input_boolean.switch'] == \ template.extract_entities( - "{{ is_state('input_boolean.switch', 'off') }}", {})) + "{{ is_state('input_boolean.switch', 'off') }}", {}) - self.assertEqual( - ['trigger.entity_id'], + assert ['trigger.entity_id'] == \ template.extract_entities( - "{{ is_state(trigger.entity_id, 'off') }}", {})) + "{{ is_state(trigger.entity_id, 'off') }}", {}) - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities( - "{{ is_state(data, 'off') }}", {})) + "{{ is_state(data, 'off') }}", {}) - self.assertEqual( - ['input_boolean.switch'], + assert ['input_boolean.switch'] == \ template.extract_entities( "{{ is_state(data, 'off') }}", - {'data': 'input_boolean.switch'})) + {'data': 'input_boolean.switch'}) - self.assertEqual( - ['input_boolean.switch'], + assert ['input_boolean.switch'] == \ template.extract_entities( "{{ is_state(trigger.entity_id, 'off') }}", - {'trigger': {'entity_id': 'input_boolean.switch'}})) + {'trigger': {'entity_id': 'input_boolean.switch'}}) - self.assertEqual( - MATCH_ALL, + assert MATCH_ALL == \ template.extract_entities( "{{ is_state('media_player.' ~ where , 'playing') }}", - {'where': 'livingroom'})) + {'where': 'livingroom'}) @asyncio.coroutine diff --git a/tests/test_config.py b/tests/test_config.py index 0e53bc0cdfb..056bf30efe5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -102,7 +102,7 @@ class TestConfig(unittest.TestCase): """Test if it finds a YAML config file.""" create_file(YAML_PATH) - self.assertEqual(YAML_PATH, config_util.find_config_file(CONFIG_DIR)) + assert YAML_PATH == config_util.find_config_file(CONFIG_DIR) @mock.patch('builtins.print') def test_ensure_config_exists_creates_config(self, mock_print): @@ -112,8 +112,8 @@ class TestConfig(unittest.TestCase): """ config_util.ensure_config_exists(CONFIG_DIR, False) - self.assertTrue(os.path.isfile(YAML_PATH)) - self.assertTrue(mock_print.called) + assert os.path.isfile(YAML_PATH) + assert mock_print.called def test_ensure_config_exists_uses_existing_config(self): """Test that calling ensure_config_exists uses existing config.""" @@ -124,21 +124,20 @@ class TestConfig(unittest.TestCase): content = f.read() # File created with create_file are empty - self.assertEqual('', content) + assert '' == content def test_load_yaml_config_converts_empty_files_to_dict(self): """Test that loading an empty file returns an empty dict.""" create_file(YAML_PATH) - self.assertIsInstance( - config_util.load_yaml_config_file(YAML_PATH), dict) + assert isinstance(config_util.load_yaml_config_file(YAML_PATH), dict) def test_load_yaml_config_raises_error_if_not_dict(self): """Test error raised when YAML file is not a dict.""" with open(YAML_PATH, 'w') as f: f.write('5') - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): config_util.load_yaml_config_file(YAML_PATH) def test_load_yaml_config_raises_error_if_malformed_yaml(self): @@ -146,7 +145,7 @@ class TestConfig(unittest.TestCase): with open(YAML_PATH, 'w') as f: f.write(':') - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): config_util.load_yaml_config_file(YAML_PATH) def test_load_yaml_config_raises_error_if_unsafe_yaml(self): @@ -154,7 +153,7 @@ class TestConfig(unittest.TestCase): with open(YAML_PATH, 'w') as f: f.write('hello: !!python/object/apply:os.system') - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): config_util.load_yaml_config_file(YAML_PATH) def test_load_yaml_config_preserves_key_order(self): @@ -163,9 +162,8 @@ class TestConfig(unittest.TestCase): f.write('hello: 2\n') f.write('world: 1\n') - self.assertEqual( - [('hello', 2), ('world', 1)], - list(config_util.load_yaml_config_file(YAML_PATH).items())) + assert [('hello', 2), ('world', 1)] == \ + list(config_util.load_yaml_config_file(YAML_PATH).items()) @mock.patch('homeassistant.util.location.detect_location_info', return_value=location_util.LocationInfo( @@ -181,7 +179,7 @@ class TestConfig(unittest.TestCase): config = config_util.load_yaml_config_file(YAML_PATH) - self.assertIn(DOMAIN, config) + assert DOMAIN in config ha_conf = config[DOMAIN] @@ -205,10 +203,9 @@ class TestConfig(unittest.TestCase): Non existing folder returns None. """ - self.assertIsNone( - config_util.create_default_config( - os.path.join(CONFIG_DIR, 'non_existing_dir/'), False)) - self.assertTrue(mock_print.called) + assert config_util.create_default_config( + os.path.join(CONFIG_DIR, 'non_existing_dir/'), False) is None + assert mock_print.called # pylint: disable=no-self-use def test_core_config_schema(self): @@ -264,7 +261,7 @@ class TestConfig(unittest.TestCase): """Test that customize_glob preserves order.""" conf = config_util.CORE_CONFIG_SCHEMA( {'customize_glob': OrderedDict()}) - self.assertIsInstance(conf['customize_glob'], OrderedDict) + assert isinstance(conf['customize_glob'], OrderedDict) def _compute_state(self, config): run_coroutine_threadsafe( @@ -306,14 +303,10 @@ class TestConfig(unittest.TestCase): config_util.process_ha_config_upgrade(self.hass) hass_path = self.hass.config.path.return_value - self.assertEqual(mock_os.path.isdir.call_count, 1) - self.assertEqual( - mock_os.path.isdir.call_args, mock.call(hass_path) - ) - self.assertEqual(mock_shutil.rmtree.call_count, 1) - self.assertEqual( - mock_shutil.rmtree.call_args, mock.call(hass_path) - ) + assert mock_os.path.isdir.call_count == 1 + assert mock_os.path.isdir.call_args == mock.call(hass_path) + assert mock_shutil.rmtree.call_count == 1 + assert mock_shutil.rmtree.call_args == mock.call(hass_path) def test_process_config_upgrade(self): """Test update of version on upgrade.""" @@ -327,10 +320,8 @@ class TestConfig(unittest.TestCase): config_util.process_ha_config_upgrade(self.hass) - self.assertEqual(opened_file.write.call_count, 1) - self.assertEqual( - opened_file.write.call_args, mock.call(__version__) - ) + assert opened_file.write.call_count == 1 + assert opened_file.write.call_args == mock.call(__version__) def test_config_upgrade_same_version(self): """Test no update of version on no upgrade.""" @@ -354,9 +345,8 @@ class TestConfig(unittest.TestCase): opened_file = mock_open.return_value # pylint: disable=no-member config_util.process_ha_config_upgrade(self.hass) - self.assertEqual(opened_file.write.call_count, 1) - self.assertEqual( - opened_file.write.call_args, mock.call(__version__)) + assert opened_file.write.call_count == 1 + assert opened_file.write.call_args == mock.call(__version__) @mock.patch('homeassistant.config.shutil') @mock.patch('homeassistant.config.os') diff --git a/tests/test_core.py b/tests/test_core.py index 7ab624447c5..69cde6c1403 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -253,19 +253,17 @@ class TestEvent(unittest.TestCase): for _ in range(2) ] - self.assertEqual(event1, event2) + assert event1 == event2 def test_repr(self): """Test that repr method works.""" - self.assertEqual( - "", - str(ha.Event("TestEvent"))) + assert "" == \ + str(ha.Event("TestEvent")) - self.assertEqual( - "", + assert "" == \ str(ha.Event("TestEvent", {"beer": "nice"}, - ha.EventOrigin.remote))) + ha.EventOrigin.remote)) def test_as_dict(self): """Test as dictionary.""" @@ -284,7 +282,7 @@ class TestEvent(unittest.TestCase): 'user_id': event.context.user_id, }, } - self.assertEqual(expected, event.as_dict()) + assert expected == event.as_dict() class TestEventBus(unittest.TestCase): @@ -310,11 +308,11 @@ class TestEventBus(unittest.TestCase): unsub = self.bus.listen('test', listener) - self.assertEqual(old_count + 1, len(self.bus.listeners)) + assert old_count + 1 == len(self.bus.listeners) # Remove listener unsub() - self.assertEqual(old_count, len(self.bus.listeners)) + assert old_count == len(self.bus.listeners) # Should do nothing now unsub() @@ -357,7 +355,7 @@ class TestEventBus(unittest.TestCase): self.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(runs)) + assert 1 == len(runs) def test_listen_once_event_with_coroutine(self): """Test listen_once_event method.""" @@ -374,7 +372,7 @@ class TestEventBus(unittest.TestCase): self.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(runs)) + assert 1 == len(runs) def test_listen_once_event_with_thread(self): """Test listen_once_event method.""" @@ -390,7 +388,7 @@ class TestEventBus(unittest.TestCase): self.bus.fire('test_event') self.hass.block_till_done() - self.assertEqual(1, len(runs)) + assert 1 == len(runs) def test_thread_event_listener(self): """Test thread event listener.""" @@ -436,59 +434,56 @@ class TestState(unittest.TestCase): def test_init(self): """Test state.init.""" - self.assertRaises( - InvalidEntityFormatError, ha.State, - 'invalid_entity_format', 'test_state') + with pytest.raises(InvalidEntityFormatError): + ha.State('invalid_entity_format', 'test_state') - self.assertRaises( - InvalidStateError, ha.State, - 'domain.long_state', 't' * 256) + with pytest.raises(InvalidStateError): + ha.State('domain.long_state', 't' * 256) def test_domain(self): """Test domain.""" state = ha.State('some_domain.hello', 'world') - self.assertEqual('some_domain', state.domain) + assert 'some_domain' == state.domain def test_object_id(self): """Test object ID.""" state = ha.State('domain.hello', 'world') - self.assertEqual('hello', state.object_id) + assert 'hello' == state.object_id def test_name_if_no_friendly_name_attr(self): """Test if there is no friendly name.""" state = ha.State('domain.hello_world', 'world') - self.assertEqual('hello world', state.name) + assert 'hello world' == state.name def test_name_if_friendly_name_attr(self): """Test if there is a friendly name.""" name = 'Some Unique Name' state = ha.State('domain.hello_world', 'world', {ATTR_FRIENDLY_NAME: name}) - self.assertEqual(name, state.name) + assert name == state.name def test_dict_conversion(self): """Test conversion of dict.""" state = ha.State('domain.hello', 'world', {'some': 'attr'}) - self.assertEqual(state, ha.State.from_dict(state.as_dict())) + assert state == ha.State.from_dict(state.as_dict()) def test_dict_conversion_with_wrong_data(self): """Test conversion with wrong data.""" - self.assertIsNone(ha.State.from_dict(None)) - self.assertIsNone(ha.State.from_dict({'state': 'yes'})) - self.assertIsNone(ha.State.from_dict({'entity_id': 'yes'})) + assert ha.State.from_dict(None) is None + assert ha.State.from_dict({'state': 'yes'}) is None + assert ha.State.from_dict({'entity_id': 'yes'}) is None def test_repr(self): """Test state.repr.""" - self.assertEqual("", - str(ha.State( - "happy.happy", "on", - last_changed=datetime(1984, 12, 8, 12, 0, 0)))) + assert "" == \ + str(ha.State( + "happy.happy", "on", + last_changed=datetime(1984, 12, 8, 12, 0, 0))) - self.assertEqual( - "", + assert "" == \ str(ha.State("happy.happy", "on", {"brightness": 144}, - datetime(1984, 12, 8, 12, 0, 0)))) + datetime(1984, 12, 8, 12, 0, 0))) class TestStateMachine(unittest.TestCase): @@ -509,25 +504,25 @@ class TestStateMachine(unittest.TestCase): def test_is_state(self): """Test is_state method.""" - self.assertTrue(self.states.is_state('light.Bowl', 'on')) - self.assertFalse(self.states.is_state('light.Bowl', 'off')) - self.assertFalse(self.states.is_state('light.Non_existing', 'on')) + assert self.states.is_state('light.Bowl', 'on') + assert not self.states.is_state('light.Bowl', 'off') + assert not self.states.is_state('light.Non_existing', 'on') def test_entity_ids(self): """Test get_entity_ids method.""" ent_ids = self.states.entity_ids() - self.assertEqual(2, len(ent_ids)) - self.assertTrue('light.bowl' in ent_ids) - self.assertTrue('switch.ac' in ent_ids) + assert 2 == len(ent_ids) + assert 'light.bowl' in ent_ids + assert 'switch.ac' in ent_ids ent_ids = self.states.entity_ids('light') - self.assertEqual(1, len(ent_ids)) - self.assertTrue('light.bowl' in ent_ids) + assert 1 == len(ent_ids) + assert 'light.bowl' in ent_ids def test_all(self): """Test everything.""" states = sorted(state.entity_id for state in self.states.all()) - self.assertEqual(['light.bowl', 'switch.ac'], states) + assert ['light.bowl', 'switch.ac'] == states def test_remove(self): """Test remove method.""" @@ -539,21 +534,21 @@ class TestStateMachine(unittest.TestCase): self.hass.bus.listen(EVENT_STATE_CHANGED, callback) - self.assertIn('light.bowl', self.states.entity_ids()) - self.assertTrue(self.states.remove('light.bowl')) + assert 'light.bowl' in self.states.entity_ids() + assert self.states.remove('light.bowl') self.hass.block_till_done() - self.assertNotIn('light.bowl', self.states.entity_ids()) - self.assertEqual(1, len(events)) - self.assertEqual('light.bowl', events[0].data.get('entity_id')) - self.assertIsNotNone(events[0].data.get('old_state')) - self.assertEqual('light.bowl', events[0].data['old_state'].entity_id) - self.assertIsNone(events[0].data.get('new_state')) + assert 'light.bowl' not in self.states.entity_ids() + assert 1 == len(events) + assert 'light.bowl' == events[0].data.get('entity_id') + assert events[0].data.get('old_state') is not None + assert 'light.bowl' == events[0].data['old_state'].entity_id + assert events[0].data.get('new_state') is None # If it does not exist, we should get False - self.assertFalse(self.states.remove('light.Bowl')) + assert not self.states.remove('light.Bowl') self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) def test_case_insensitivty(self): """Test insensitivty.""" @@ -568,8 +563,8 @@ class TestStateMachine(unittest.TestCase): self.states.set('light.BOWL', 'off') self.hass.block_till_done() - self.assertTrue(self.states.is_state('light.bowl', 'off')) - self.assertEqual(1, len(runs)) + assert self.states.is_state('light.bowl', 'off') + assert 1 == len(runs) def test_last_changed_not_updated_on_same_state(self): """Test to not update the existing, same state.""" @@ -597,11 +592,11 @@ class TestStateMachine(unittest.TestCase): self.states.set('light.bowl', 'on') self.hass.block_till_done() - self.assertEqual(0, len(events)) + assert 0 == len(events) self.states.set('light.bowl', 'on', None, True) self.hass.block_till_done() - self.assertEqual(1, len(events)) + assert 1 == len(events) def test_service_call_repr(): @@ -647,12 +642,9 @@ class TestServiceRegistry(unittest.TestCase): def test_has_service(self): """Test has_service method.""" - self.assertTrue( - self.services.has_service("tesT_domaiN", "tesT_servicE")) - self.assertFalse( - self.services.has_service("test_domain", "non_existing")) - self.assertFalse( - self.services.has_service("non_existing", "test_service")) + assert self.services.has_service("tesT_domaiN", "tesT_servicE") + assert not self.services.has_service("test_domain", "non_existing") + assert not self.services.has_service("non_existing", "test_service") def test_services(self): """Test services.""" @@ -675,9 +667,9 @@ class TestServiceRegistry(unittest.TestCase): assert self.calls_register[-1].data['domain'] == 'test_domain' assert self.calls_register[-1].data['service'] == 'register_calls' - self.assertTrue( - self.services.call('test_domain', 'REGISTER_CALLS', blocking=True)) - self.assertEqual(1, len(calls)) + assert self.services.call('test_domain', 'REGISTER_CALLS', + blocking=True) + assert 1 == len(calls) def test_call_non_existing_with_blocking(self): """Test non-existing with blocking.""" @@ -706,10 +698,10 @@ class TestServiceRegistry(unittest.TestCase): assert self.calls_register[-1].data['domain'] == 'test_domain' assert self.calls_register[-1].data['service'] == 'register_calls' - self.assertTrue( - self.services.call('test_domain', 'REGISTER_CALLS', blocking=True)) + assert self.services.call('test_domain', 'REGISTER_CALLS', + blocking=True) self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) def test_callback_service(self): """Test registering and calling an async service.""" @@ -728,10 +720,10 @@ class TestServiceRegistry(unittest.TestCase): assert self.calls_register[-1].data['domain'] == 'test_domain' assert self.calls_register[-1].data['service'] == 'register_calls' - self.assertTrue( - self.services.call('test_domain', 'REGISTER_CALLS', blocking=True)) + assert self.services.call('test_domain', 'REGISTER_CALLS', + blocking=True) self.hass.block_till_done() - self.assertEqual(1, len(calls)) + assert 1 == len(calls) def test_remove_service(self): """Test remove service.""" @@ -778,19 +770,19 @@ class TestConfig(unittest.TestCase): def setUp(self): """Set up things to be run when tests are started.""" self.config = ha.Config() - self.assertIsNone(self.config.config_dir) + assert self.config.config_dir is None def test_path_with_file(self): """Test get_config_path method.""" self.config.config_dir = '/tmp/ha-config' - self.assertEqual("/tmp/ha-config/test.conf", - self.config.path("test.conf")) + assert "/tmp/ha-config/test.conf" == \ + self.config.path("test.conf") def test_path_with_dir_and_file(self): """Test get_config_path method.""" self.config.config_dir = '/tmp/ha-config' - self.assertEqual("/tmp/ha-config/dir/test.conf", - self.config.path("dir", "test.conf")) + assert "/tmp/ha-config/dir/test.conf" == \ + self.config.path("dir", "test.conf") def test_as_dict(self): """Test as dict.""" @@ -808,7 +800,7 @@ class TestConfig(unittest.TestCase): 'version': __version__, } - self.assertEqual(expected, self.config.as_dict()) + assert expected == self.config.as_dict() def test_is_allowed_path(self): """Test is_allowed_path method.""" @@ -843,7 +835,7 @@ class TestConfig(unittest.TestCase): for path in unvalid: assert not self.config.is_allowed_path(path) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.config.is_allowed_path(None) diff --git a/tests/test_loader.py b/tests/test_loader.py index c4adb971593..90d259c860e 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -34,8 +34,8 @@ class TestLoader(unittest.TestCase): def test_get_component(self): """Test if get_component works.""" - self.assertEqual(http, loader.get_component(self.hass, 'http')) - self.assertIsNotNone(loader.get_component(self.hass, 'light.hue')) + assert http == loader.get_component(self.hass, 'http') + assert loader.get_component(self.hass, 'light.hue') is not None def test_load_order_component(self): """Test if we can get the proper load order of components.""" @@ -43,23 +43,22 @@ class TestLoader(unittest.TestCase): loader.set_component(self.hass, 'mod2', MockModule('mod2', ['mod1'])) loader.set_component(self.hass, 'mod3', MockModule('mod3', ['mod2'])) - self.assertEqual( - ['mod1', 'mod2', 'mod3'], - loader.load_order_component(self.hass, 'mod3')) + assert ['mod1', 'mod2', 'mod3'] == \ + loader.load_order_component(self.hass, 'mod3') # Create circular dependency loader.set_component(self.hass, 'mod1', MockModule('mod1', ['mod3'])) - self.assertEqual([], loader.load_order_component(self.hass, 'mod3')) + assert [] == loader.load_order_component(self.hass, 'mod3') # Depend on non-existing component loader.set_component(self.hass, 'mod1', MockModule('mod1', ['nonexisting'])) - self.assertEqual([], loader.load_order_component(self.hass, 'mod1')) + assert [] == loader.load_order_component(self.hass, 'mod1') # Try to get load order for non-existing component - self.assertEqual([], loader.load_order_component(self.hass, 'mod1')) + assert [] == loader.load_order_component(self.hass, 'mod1') def test_component_loader(hass): diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 74ba72cd3d1..b7802d3dc09 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -12,203 +12,203 @@ class TestColorUtil(unittest.TestCase): # pylint: disable=invalid-name def test_color_RGB_to_xy_brightness(self): """Test color_RGB_to_xy_brightness.""" - self.assertEqual((0, 0, 0), - color_util.color_RGB_to_xy_brightness(0, 0, 0)) - self.assertEqual((0.323, 0.329, 255), - color_util.color_RGB_to_xy_brightness(255, 255, 255)) + assert (0, 0, 0) == \ + color_util.color_RGB_to_xy_brightness(0, 0, 0) + assert (0.323, 0.329, 255) == \ + color_util.color_RGB_to_xy_brightness(255, 255, 255) - self.assertEqual((0.136, 0.04, 12), - color_util.color_RGB_to_xy_brightness(0, 0, 255)) + assert (0.136, 0.04, 12) == \ + color_util.color_RGB_to_xy_brightness(0, 0, 255) - self.assertEqual((0.172, 0.747, 170), - color_util.color_RGB_to_xy_brightness(0, 255, 0)) + assert (0.172, 0.747, 170) == \ + color_util.color_RGB_to_xy_brightness(0, 255, 0) - self.assertEqual((0.701, 0.299, 72), - color_util.color_RGB_to_xy_brightness(255, 0, 0)) + assert (0.701, 0.299, 72) == \ + color_util.color_RGB_to_xy_brightness(255, 0, 0) - self.assertEqual((0.701, 0.299, 16), - color_util.color_RGB_to_xy_brightness(128, 0, 0)) + assert (0.701, 0.299, 16) == \ + color_util.color_RGB_to_xy_brightness(128, 0, 0) def test_color_RGB_to_xy(self): """Test color_RGB_to_xy.""" - self.assertEqual((0, 0), - color_util.color_RGB_to_xy(0, 0, 0)) - self.assertEqual((0.323, 0.329), - color_util.color_RGB_to_xy(255, 255, 255)) + assert (0, 0) == \ + color_util.color_RGB_to_xy(0, 0, 0) + assert (0.323, 0.329) == \ + color_util.color_RGB_to_xy(255, 255, 255) - self.assertEqual((0.136, 0.04), - color_util.color_RGB_to_xy(0, 0, 255)) + assert (0.136, 0.04) == \ + color_util.color_RGB_to_xy(0, 0, 255) - self.assertEqual((0.172, 0.747), - color_util.color_RGB_to_xy(0, 255, 0)) + assert (0.172, 0.747) == \ + color_util.color_RGB_to_xy(0, 255, 0) - self.assertEqual((0.701, 0.299), - color_util.color_RGB_to_xy(255, 0, 0)) + assert (0.701, 0.299) == \ + color_util.color_RGB_to_xy(255, 0, 0) - self.assertEqual((0.701, 0.299), - color_util.color_RGB_to_xy(128, 0, 0)) + assert (0.701, 0.299) == \ + color_util.color_RGB_to_xy(128, 0, 0) def test_color_xy_brightness_to_RGB(self): """Test color_xy_brightness_to_RGB.""" - self.assertEqual((0, 0, 0), - color_util.color_xy_brightness_to_RGB(1, 1, 0)) + assert (0, 0, 0) == \ + color_util.color_xy_brightness_to_RGB(1, 1, 0) - self.assertEqual((194, 186, 169), - color_util.color_xy_brightness_to_RGB(.35, .35, 128)) + assert (194, 186, 169) == \ + color_util.color_xy_brightness_to_RGB(.35, .35, 128) - self.assertEqual((255, 243, 222), - color_util.color_xy_brightness_to_RGB(.35, .35, 255)) + assert (255, 243, 222) == \ + color_util.color_xy_brightness_to_RGB(.35, .35, 255) - self.assertEqual((255, 0, 60), - color_util.color_xy_brightness_to_RGB(1, 0, 255)) + assert (255, 0, 60) == \ + color_util.color_xy_brightness_to_RGB(1, 0, 255) - self.assertEqual((0, 255, 0), - color_util.color_xy_brightness_to_RGB(0, 1, 255)) + assert (0, 255, 0) == \ + color_util.color_xy_brightness_to_RGB(0, 1, 255) - self.assertEqual((0, 63, 255), - color_util.color_xy_brightness_to_RGB(0, 0, 255)) + assert (0, 63, 255) == \ + color_util.color_xy_brightness_to_RGB(0, 0, 255) def test_color_xy_to_RGB(self): """Test color_xy_to_RGB.""" - self.assertEqual((255, 243, 222), - color_util.color_xy_to_RGB(.35, .35)) + assert (255, 243, 222) == \ + color_util.color_xy_to_RGB(.35, .35) - self.assertEqual((255, 0, 60), - color_util.color_xy_to_RGB(1, 0)) + assert (255, 0, 60) == \ + color_util.color_xy_to_RGB(1, 0) - self.assertEqual((0, 255, 0), - color_util.color_xy_to_RGB(0, 1)) + assert (0, 255, 0) == \ + color_util.color_xy_to_RGB(0, 1) - self.assertEqual((0, 63, 255), - color_util.color_xy_to_RGB(0, 0)) + assert (0, 63, 255) == \ + color_util.color_xy_to_RGB(0, 0) def test_color_RGB_to_hsv(self): """Test color_RGB_to_hsv.""" - self.assertEqual((0, 0, 0), - color_util.color_RGB_to_hsv(0, 0, 0)) + assert (0, 0, 0) == \ + color_util.color_RGB_to_hsv(0, 0, 0) - self.assertEqual((0, 0, 100), - color_util.color_RGB_to_hsv(255, 255, 255)) + assert (0, 0, 100) == \ + color_util.color_RGB_to_hsv(255, 255, 255) - self.assertEqual((240, 100, 100), - color_util.color_RGB_to_hsv(0, 0, 255)) + assert (240, 100, 100) == \ + color_util.color_RGB_to_hsv(0, 0, 255) - self.assertEqual((120, 100, 100), - color_util.color_RGB_to_hsv(0, 255, 0)) + assert (120, 100, 100) == \ + color_util.color_RGB_to_hsv(0, 255, 0) - self.assertEqual((0, 100, 100), - color_util.color_RGB_to_hsv(255, 0, 0)) + assert (0, 100, 100) == \ + color_util.color_RGB_to_hsv(255, 0, 0) def test_color_hsv_to_RGB(self): """Test color_hsv_to_RGB.""" - self.assertEqual((0, 0, 0), - color_util.color_hsv_to_RGB(0, 0, 0)) + assert (0, 0, 0) == \ + color_util.color_hsv_to_RGB(0, 0, 0) - self.assertEqual((255, 255, 255), - color_util.color_hsv_to_RGB(0, 0, 100)) + assert (255, 255, 255) == \ + color_util.color_hsv_to_RGB(0, 0, 100) - self.assertEqual((0, 0, 255), - color_util.color_hsv_to_RGB(240, 100, 100)) + assert (0, 0, 255) == \ + color_util.color_hsv_to_RGB(240, 100, 100) - self.assertEqual((0, 255, 0), - color_util.color_hsv_to_RGB(120, 100, 100)) + assert (0, 255, 0) == \ + color_util.color_hsv_to_RGB(120, 100, 100) - self.assertEqual((255, 0, 0), - color_util.color_hsv_to_RGB(0, 100, 100)) + assert (255, 0, 0) == \ + color_util.color_hsv_to_RGB(0, 100, 100) def test_color_hsb_to_RGB(self): """Test color_hsb_to_RGB.""" - self.assertEqual((0, 0, 0), - color_util.color_hsb_to_RGB(0, 0, 0)) + assert (0, 0, 0) == \ + color_util.color_hsb_to_RGB(0, 0, 0) - self.assertEqual((255, 255, 255), - color_util.color_hsb_to_RGB(0, 0, 1.0)) + assert (255, 255, 255) == \ + color_util.color_hsb_to_RGB(0, 0, 1.0) - self.assertEqual((0, 0, 255), - color_util.color_hsb_to_RGB(240, 1.0, 1.0)) + assert (0, 0, 255) == \ + color_util.color_hsb_to_RGB(240, 1.0, 1.0) - self.assertEqual((0, 255, 0), - color_util.color_hsb_to_RGB(120, 1.0, 1.0)) + assert (0, 255, 0) == \ + color_util.color_hsb_to_RGB(120, 1.0, 1.0) - self.assertEqual((255, 0, 0), - color_util.color_hsb_to_RGB(0, 1.0, 1.0)) + assert (255, 0, 0) == \ + color_util.color_hsb_to_RGB(0, 1.0, 1.0) def test_color_xy_to_hs(self): """Test color_xy_to_hs.""" - self.assertEqual((47.294, 100), - color_util.color_xy_to_hs(1, 1)) + assert (47.294, 100) == \ + color_util.color_xy_to_hs(1, 1) - self.assertEqual((38.182, 12.941), - color_util.color_xy_to_hs(.35, .35)) + assert (38.182, 12.941) == \ + color_util.color_xy_to_hs(.35, .35) - self.assertEqual((345.882, 100), - color_util.color_xy_to_hs(1, 0)) + assert (345.882, 100) == \ + color_util.color_xy_to_hs(1, 0) - self.assertEqual((120, 100), - color_util.color_xy_to_hs(0, 1)) + assert (120, 100) == \ + color_util.color_xy_to_hs(0, 1) - self.assertEqual((225.176, 100), - color_util.color_xy_to_hs(0, 0)) + assert (225.176, 100) == \ + color_util.color_xy_to_hs(0, 0) def test_color_hs_to_xy(self): """Test color_hs_to_xy.""" - self.assertEqual((0.151, 0.343), - color_util.color_hs_to_xy(180, 100)) + assert (0.151, 0.343) == \ + color_util.color_hs_to_xy(180, 100) - self.assertEqual((0.356, 0.321), - color_util.color_hs_to_xy(350, 12.5)) + assert (0.356, 0.321) == \ + color_util.color_hs_to_xy(350, 12.5) - self.assertEqual((0.229, 0.474), - color_util.color_hs_to_xy(140, 50)) + assert (0.229, 0.474) == \ + color_util.color_hs_to_xy(140, 50) - self.assertEqual((0.474, 0.317), - color_util.color_hs_to_xy(0, 40)) + assert (0.474, 0.317) == \ + color_util.color_hs_to_xy(0, 40) - self.assertEqual((0.323, 0.329), - color_util.color_hs_to_xy(360, 0)) + assert (0.323, 0.329) == \ + color_util.color_hs_to_xy(360, 0) def test_rgb_hex_to_rgb_list(self): """Test rgb_hex_to_rgb_list.""" - self.assertEqual([255, 255, 255], - color_util.rgb_hex_to_rgb_list('ffffff')) + assert [255, 255, 255] == \ + color_util.rgb_hex_to_rgb_list('ffffff') - self.assertEqual([0, 0, 0], - color_util.rgb_hex_to_rgb_list('000000')) + assert [0, 0, 0] == \ + color_util.rgb_hex_to_rgb_list('000000') - self.assertEqual([255, 255, 255, 255], - color_util.rgb_hex_to_rgb_list('ffffffff')) + assert [255, 255, 255, 255] == \ + color_util.rgb_hex_to_rgb_list('ffffffff') - self.assertEqual([0, 0, 0, 0], - color_util.rgb_hex_to_rgb_list('00000000')) + assert [0, 0, 0, 0] == \ + color_util.rgb_hex_to_rgb_list('00000000') - self.assertEqual([51, 153, 255], - color_util.rgb_hex_to_rgb_list('3399ff')) + assert [51, 153, 255] == \ + color_util.rgb_hex_to_rgb_list('3399ff') - self.assertEqual([51, 153, 255, 0], - color_util.rgb_hex_to_rgb_list('3399ff00')) + assert [51, 153, 255, 0] == \ + color_util.rgb_hex_to_rgb_list('3399ff00') def test_color_name_to_rgb_valid_name(self): """Test color_name_to_rgb.""" - self.assertEqual((255, 0, 0), - color_util.color_name_to_rgb('red')) + assert (255, 0, 0) == \ + color_util.color_name_to_rgb('red') - self.assertEqual((0, 0, 255), - color_util.color_name_to_rgb('blue')) + assert (0, 0, 255) == \ + color_util.color_name_to_rgb('blue') - self.assertEqual((0, 128, 0), - color_util.color_name_to_rgb('green')) + assert (0, 128, 0) == \ + color_util.color_name_to_rgb('green') # spaces in the name - self.assertEqual((72, 61, 139), - color_util.color_name_to_rgb('dark slate blue')) + assert (72, 61, 139) == \ + color_util.color_name_to_rgb('dark slate blue') # spaces removed from name - self.assertEqual((72, 61, 139), - color_util.color_name_to_rgb('darkslateblue')) - self.assertEqual((72, 61, 139), - color_util.color_name_to_rgb('dark slateblue')) - self.assertEqual((72, 61, 139), - color_util.color_name_to_rgb('darkslate blue')) + assert (72, 61, 139) == \ + color_util.color_name_to_rgb('darkslateblue') + assert (72, 61, 139) == \ + color_util.color_name_to_rgb('dark slateblue') + assert (72, 61, 139) == \ + color_util.color_name_to_rgb('darkslate blue') def test_color_name_to_rgb_unknown_name_raises_value_error(self): """Test color_name_to_rgb.""" @@ -217,55 +217,55 @@ class TestColorUtil(unittest.TestCase): def test_color_rgb_to_rgbw(self): """Test color_rgb_to_rgbw.""" - self.assertEqual((0, 0, 0, 0), - color_util.color_rgb_to_rgbw(0, 0, 0)) + assert (0, 0, 0, 0) == \ + color_util.color_rgb_to_rgbw(0, 0, 0) - self.assertEqual((0, 0, 0, 255), - color_util.color_rgb_to_rgbw(255, 255, 255)) + assert (0, 0, 0, 255) == \ + color_util.color_rgb_to_rgbw(255, 255, 255) - self.assertEqual((255, 0, 0, 0), - color_util.color_rgb_to_rgbw(255, 0, 0)) + assert (255, 0, 0, 0) == \ + color_util.color_rgb_to_rgbw(255, 0, 0) - self.assertEqual((0, 255, 0, 0), - color_util.color_rgb_to_rgbw(0, 255, 0)) + assert (0, 255, 0, 0) == \ + color_util.color_rgb_to_rgbw(0, 255, 0) - self.assertEqual((0, 0, 255, 0), - color_util.color_rgb_to_rgbw(0, 0, 255)) + assert (0, 0, 255, 0) == \ + color_util.color_rgb_to_rgbw(0, 0, 255) - self.assertEqual((255, 127, 0, 0), - color_util.color_rgb_to_rgbw(255, 127, 0)) + assert (255, 127, 0, 0) == \ + color_util.color_rgb_to_rgbw(255, 127, 0) - self.assertEqual((255, 0, 0, 253), - color_util.color_rgb_to_rgbw(255, 127, 127)) + assert (255, 0, 0, 253) == \ + color_util.color_rgb_to_rgbw(255, 127, 127) - self.assertEqual((0, 0, 0, 127), - color_util.color_rgb_to_rgbw(127, 127, 127)) + assert (0, 0, 0, 127) == \ + color_util.color_rgb_to_rgbw(127, 127, 127) def test_color_rgbw_to_rgb(self): """Test color_rgbw_to_rgb.""" - self.assertEqual((0, 0, 0), - color_util.color_rgbw_to_rgb(0, 0, 0, 0)) + assert (0, 0, 0) == \ + color_util.color_rgbw_to_rgb(0, 0, 0, 0) - self.assertEqual((255, 255, 255), - color_util.color_rgbw_to_rgb(0, 0, 0, 255)) + assert (255, 255, 255) == \ + color_util.color_rgbw_to_rgb(0, 0, 0, 255) - self.assertEqual((255, 0, 0), - color_util.color_rgbw_to_rgb(255, 0, 0, 0)) + assert (255, 0, 0) == \ + color_util.color_rgbw_to_rgb(255, 0, 0, 0) - self.assertEqual((0, 255, 0), - color_util.color_rgbw_to_rgb(0, 255, 0, 0)) + assert (0, 255, 0) == \ + color_util.color_rgbw_to_rgb(0, 255, 0, 0) - self.assertEqual((0, 0, 255), - color_util.color_rgbw_to_rgb(0, 0, 255, 0)) + assert (0, 0, 255) == \ + color_util.color_rgbw_to_rgb(0, 0, 255, 0) - self.assertEqual((255, 127, 0), - color_util.color_rgbw_to_rgb(255, 127, 0, 0)) + assert (255, 127, 0) == \ + color_util.color_rgbw_to_rgb(255, 127, 0, 0) - self.assertEqual((255, 127, 127), - color_util.color_rgbw_to_rgb(255, 0, 0, 253)) + assert (255, 127, 127) == \ + color_util.color_rgbw_to_rgb(255, 0, 0, 253) - self.assertEqual((127, 127, 127), - color_util.color_rgbw_to_rgb(0, 0, 0, 127)) + assert (127, 127, 127) == \ + color_util.color_rgbw_to_rgb(0, 0, 0, 127) def test_color_rgb_to_hex(self): """Test color_rgb_to_hex.""" @@ -281,12 +281,12 @@ class ColorTemperatureMiredToKelvinTests(unittest.TestCase): def test_should_return_25000_kelvin_when_input_is_40_mired(self): """Function should return 25000K if given 40 mired.""" kelvin = color_util.color_temperature_mired_to_kelvin(40) - self.assertEqual(25000, kelvin) + assert 25000 == kelvin def test_should_return_5000_kelvin_when_input_is_200_mired(self): """Function should return 5000K if given 200 mired.""" kelvin = color_util.color_temperature_mired_to_kelvin(200) - self.assertEqual(5000, kelvin) + assert 5000 == kelvin class ColorTemperatureKelvinToMiredTests(unittest.TestCase): @@ -295,12 +295,12 @@ class ColorTemperatureKelvinToMiredTests(unittest.TestCase): def test_should_return_40_mired_when_input_is_25000_kelvin(self): """Function should return 40 mired when given 25000 Kelvin.""" mired = color_util.color_temperature_kelvin_to_mired(25000) - self.assertEqual(40, mired) + assert 40 == mired def test_should_return_200_mired_when_input_is_5000_kelvin(self): """Function should return 200 mired when given 5000 Kelvin.""" mired = color_util.color_temperature_kelvin_to_mired(5000) - self.assertEqual(200, mired) + assert 200 == mired class ColorTemperatureToRGB(unittest.TestCase): @@ -310,13 +310,13 @@ class ColorTemperatureToRGB(unittest.TestCase): """Function should return same value for 999 Kelvin and 0 Kelvin.""" rgb_1 = color_util.color_temperature_to_rgb(999) rgb_2 = color_util.color_temperature_to_rgb(0) - self.assertEqual(rgb_1, rgb_2) + assert rgb_1 == rgb_2 def test_returns_same_value_for_any_two_temperatures_above_40000(self): """Function should return same value for 40001K and 999999K.""" rgb_1 = color_util.color_temperature_to_rgb(40001) rgb_2 = color_util.color_temperature_to_rgb(999999) - self.assertEqual(rgb_1, rgb_2) + assert rgb_1 == rgb_2 def test_should_return_pure_white_at_6600(self): """ @@ -327,19 +327,19 @@ class ColorTemperatureToRGB(unittest.TestCase): guess" approach. """ rgb = color_util.color_temperature_to_rgb(6600) - self.assertEqual((255, 255, 255), rgb) + assert (255, 255, 255) == rgb def test_color_above_6600_should_have_more_blue_than_red_or_green(self): """Function should return a higher blue value for blue-ish light.""" rgb = color_util.color_temperature_to_rgb(6700) - self.assertGreater(rgb[2], rgb[1]) - self.assertGreater(rgb[2], rgb[0]) + assert rgb[2] > rgb[1] + assert rgb[2] > rgb[0] def test_color_below_6600_should_have_more_red_than_blue_or_green(self): """Function should return a higher red value for red-ish light.""" rgb = color_util.color_temperature_to_rgb(6500) - self.assertGreater(rgb[0], rgb[1]) - self.assertGreater(rgb[0], rgb[2]) + assert rgb[0] > rgb[1] + assert rgb[0] > rgb[2] def test_get_color_in_voluptuous(): diff --git a/tests/util/test_distance.py b/tests/util/test_distance.py index 2ad3b42fdb8..162f1a2fa99 100644 --- a/tests/util/test_distance.py +++ b/tests/util/test_distance.py @@ -4,6 +4,7 @@ import unittest import homeassistant.util.distance as distance_util from homeassistant.const import (LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_FEET, LENGTH_MILES) +import pytest INVALID_SYMBOL = 'bob' VALID_SYMBOL = LENGTH_KILOMETERS @@ -14,78 +15,65 @@ class TestDistanceUtil(unittest.TestCase): def test_convert_same_unit(self): """Test conversion from any unit to same unit.""" - self.assertEqual(5, - distance_util.convert(5, LENGTH_KILOMETERS, - LENGTH_KILOMETERS)) - self.assertEqual(2, - distance_util.convert(2, LENGTH_METERS, - LENGTH_METERS)) - self.assertEqual(10, - distance_util.convert(10, LENGTH_MILES, LENGTH_MILES)) - self.assertEqual(9, - distance_util.convert(9, LENGTH_FEET, LENGTH_FEET)) + assert 5 == distance_util.convert(5, LENGTH_KILOMETERS, + LENGTH_KILOMETERS) + assert 2 == distance_util.convert(2, LENGTH_METERS, + LENGTH_METERS) + assert 10 == distance_util.convert(10, LENGTH_MILES, LENGTH_MILES) + assert 9 == distance_util.convert(9, LENGTH_FEET, LENGTH_FEET) def test_convert_invalid_unit(self): """Test exception is thrown for invalid units.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): distance_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): distance_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) def test_convert_nonnumeric_value(self): """Test exception is thrown for nonnumeric type.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): distance_util.convert('a', LENGTH_KILOMETERS, LENGTH_METERS) def test_convert_from_miles(self): """Test conversion from miles to other units.""" miles = 5 - self.assertEqual( - distance_util.convert(miles, LENGTH_MILES, LENGTH_KILOMETERS), - 8.04672) - self.assertEqual( - distance_util.convert(miles, LENGTH_MILES, LENGTH_METERS), - 8046.72) - self.assertEqual( - distance_util.convert(miles, LENGTH_MILES, LENGTH_FEET), - 26400.0008448) + assert distance_util.convert( + miles, LENGTH_MILES, LENGTH_KILOMETERS + ) == 8.04672 + assert distance_util.convert(miles, LENGTH_MILES, LENGTH_METERS) == \ + 8046.72 + assert distance_util.convert(miles, LENGTH_MILES, LENGTH_FEET) == \ + 26400.0008448 def test_convert_from_feet(self): """Test conversion from feet to other units.""" feet = 5000 - self.assertEqual( - distance_util.convert(feet, LENGTH_FEET, LENGTH_KILOMETERS), - 1.524) - self.assertEqual( - distance_util.convert(feet, LENGTH_FEET, LENGTH_METERS), - 1524) - self.assertEqual( - distance_util.convert(feet, LENGTH_FEET, LENGTH_MILES), - 0.9469694040000001) + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_KILOMETERS) == \ + 1.524 + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_METERS) == \ + 1524 + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_MILES) == \ + 0.9469694040000001 def test_convert_from_kilometers(self): """Test conversion from kilometers to other units.""" km = 5 - self.assertEqual( - distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_FEET), - 16404.2) - self.assertEqual( - distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_METERS), - 5000) - self.assertEqual( - distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_MILES), - 3.106855) + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_FEET) == \ + 16404.2 + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_METERS) == \ + 5000 + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_MILES) == \ + 3.106855 def test_convert_from_meters(self): """Test conversion from meters to other units.""" m = 5000 - self.assertEqual(distance_util.convert(m, LENGTH_METERS, LENGTH_FEET), - 16404.2) - self.assertEqual( - distance_util.convert(m, LENGTH_METERS, LENGTH_KILOMETERS), - 5) - self.assertEqual(distance_util.convert(m, LENGTH_METERS, LENGTH_MILES), - 3.106855) + assert distance_util.convert(m, LENGTH_METERS, LENGTH_FEET) == \ + 16404.2 + assert distance_util.convert(m, LENGTH_METERS, LENGTH_KILOMETERS) == \ + 5 + assert distance_util.convert(m, LENGTH_METERS, LENGTH_MILES) == \ + 3.106855 diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 35a83de6bfb..52f55fff345 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -3,6 +3,7 @@ import unittest from datetime import datetime, timedelta import homeassistant.util.dt as dt_util +import pytest TEST_TIME_ZONE = 'America/Los_Angeles' @@ -22,14 +23,14 @@ class TestDateUtil(unittest.TestCase): """Test getting a time zone.""" time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) - self.assertIsNotNone(time_zone) - self.assertEqual(TEST_TIME_ZONE, time_zone.zone) + assert time_zone is not None + assert TEST_TIME_ZONE == time_zone.zone def test_get_time_zone_returns_none_for_garbage_time_zone(self): """Test getting a non existing time zone.""" time_zone = dt_util.get_time_zone("Non existing time zone") - self.assertIsNone(time_zone) + assert time_zone is None def test_set_default_time_zone(self): """Test setting default time zone.""" @@ -38,36 +39,34 @@ class TestDateUtil(unittest.TestCase): dt_util.set_default_time_zone(time_zone) # We cannot compare the timezones directly because of DST - self.assertEqual(time_zone.zone, dt_util.now().tzinfo.zone) + assert time_zone.zone == dt_util.now().tzinfo.zone def test_utcnow(self): """Test the UTC now method.""" - self.assertAlmostEqual( - dt_util.utcnow().replace(tzinfo=None), - datetime.utcnow(), - delta=timedelta(seconds=1)) + assert abs(dt_util.utcnow().replace(tzinfo=None)-datetime.utcnow()) < \ + timedelta(seconds=1) def test_now(self): """Test the now method.""" dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) - self.assertAlmostEqual( - dt_util.as_utc(dt_util.now()).replace(tzinfo=None), - datetime.utcnow(), - delta=timedelta(seconds=1)) + assert abs( + dt_util.as_utc(dt_util.now()).replace( + tzinfo=None + ) - datetime.utcnow() + ) < timedelta(seconds=1) def test_as_utc_with_naive_object(self): """Test the now method.""" utcnow = datetime.utcnow() - self.assertEqual(utcnow, - dt_util.as_utc(utcnow).replace(tzinfo=None)) + assert utcnow == dt_util.as_utc(utcnow).replace(tzinfo=None) def test_as_utc_with_utc_object(self): """Test UTC time with UTC object.""" utcnow = dt_util.utcnow() - self.assertEqual(utcnow, dt_util.as_utc(utcnow)) + assert utcnow == dt_util.as_utc(utcnow) def test_as_utc_with_local_object(self): """Test the UTC time with local object.""" @@ -75,20 +74,19 @@ class TestDateUtil(unittest.TestCase): localnow = dt_util.now() utcnow = dt_util.as_utc(localnow) - self.assertEqual(localnow, utcnow) - self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo) + assert localnow == utcnow + assert localnow.tzinfo != utcnow.tzinfo def test_as_local_with_naive_object(self): """Test local time with native object.""" now = dt_util.now() - self.assertAlmostEqual( - now, dt_util.as_local(datetime.utcnow()), - delta=timedelta(seconds=1)) + assert abs(now-dt_util.as_local(datetime.utcnow())) < \ + timedelta(seconds=1) def test_as_local_with_local_object(self): """Test local with local object.""" now = dt_util.now() - self.assertEqual(now, now) + assert now == now def test_as_local_with_utc_object(self): """Test local time with UTC object.""" @@ -97,27 +95,26 @@ class TestDateUtil(unittest.TestCase): utcnow = dt_util.utcnow() localnow = dt_util.as_local(utcnow) - self.assertEqual(localnow, utcnow) - self.assertNotEqual(localnow.tzinfo, utcnow.tzinfo) + assert localnow == utcnow + assert localnow.tzinfo != utcnow.tzinfo def test_utc_from_timestamp(self): """Test utc_from_timestamp method.""" - self.assertEqual( - datetime(1986, 7, 9, tzinfo=dt_util.UTC), - dt_util.utc_from_timestamp(521251200)) + assert datetime(1986, 7, 9, tzinfo=dt_util.UTC) == \ + dt_util.utc_from_timestamp(521251200) def test_as_timestamp(self): """Test as_timestamp method.""" ts = 1462401234 utc_dt = dt_util.utc_from_timestamp(ts) - self.assertEqual(ts, dt_util.as_timestamp(utc_dt)) + assert ts == dt_util.as_timestamp(utc_dt) utc_iso = utc_dt.isoformat() - self.assertEqual(ts, dt_util.as_timestamp(utc_iso)) + assert ts == dt_util.as_timestamp(utc_iso) # confirm the ability to handle a string passed in delta = dt_util.as_timestamp("2016-01-01 12:12:12") delta -= dt_util.as_timestamp("2016-01-01 12:12:11") - self.assertEqual(1, delta) + assert 1 == delta def test_parse_datetime_converts_correctly(self): """Test parse_datetime converts strings.""" @@ -131,72 +128,61 @@ class TestDateUtil(unittest.TestCase): def test_parse_datetime_returns_none_for_incorrect_format(self): """Test parse_datetime returns None if incorrect format.""" - self.assertIsNone(dt_util.parse_datetime("not a datetime string")) + assert dt_util.parse_datetime("not a datetime string") is None def test_get_age(self): """Test get_age.""" diff = dt_util.now() - timedelta(seconds=0) - self.assertEqual(dt_util.get_age(diff), "0 seconds") + assert dt_util.get_age(diff) == "0 seconds" diff = dt_util.now() - timedelta(seconds=1) - self.assertEqual(dt_util.get_age(diff), "1 second") + assert dt_util.get_age(diff) == "1 second" diff = dt_util.now() - timedelta(seconds=30) - self.assertEqual(dt_util.get_age(diff), "30 seconds") + assert dt_util.get_age(diff) == "30 seconds" diff = dt_util.now() - timedelta(minutes=5) - self.assertEqual(dt_util.get_age(diff), "5 minutes") + assert dt_util.get_age(diff) == "5 minutes" diff = dt_util.now() - timedelta(minutes=1) - self.assertEqual(dt_util.get_age(diff), "1 minute") + assert dt_util.get_age(diff) == "1 minute" diff = dt_util.now() - timedelta(minutes=300) - self.assertEqual(dt_util.get_age(diff), "5 hours") + assert dt_util.get_age(diff) == "5 hours" diff = dt_util.now() - timedelta(minutes=320) - self.assertEqual(dt_util.get_age(diff), "5 hours") + assert dt_util.get_age(diff) == "5 hours" diff = dt_util.now() - timedelta(minutes=2*60*24) - self.assertEqual(dt_util.get_age(diff), "2 days") + assert dt_util.get_age(diff) == "2 days" diff = dt_util.now() - timedelta(minutes=32*60*24) - self.assertEqual(dt_util.get_age(diff), "1 month") + assert dt_util.get_age(diff) == "1 month" diff = dt_util.now() - timedelta(minutes=365*60*24) - self.assertEqual(dt_util.get_age(diff), "1 year") + assert dt_util.get_age(diff) == "1 year" def test_parse_time_expression(self): """Test parse_time_expression.""" - self.assertEqual( - [x for x in range(60)], + assert [x for x in range(60)] == \ dt_util.parse_time_expression('*', 0, 59) - ) - self.assertEqual( - [x for x in range(60)], + assert [x for x in range(60)] == \ dt_util.parse_time_expression(None, 0, 59) - ) - self.assertEqual( - [x for x in range(0, 60, 5)], + assert [x for x in range(0, 60, 5)] == \ dt_util.parse_time_expression('/5', 0, 59) - ) - self.assertEqual( - [1, 2, 3], + assert [1, 2, 3] == \ dt_util.parse_time_expression([2, 1, 3], 0, 59) - ) - self.assertEqual( - [x for x in range(24)], + assert [x for x in range(24)] == \ dt_util.parse_time_expression('*', 0, 23) - ) - self.assertEqual( - [42], + assert [42] == \ dt_util.parse_time_expression(42, 0, 59) - ) - self.assertRaises(ValueError, dt_util.parse_time_expression, 61, 0, 60) + with pytest.raises(ValueError): + dt_util.parse_time_expression(61, 0, 60) def test_find_next_time_expression_time_basic(self): """Test basic stuff for find_next_time_expression_time.""" @@ -209,25 +195,17 @@ class TestDateUtil(unittest.TestCase): return dt_util.find_next_time_expression_time( dt, seconds, minutes, hours) - self.assertEqual( - datetime(2018, 10, 7, 10, 30, 0), + assert datetime(2018, 10, 7, 10, 30, 0) == \ find(datetime(2018, 10, 7, 10, 20, 0), '*', '/30', 0) - ) - self.assertEqual( - datetime(2018, 10, 7, 10, 30, 0), + assert datetime(2018, 10, 7, 10, 30, 0) == \ find(datetime(2018, 10, 7, 10, 30, 0), '*', '/30', 0) - ) - self.assertEqual( - datetime(2018, 10, 7, 12, 30, 30), + assert datetime(2018, 10, 7, 12, 30, 30) == \ find(datetime(2018, 10, 7, 10, 30, 0), '/3', '/30', [30, 45]) - ) - self.assertEqual( - datetime(2018, 10, 8, 5, 0, 0), + assert datetime(2018, 10, 8, 5, 0, 0) == \ find(datetime(2018, 10, 7, 10, 30, 0), 5, 0, 0) - ) def test_find_next_time_expression_time_dst(self): """Test daylight saving time for find_next_time_expression_time.""" @@ -244,48 +222,32 @@ class TestDateUtil(unittest.TestCase): dt, seconds, minutes, hours) # Entering DST, clocks are rolled forward - self.assertEqual( - tz.localize(datetime(2018, 3, 26, 2, 30, 0)), + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ find(tz.localize(datetime(2018, 3, 25, 1, 50, 0)), 2, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 3, 26, 2, 30, 0)), + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ find(tz.localize(datetime(2018, 3, 25, 3, 50, 0)), 2, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 3, 26, 2, 30, 0)), + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ find(tz.localize(datetime(2018, 3, 26, 1, 50, 0)), 2, 30, 0) - ) # Leaving DST, clocks are rolled back - self.assertEqual( - tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False), + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False), 2, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False), + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), 2, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 10, 28, 4, 30, 0), is_dst=False), + assert tz.localize(datetime(2018, 10, 28, 4, 30, 0), is_dst=False) == \ find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), 4, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=True), + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=True) == \ find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True), 2, 30, 0) - ) - self.assertEqual( - tz.localize(datetime(2018, 10, 29, 2, 30, 0)), + assert tz.localize(datetime(2018, 10, 29, 2, 30, 0)) == \ find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False), 2, 30, 0) - ) diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 1f43c5a4b49..10b4fe0ad8c 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -5,6 +5,7 @@ from datetime import datetime, timedelta from homeassistant import util import homeassistant.util.dt as dt_util +import pytest class TestUtil(unittest.TestCase): @@ -12,60 +13,56 @@ class TestUtil(unittest.TestCase): def test_sanitize_filename(self): """Test sanitize_filename.""" - self.assertEqual("test", util.sanitize_filename("test")) - self.assertEqual("test", util.sanitize_filename("/test")) - self.assertEqual("test", util.sanitize_filename("..test")) - self.assertEqual("test", util.sanitize_filename("\\test")) - self.assertEqual("test", util.sanitize_filename("\\../test")) + assert "test" == util.sanitize_filename("test") + assert "test" == util.sanitize_filename("/test") + assert "test" == util.sanitize_filename("..test") + assert "test" == util.sanitize_filename("\\test") + assert "test" == util.sanitize_filename("\\../test") def test_sanitize_path(self): """Test sanitize_path.""" - self.assertEqual("test/path", util.sanitize_path("test/path")) - self.assertEqual("test/path", util.sanitize_path("~test/path")) - self.assertEqual("//test/path", - util.sanitize_path("~/../test/path")) + assert "test/path" == util.sanitize_path("test/path") + assert "test/path" == util.sanitize_path("~test/path") + assert "//test/path" == util.sanitize_path("~/../test/path") def test_slugify(self): """Test slugify.""" - self.assertEqual("test", util.slugify("T-!@#$!#@$!$est")) - self.assertEqual("test_more", util.slugify("Test More")) - self.assertEqual("test_more", util.slugify("Test_(More)")) - self.assertEqual("test_more", util.slugify("Tèst_Mörê")) - self.assertEqual("b827eb000000", util.slugify("B8:27:EB:00:00:00")) - self.assertEqual("testcom", util.slugify("test.com")) - self.assertEqual("greg_phone__exp_wayp1", - util.slugify("greg_phone - exp_wayp1")) - self.assertEqual("we_are_we_are_a_test_calendar", - util.slugify("We are, we are, a... Test Calendar")) - self.assertEqual("test_aouss_aou", util.slugify("Tèst_äöüß_ÄÖÜ")) + assert "test" == util.slugify("T-!@#$!#@$!$est") + assert "test_more" == util.slugify("Test More") + assert "test_more" == util.slugify("Test_(More)") + assert "test_more" == util.slugify("Tèst_Mörê") + assert "b827eb000000" == util.slugify("B8:27:EB:00:00:00") + assert "testcom" == util.slugify("test.com") + assert "greg_phone__exp_wayp1" == \ + util.slugify("greg_phone - exp_wayp1") + assert "we_are_we_are_a_test_calendar" == \ + util.slugify("We are, we are, a... Test Calendar") + assert "test_aouss_aou" == util.slugify("Tèst_äöüß_ÄÖÜ") def test_repr_helper(self): """Test repr_helper.""" - self.assertEqual("A", util.repr_helper("A")) - self.assertEqual("5", util.repr_helper(5)) - self.assertEqual("True", util.repr_helper(True)) - self.assertEqual("test=1", - util.repr_helper({"test": 1})) - self.assertEqual("1986-07-09T12:00:00+00:00", - util.repr_helper(datetime(1986, 7, 9, 12, 0, 0))) + assert "A" == util.repr_helper("A") + assert "5" == util.repr_helper(5) + assert "True" == util.repr_helper(True) + assert "test=1" == util.repr_helper({"test": 1}) + assert "1986-07-09T12:00:00+00:00" == \ + util.repr_helper(datetime(1986, 7, 9, 12, 0, 0)) def test_convert(self): """Test convert.""" - self.assertEqual(5, util.convert("5", int)) - self.assertEqual(5.0, util.convert("5", float)) - self.assertEqual(True, util.convert("True", bool)) - self.assertEqual(1, util.convert("NOT A NUMBER", int, 1)) - self.assertEqual(1, util.convert(None, int, 1)) - self.assertEqual(1, util.convert(object, int, 1)) + assert 5 == util.convert("5", int) + assert 5.0 == util.convert("5", float) + assert util.convert("True", bool) is True + assert 1 == util.convert("NOT A NUMBER", int, 1) + assert 1 == util.convert(None, int, 1) + assert 1 == util.convert(object, int, 1) def test_ensure_unique_string(self): """Test ensure_unique_string.""" - self.assertEqual( - "Beer_3", - util.ensure_unique_string("Beer", ["Beer", "Beer_2"])) - self.assertEqual( - "Beer", - util.ensure_unique_string("Beer", ["Wine", "Soda"])) + assert "Beer_3" == \ + util.ensure_unique_string("Beer", ["Beer", "Beer_2"]) + assert "Beer" == \ + util.ensure_unique_string("Beer", ["Wine", "Soda"]) def test_ordered_enum(self): """Test the ordered enum class.""" @@ -76,96 +73,96 @@ class TestUtil(unittest.TestCase): SECOND = 2 THIRD = 3 - self.assertTrue(TestEnum.SECOND >= TestEnum.FIRST) - self.assertTrue(TestEnum.SECOND >= TestEnum.SECOND) - self.assertFalse(TestEnum.SECOND >= TestEnum.THIRD) + assert TestEnum.SECOND >= TestEnum.FIRST + assert TestEnum.SECOND >= TestEnum.SECOND + assert not (TestEnum.SECOND >= TestEnum.THIRD) - self.assertTrue(TestEnum.SECOND > TestEnum.FIRST) - self.assertFalse(TestEnum.SECOND > TestEnum.SECOND) - self.assertFalse(TestEnum.SECOND > TestEnum.THIRD) + assert TestEnum.SECOND > TestEnum.FIRST + assert not (TestEnum.SECOND > TestEnum.SECOND) + assert not (TestEnum.SECOND > TestEnum.THIRD) - self.assertFalse(TestEnum.SECOND <= TestEnum.FIRST) - self.assertTrue(TestEnum.SECOND <= TestEnum.SECOND) - self.assertTrue(TestEnum.SECOND <= TestEnum.THIRD) + assert not (TestEnum.SECOND <= TestEnum.FIRST) + assert TestEnum.SECOND <= TestEnum.SECOND + assert TestEnum.SECOND <= TestEnum.THIRD - self.assertFalse(TestEnum.SECOND < TestEnum.FIRST) - self.assertFalse(TestEnum.SECOND < TestEnum.SECOND) - self.assertTrue(TestEnum.SECOND < TestEnum.THIRD) + assert not (TestEnum.SECOND < TestEnum.FIRST) + assert not (TestEnum.SECOND < TestEnum.SECOND) + assert TestEnum.SECOND < TestEnum.THIRD # Python will raise a TypeError if the <, <=, >, >= methods # raise a NotImplemented error. - self.assertRaises(TypeError, - lambda x, y: x < y, TestEnum.FIRST, 1) + with pytest.raises(TypeError): + TestEnum.FIRST < 1 - self.assertRaises(TypeError, - lambda x, y: x <= y, TestEnum.FIRST, 1) + with pytest.raises(TypeError): + TestEnum.FIRST <= 1 - self.assertRaises(TypeError, - lambda x, y: x > y, TestEnum.FIRST, 1) + with pytest.raises(TypeError): + TestEnum.FIRST > 1 - self.assertRaises(TypeError, - lambda x, y: x >= y, TestEnum.FIRST, 1) + with pytest.raises(TypeError): + TestEnum.FIRST >= 1 def test_ordered_set(self): """Test ordering of set.""" set1 = util.OrderedSet([1, 2, 3, 4]) set2 = util.OrderedSet([3, 4, 5]) - self.assertEqual(4, len(set1)) - self.assertEqual(3, len(set2)) + assert 4 == len(set1) + assert 3 == len(set2) - self.assertIn(1, set1) - self.assertIn(2, set1) - self.assertIn(3, set1) - self.assertIn(4, set1) - self.assertNotIn(5, set1) + assert 1 in set1 + assert 2 in set1 + assert 3 in set1 + assert 4 in set1 + assert 5 not in set1 - self.assertNotIn(1, set2) - self.assertNotIn(2, set2) - self.assertIn(3, set2) - self.assertIn(4, set2) - self.assertIn(5, set2) + assert 1 not in set2 + assert 2 not in set2 + assert 3 in set2 + assert 4 in set2 + assert 5 in set2 set1.add(5) - self.assertIn(5, set1) + assert 5 in set1 set1.discard(5) - self.assertNotIn(5, set1) + assert 5 not in set1 # Try again while key is not in set1.discard(5) - self.assertNotIn(5, set1) + assert 5 not in set1 - self.assertEqual([1, 2, 3, 4], list(set1)) - self.assertEqual([4, 3, 2, 1], list(reversed(set1))) + assert [1, 2, 3, 4] == list(set1) + assert [4, 3, 2, 1] == list(reversed(set1)) - self.assertEqual(1, set1.pop(False)) - self.assertEqual([2, 3, 4], list(set1)) + assert 1 == set1.pop(False) + assert [2, 3, 4] == list(set1) - self.assertEqual(4, set1.pop()) - self.assertEqual([2, 3], list(set1)) + assert 4 == set1.pop() + assert [2, 3] == list(set1) - self.assertEqual('OrderedSet()', str(util.OrderedSet())) - self.assertEqual('OrderedSet([2, 3])', str(set1)) + assert 'OrderedSet()' == str(util.OrderedSet()) + assert 'OrderedSet([2, 3])' == str(set1) - self.assertEqual(set1, util.OrderedSet([2, 3])) - self.assertNotEqual(set1, util.OrderedSet([3, 2])) - self.assertEqual(set1, set([2, 3])) - self.assertEqual(set1, {3, 2}) - self.assertEqual(set1, [2, 3]) - self.assertEqual(set1, [3, 2]) - self.assertNotEqual(set1, {2}) + assert set1 == util.OrderedSet([2, 3]) + assert set1 != util.OrderedSet([3, 2]) + assert set1 == set([2, 3]) + assert set1 == {3, 2} + assert set1 == [2, 3] + assert set1 == [3, 2] + assert set1 != {2} set3 = util.OrderedSet(set1) set3.update(set2) - self.assertEqual([3, 4, 5, 2], set3) - self.assertEqual([3, 4, 5, 2], set1 | set2) - self.assertEqual([3], set1 & set2) - self.assertEqual([2], set1 - set2) + assert [3, 4, 5, 2] == set3 + assert [3, 4, 5, 2] == set1 | set2 + assert [3] == set1 & set2 + assert [2] == set1 - set2 set1.update([1, 2], [5, 6]) - self.assertEqual([2, 3, 1, 5, 6], set1) + assert [2, 3, 1, 5, 6] == set1 def test_throttle(self): """Test the add cooldown decorator.""" @@ -188,36 +185,36 @@ class TestUtil(unittest.TestCase): test_throttle1() test_throttle2() - self.assertEqual(1, len(calls1)) - self.assertEqual(1, len(calls2)) + assert 1 == len(calls1) + assert 1 == len(calls2) # Call second time. Methods should not get called test_throttle1() test_throttle2() - self.assertEqual(1, len(calls1)) - self.assertEqual(1, len(calls2)) + assert 1 == len(calls1) + assert 1 == len(calls2) # Call again, overriding throttle, only first one should fire test_throttle1(no_throttle=True) test_throttle2(no_throttle=True) - self.assertEqual(2, len(calls1)) - self.assertEqual(1, len(calls2)) + assert 2 == len(calls1) + assert 1 == len(calls2) with patch('homeassistant.util.utcnow', return_value=plus3): test_throttle1() test_throttle2() - self.assertEqual(2, len(calls1)) - self.assertEqual(1, len(calls2)) + assert 2 == len(calls1) + assert 1 == len(calls2) with patch('homeassistant.util.utcnow', return_value=plus5): test_throttle1() test_throttle2() - self.assertEqual(3, len(calls1)) - self.assertEqual(2, len(calls2)) + assert 3 == len(calls1) + assert 2 == len(calls2) def test_throttle_per_instance(self): """Test that the throttle method is done per instance of a class.""" @@ -229,8 +226,8 @@ class TestUtil(unittest.TestCase): """Test the throttle.""" return True - self.assertTrue(Tester().hello()) - self.assertTrue(Tester().hello()) + assert Tester().hello() + assert Tester().hello() def test_throttle_on_method(self): """Test that throttle works when wrapping a method.""" @@ -244,8 +241,8 @@ class TestUtil(unittest.TestCase): tester = Tester() throttled = util.Throttle(timedelta(seconds=1))(tester.hello) - self.assertTrue(throttled()) - self.assertIsNone(throttled()) + assert throttled() + assert throttled() is None def test_throttle_on_two_method(self): """Test that throttle works when wrapping two methods.""" @@ -264,8 +261,8 @@ class TestUtil(unittest.TestCase): tester = Tester() - self.assertTrue(tester.hello()) - self.assertTrue(tester.goodbye()) + assert tester.hello() + assert tester.goodbye() @patch.object(util, 'random') def test_get_random_string(self, mock_random): diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 53f62682b5e..414a9f400aa 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -7,6 +7,7 @@ from tempfile import mkdtemp from homeassistant.util.json import (SerializationError, load_json, save_json) from homeassistant.exceptions import HomeAssistantError +import pytest # Test data that can be saved as JSON TEST_JSON_A = {"a": 1, "B": "two"} @@ -38,7 +39,7 @@ class TestJSON(unittest.TestCase): fname = self._path_for("test1") save_json(fname, TEST_JSON_A) data = load_json(fname) - self.assertEqual(data, TEST_JSON_A) + assert data == TEST_JSON_A # Skipped on Windows @unittest.skipIf(sys.platform.startswith('win'), @@ -48,9 +49,9 @@ class TestJSON(unittest.TestCase): fname = self._path_for("test2") save_json(fname, TEST_JSON_A, private=True) data = load_json(fname) - self.assertEqual(data, TEST_JSON_A) + assert data == TEST_JSON_A stats = os.stat(fname) - self.assertEqual(stats.st_mode & 0o77, 0) + assert stats.st_mode & 0o77 == 0 def test_overwrite_and_reload(self): """Test that we can overwrite an existing file and read back.""" @@ -58,12 +59,12 @@ class TestJSON(unittest.TestCase): save_json(fname, TEST_JSON_A) save_json(fname, TEST_JSON_B) data = load_json(fname) - self.assertEqual(data, TEST_JSON_B) + assert data == TEST_JSON_B def test_save_bad_data(self): """Test error from trying to save unserialisable data.""" fname = self._path_for("test4") - with self.assertRaises(SerializationError): + with pytest.raises(SerializationError): save_json(fname, TEST_BAD_OBJECT) def test_load_bad_data(self): @@ -71,5 +72,5 @@ class TestJSON(unittest.TestCase): fname = self._path_for("test5") with open(fname, "w") as fh: fh.write(TEST_BAD_SERIALIED) - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): load_json(fname) diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 83edb056139..31b2d49b4ec 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -17,6 +17,7 @@ from homeassistant.const import ( TEMPERATURE, VOLUME ) +import pytest SYSTEM_NAME = 'TEST' INVALID_UNIT = 'INVALID' @@ -27,27 +28,27 @@ class TestUnitSystem(unittest.TestCase): def test_invalid_units(self): """Test errors are raised when invalid units are passed in.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, VOLUME_LITERS, MASS_GRAMS) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, VOLUME_LITERS, MASS_GRAMS) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, MASS_GRAMS) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, INVALID_UNIT) def test_invalid_value(self): """Test no conversion happens if value is non-numeric.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): METRIC_SYSTEM.length('25a', LENGTH_KILOMETERS) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): METRIC_SYSTEM.temperature('50K', TEMP_CELSIUS) def test_as_dict(self): @@ -59,75 +60,62 @@ class TestUnitSystem(unittest.TestCase): MASS: MASS_GRAMS } - self.assertEqual(expected, METRIC_SYSTEM.as_dict()) + assert expected == METRIC_SYSTEM.as_dict() def test_temperature_same_unit(self): """Test no conversion happens if to unit is same as from unit.""" - self.assertEqual( - 5, + assert 5 == \ METRIC_SYSTEM.temperature(5, - METRIC_SYSTEM.temperature_unit)) + METRIC_SYSTEM.temperature_unit) def test_temperature_unknown_unit(self): """Test no conversion happens if unknown unit.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): METRIC_SYSTEM.temperature(5, 'K') def test_temperature_to_metric(self): """Test temperature conversion to metric system.""" - self.assertEqual( - 25, - METRIC_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit)) - self.assertEqual( - 26.7, + assert 25 == \ + METRIC_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit) + assert 26.7 == \ round(METRIC_SYSTEM.temperature( - 80, IMPERIAL_SYSTEM.temperature_unit), 1)) + 80, IMPERIAL_SYSTEM.temperature_unit), 1) def test_temperature_to_imperial(self): """Test temperature conversion to imperial system.""" - self.assertEqual( - 77, - IMPERIAL_SYSTEM.temperature(77, IMPERIAL_SYSTEM.temperature_unit)) - self.assertEqual( - 77, - IMPERIAL_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit)) + assert 77 == \ + IMPERIAL_SYSTEM.temperature(77, IMPERIAL_SYSTEM.temperature_unit) + assert 77 == \ + IMPERIAL_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit) def test_length_unknown_unit(self): """Test length conversion with unknown from unit.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): METRIC_SYSTEM.length(5, 'fr') def test_length_to_metric(self): """Test length conversion to metric system.""" - self.assertEqual( - 100, + assert 100 == \ METRIC_SYSTEM.length(100, METRIC_SYSTEM.length_unit) - ) - self.assertEqual( - 8.04672, + assert 8.04672 == \ METRIC_SYSTEM.length(5, IMPERIAL_SYSTEM.length_unit) - ) def test_length_to_imperial(self): """Test length conversion to imperial system.""" - self.assertEqual( - 100, + assert 100 == \ IMPERIAL_SYSTEM.length(100, IMPERIAL_SYSTEM.length_unit) - ) - self.assertEqual( - 3.106855, + assert 3.106855 == \ IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) - ) def test_properties(self): """Test the unit properties are returned as expected.""" - self.assertEqual(LENGTH_KILOMETERS, METRIC_SYSTEM.length_unit) - self.assertEqual(TEMP_CELSIUS, METRIC_SYSTEM.temperature_unit) - self.assertEqual(MASS_GRAMS, METRIC_SYSTEM.mass_unit) - self.assertEqual(VOLUME_LITERS, METRIC_SYSTEM.volume_unit) + assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit + assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit + assert MASS_GRAMS == METRIC_SYSTEM.mass_unit + assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit def test_is_metric(self): """Test the is metric flag.""" - self.assertTrue(METRIC_SYSTEM.is_metric) - self.assertFalse(IMPERIAL_SYSTEM.is_metric) + assert METRIC_SYSTEM.is_metric + assert not IMPERIAL_SYSTEM.is_metric diff --git a/tests/util/test_volume.py b/tests/util/test_volume.py index e78e099d7d7..26208d37b68 100644 --- a/tests/util/test_volume.py +++ b/tests/util/test_volume.py @@ -4,6 +4,7 @@ import unittest import homeassistant.util.volume as volume_util from homeassistant.const import (VOLUME_LITERS, VOLUME_MILLILITERS, VOLUME_GALLONS, VOLUME_FLUID_OUNCE) +import pytest INVALID_SYMBOL = 'bob' VALID_SYMBOL = VOLUME_LITERS @@ -14,36 +15,35 @@ class TestVolumeUtil(unittest.TestCase): def test_convert_same_unit(self): """Test conversion from any unit to same unit.""" - self.assertEqual(2, volume_util.convert(2, VOLUME_LITERS, - VOLUME_LITERS)) - self.assertEqual(3, volume_util.convert(3, VOLUME_MILLILITERS, - VOLUME_MILLILITERS)) - self.assertEqual(4, volume_util.convert(4, VOLUME_GALLONS, - VOLUME_GALLONS)) - self.assertEqual(5, volume_util.convert(5, VOLUME_FLUID_OUNCE, - VOLUME_FLUID_OUNCE)) + assert 2 == volume_util.convert(2, VOLUME_LITERS, VOLUME_LITERS) + assert 3 == volume_util.convert(3, VOLUME_MILLILITERS, + VOLUME_MILLILITERS) + assert 4 == volume_util.convert(4, VOLUME_GALLONS, + VOLUME_GALLONS) + assert 5 == volume_util.convert(5, VOLUME_FLUID_OUNCE, + VOLUME_FLUID_OUNCE) def test_convert_invalid_unit(self): """Test exception is thrown for invalid units.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): volume_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): volume_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) def test_convert_nonnumeric_value(self): """Test exception is thrown for nonnumeric type.""" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): volume_util.convert('a', VOLUME_GALLONS, VOLUME_LITERS) def test_convert_from_liters(self): """Test conversion from liters to other units.""" liters = 5 - self.assertEqual(volume_util.convert(liters, VOLUME_LITERS, - VOLUME_GALLONS), 1.321) + assert volume_util.convert(liters, VOLUME_LITERS, + VOLUME_GALLONS) == 1.321 def test_convert_from_gallons(self): """Test conversion from gallons to other units.""" gallons = 5 - self.assertEqual(volume_util.convert(gallons, VOLUME_GALLONS, - VOLUME_LITERS), 18.925) + assert volume_util.convert(gallons, VOLUME_GALLONS, + VOLUME_LITERS) == 18.925 diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index d08915b348b..2eab75cb92a 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -43,14 +43,14 @@ class TestYaml(unittest.TestCase): def test_unhashable_key(self): """Test an unhasable key.""" files = {YAML_CONFIG_FILE: 'message:\n {{ states.state }}'} - with self.assertRaises(HomeAssistantError), \ + with pytest.raises(HomeAssistantError), \ patch_yaml_files(files): load_yaml_config_file(YAML_CONFIG_FILE) def test_no_key(self): """Test item without a key.""" files = {YAML_CONFIG_FILE: 'a: a\nnokeyhere'} - with self.assertRaises(HomeAssistantError), \ + with pytest.raises(HomeAssistantError), \ patch_yaml_files(files): yaml.load_yaml(YAML_CONFIG_FILE) @@ -73,7 +73,7 @@ class TestYaml(unittest.TestCase): def test_invalid_environment_variable(self): """Test config file with no environment variable sat.""" conf = "password: !env_var PASSWORD" - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): with io.StringIO(conf) as file: yaml.yaml.safe_load(file) @@ -261,7 +261,8 @@ class TestYaml(unittest.TestCase): def test_load_yaml_encoding_error(self, mock_open): """Test raising a UnicodeDecodeError.""" mock_open.side_effect = UnicodeDecodeError('', b'', 1, 0, '') - self.assertRaises(HomeAssistantError, yaml.load_yaml, 'test') + with pytest.raises(HomeAssistantError): + yaml.load_yaml('test') def test_dump(self): """The that the dump method returns empty None values.""" @@ -332,12 +333,12 @@ class TestSecrets(unittest.TestCase): def test_secrets_from_yaml(self): """Did secrets load ok.""" expected = {'api_password': 'pwhttp'} - self.assertEqual(expected, self._yaml['http']) + assert expected == self._yaml['http'] expected = { 'username': 'un1', 'password': 'pw1'} - self.assertEqual(expected, self._yaml['component']) + assert expected == self._yaml['component'] def test_secrets_from_parent_folder(self): """Test loading secrets from parent foler.""" @@ -350,7 +351,7 @@ class TestSecrets(unittest.TestCase): ' password: !secret comp1_pw\n' '') - self.assertEqual(expected, self._yaml['http']) + assert expected == self._yaml['http'] def test_secret_overrides_parent(self): """Test loading current directory secret overrides the parent.""" @@ -365,13 +366,13 @@ class TestSecrets(unittest.TestCase): ' password: !secret comp1_pw\n' '') - self.assertEqual(expected, self._yaml['http']) + assert expected == self._yaml['http'] def test_secrets_from_unrelated_fails(self): """Test loading secrets from unrelated folder fails.""" load_yaml(os.path.join(self._unrelated_path, yaml.SECRET_YAML), 'test: failure') - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): load_yaml(os.path.join(self._sub_folder_path, 'sub.yaml'), 'http:\n' ' api_password: !secret test') @@ -380,12 +381,12 @@ class TestSecrets(unittest.TestCase): """Test keyring fallback & get_password.""" yaml.keyring = None # Ensure its not there yaml_str = 'http:\n api_password: !secret http_pw_keyring' - with self.assertRaises(yaml.HomeAssistantError): + with pytest.raises(yaml.HomeAssistantError): load_yaml(self._yaml_path, yaml_str) yaml.keyring = FakeKeyring({'http_pw_keyring': 'yeah'}) _yaml = load_yaml(self._yaml_path, yaml_str) - self.assertEqual({'http': {'api_password': 'yeah'}}, _yaml) + assert {'http': {'api_password': 'yeah'}} == _yaml @patch.object(yaml, 'credstash') def test_secrets_credstash(self, mock_credstash): @@ -395,11 +396,11 @@ class TestSecrets(unittest.TestCase): _yaml = load_yaml(self._yaml_path, yaml_str) log = logging.getLogger() log.error(_yaml['http']) - self.assertEqual({'api_password': 'yeah'}, _yaml['http']) + assert {'api_password': 'yeah'} == _yaml['http'] def test_secrets_logger_removed(self): """Ensure logger: debug was removed.""" - with self.assertRaises(yaml.HomeAssistantError): + with pytest.raises(yaml.HomeAssistantError): load_yaml(self._yaml_path, 'api_password: !secret logger') @patch('homeassistant.util.yaml._LOGGER.error') @@ -418,7 +419,7 @@ class TestSecrets(unittest.TestCase): ' comp1_un: un1\n' ' comp1_pw: pw1\n') yaml.clear_secret_cache() - with self.assertRaises(HomeAssistantError): + with pytest.raises(HomeAssistantError): load_yaml(self._yaml_path, 'http:\n' ' api_password: !secret http_pw\n' From a10fb94e9eac106aba4effe7b6b050e23a2ab921 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Wed, 24 Oct 2018 04:37:07 -0700 Subject: [PATCH 034/230] Remove webhook_id from yaml config for mailgun (#17732) --- homeassistant/components/mailgun/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index 25f697084d3..e52bc663c9a 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -23,7 +23,6 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_DOMAIN): cv.string, vol.Optional(CONF_SANDBOX, default=DEFAULT_SANDBOX): cv.boolean, - vol.Optional(CONF_WEBHOOK_ID): cv.string, }), }, extra=vol.ALLOW_EXTRA) From ad3d7c9523b83423e1db959814052b0c6366d8fc Mon Sep 17 00:00:00 2001 From: Hedda Date: Wed, 24 Oct 2018 14:12:30 +0200 Subject: [PATCH 035/230] Update zha __init__.py to reflect new Zigbee name stylization by the Zigbee Alliance (#17751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zigbee Alliance has changed their stylized writing (and logo) of the Zigbee name from “ZigBee” to “Zigbee”, as in they are no longer writing Zigbee with a capital “B” in the middle of the name. --- homeassistant/components/zha/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 8cea746f89a..228e589ab01 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,5 +1,5 @@ """ -Support for ZigBee Home Automation devices. +Support for Zigbee Home Automation devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ @@ -71,7 +71,7 @@ SERVICE_SCHEMAS = { } -# ZigBee definitions +# Zigbee definitions CENTICELSIUS = 'C-100' # Key in hass.data dict containing discovery info DISCOVERY_KEY = 'zha_discovery_info' From 86e67e4712390a3c118d9f167df4393e3c22fd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Wed, 24 Oct 2018 14:13:07 +0200 Subject: [PATCH 036/230] Clean up clicksend_tts (#17749) * Clean up clicksend_tts * style --- .../components/notify/clicksend_tts.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/notify/clicksend_tts.py b/homeassistant/components/notify/clicksend_tts.py index 26a29993290..c60e02ece1a 100644 --- a/homeassistant/components/notify/clicksend_tts.py +++ b/homeassistant/components/notify/clicksend_tts.py @@ -9,15 +9,15 @@ https://home-assistant.io/components/notify.clicksend_tts/ import json import logging -from aiohttp.hdrs import CONTENT_TYPE import requests import voluptuous as vol +from aiohttp.hdrs import CONTENT_TYPE +import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import ( CONF_API_KEY, CONF_USERNAME, CONF_RECIPIENT, CONTENT_TYPE_JSON) -import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -30,6 +30,7 @@ CONF_VOICE = 'voice' DEFAULT_LANGUAGE = 'en-us' DEFAULT_VOICE = 'female' +TIMEOUT = 5 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, @@ -42,7 +43,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def get_service(hass, config, discovery_info=None): """Get the ClickSend notification service.""" - if _authenticate(config) is False: + if not _authenticate(config): _LOGGER.error("You are not authorized to access ClickSend") return None @@ -66,15 +67,19 @@ class ClicksendNotificationService(BaseNotificationService): 'to': self.recipient, 'body': message, 'lang': self.language, 'voice': self.voice}]}) api_url = "{}/voice/send".format(BASE_API_URL) - resp = requests.post(api_url, data=json.dumps(data), headers=HEADERS, - auth=(self.username, self.api_key), timeout=5) + resp = requests.post(api_url, + data=json.dumps(data), + headers=HEADERS, + auth=(self.username, self.api_key), + timeout=TIMEOUT) + if resp.status_code == 200: + return obj = json.loads(resp.text) response_msg = obj['response_msg'] response_code = obj['response_code'] - if resp.status_code != 200: - _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, - response_msg, response_code) + _LOGGER.error("Error %s : %s (Code %s)", resp.status_code, + response_msg, response_code) def _authenticate(config): @@ -82,7 +87,7 @@ def _authenticate(config): api_url = '{}/account'.format(BASE_API_URL) resp = requests.get(api_url, headers=HEADERS, auth=(config.get(CONF_USERNAME), - config.get(CONF_API_KEY)), timeout=5) + config.get(CONF_API_KEY)), timeout=TIMEOUT) if resp.status_code != 200: return False From 8283f50e22a76a531be5868b07c2c96a09a6a490 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Oct 2018 14:14:01 +0200 Subject: [PATCH 037/230] Remove day (fixes #17741) (#17743) --- homeassistant/components/sensor/fastdotcom.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py index c6a56701f7c..761dc7c6a00 100644 --- a/homeassistant/components/sensor/fastdotcom.py +++ b/homeassistant/components/sensor/fastdotcom.py @@ -22,7 +22,6 @@ _LOGGER = logging.getLogger(__name__) CONF_SECOND = 'second' CONF_MINUTE = 'minute' CONF_HOUR = 'hour' -CONF_DAY = 'day' CONF_MANUAL = 'manual' ICON = 'mdi:speedometer' @@ -34,8 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]), vol.Optional(CONF_HOUR): vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 23))]), - vol.Optional(CONF_DAY): - vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(1, 31))]), vol.Optional(CONF_MANUAL, default=False): cv.boolean, }) @@ -109,8 +106,7 @@ class SpeedtestData: if not config.get(CONF_MANUAL): track_time_change( hass, self.update, second=config.get(CONF_SECOND), - minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR), - day=config.get(CONF_DAY)) + minute=config.get(CONF_MINUTE), hour=config.get(CONF_HOUR)) def update(self, now): """Get the latest data from fast.com.""" From c7c0ed89c8cfabc8e3175e2cd70aac8b383185a2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Oct 2018 15:23:09 +0200 Subject: [PATCH 038/230] Convert auth websocket commands to use async_response decorator (#17755) --- homeassistant/components/auth/__init__.py | 91 ++++++++++------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 58be53d4122..f4b314918f7 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -424,54 +424,46 @@ def _create_auth_code_store(): @websocket_api.ws_require_user() -@callback -def websocket_current_user( +@websocket_api.async_response +async def websocket_current_user( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): """Return the current user.""" - async def async_get_current_user(user): - """Get current user.""" - enabled_modules = await hass.auth.async_get_enabled_mfa(user) + user = connection.user + enabled_modules = await hass.auth.async_get_enabled_mfa(user) - connection.send_message( - websocket_api.result_message(msg['id'], { - 'id': user.id, - 'name': user.name, - 'is_owner': user.is_owner, - 'credentials': [{'auth_provider_type': c.auth_provider_type, - 'auth_provider_id': c.auth_provider_id} - for c in user.credentials], - 'mfa_modules': [{ - 'id': module.id, - 'name': module.name, - 'enabled': module.id in enabled_modules, - } for module in hass.auth.auth_mfa_modules], - })) - - hass.async_create_task(async_get_current_user(connection.user)) + connection.send_message( + websocket_api.result_message(msg['id'], { + 'id': user.id, + 'name': user.name, + 'is_owner': user.is_owner, + 'credentials': [{'auth_provider_type': c.auth_provider_type, + 'auth_provider_id': c.auth_provider_id} + for c in user.credentials], + 'mfa_modules': [{ + 'id': module.id, + 'name': module.name, + 'enabled': module.id in enabled_modules, + } for module in hass.auth.auth_mfa_modules], + })) @websocket_api.ws_require_user() -@callback -def websocket_create_long_lived_access_token( +@websocket_api.async_response +async def websocket_create_long_lived_access_token( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): """Create or a long-lived access token.""" - async def async_create_long_lived_access_token(user): - """Create or a long-lived access token.""" - refresh_token = await hass.auth.async_create_refresh_token( - user, - client_name=msg['client_name'], - client_icon=msg.get('client_icon'), - token_type=TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, - access_token_expiration=timedelta(days=msg['lifespan'])) + refresh_token = await hass.auth.async_create_refresh_token( + connection.user, + client_name=msg['client_name'], + client_icon=msg.get('client_icon'), + token_type=TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN, + access_token_expiration=timedelta(days=msg['lifespan'])) - access_token = hass.auth.async_create_access_token( - refresh_token) + access_token = hass.auth.async_create_access_token( + refresh_token) - connection.send_message( - websocket_api.result_message(msg['id'], access_token)) - - hass.async_create_task( - async_create_long_lived_access_token(connection.user)) + connection.send_message( + websocket_api.result_message(msg['id'], access_token)) @websocket_api.ws_require_user() @@ -494,22 +486,17 @@ def websocket_refresh_tokens( @websocket_api.ws_require_user() -@callback -def websocket_delete_refresh_token( +@websocket_api.async_response +async def websocket_delete_refresh_token( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): """Handle a delete refresh token request.""" - async def async_delete_refresh_token(user, refresh_token_id): - """Delete a refresh token.""" - refresh_token = connection.user.refresh_tokens.get(refresh_token_id) + refresh_token = connection.user.refresh_tokens.get(msg['refresh_token_id']) - if refresh_token is None: - return websocket_api.error_message( - msg['id'], 'invalid_token_id', 'Received invalid token') + if refresh_token is None: + return websocket_api.error_message( + msg['id'], 'invalid_token_id', 'Received invalid token') - await hass.auth.async_remove_refresh_token(refresh_token) + await hass.auth.async_remove_refresh_token(refresh_token) - connection.send_message( - websocket_api.result_message(msg['id'], {})) - - hass.async_create_task( - async_delete_refresh_token(connection.user, msg['refresh_token_id'])) + connection.send_message( + websocket_api.result_message(msg['id'], {})) From 54d463e7465544396ee56db66040000577dc39dd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 24 Oct 2018 18:59:52 +0200 Subject: [PATCH 039/230] Update name (fixes #17752) (#17756) --- homeassistant/components/binary_sensor/zha.py | 6 ++-- .../components/binary_sensor/zigbee.py | 4 +-- .../components/switch/xiaomi_aqara.py | 2 +- homeassistant/components/switch/zigbee.py | 6 ++-- homeassistant/components/zigbee.py | 28 +++++++++---------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/binary_sensor/zha.py b/homeassistant/components/binary_sensor/zha.py index fa24ed89980..9365ba42cc1 100644 --- a/homeassistant/components/binary_sensor/zha.py +++ b/homeassistant/components/binary_sensor/zha.py @@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zha'] -# ZigBee Cluster Library Zone Type to Home Assistant device class +# Zigbee Cluster Library Zone Type to Home Assistant device class CLASS_MAPPING = { 0x000d: 'motion', 0x0015: 'opening', @@ -145,7 +145,7 @@ class Remote(zha.Entity, BinarySensorDevice): _domain = DOMAIN class OnOffListener: - """Listener for the OnOff ZigBee cluster.""" + """Listener for the OnOff Zigbee cluster.""" def __init__(self, entity): """Initialize OnOffListener.""" @@ -170,7 +170,7 @@ class Remote(zha.Entity, BinarySensorDevice): pass class LevelListener: - """Listener for the LevelControl ZigBee cluster.""" + """Listener for the LevelControl Zigbee cluster.""" def __init__(self, entity): """Initialize LevelListener.""" diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 6b89258209e..67c05f47094 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -1,5 +1,5 @@ """ -Contains functionality to use a ZigBee device as a binary sensor. +Contains functionality to use a Zigbee device as a binary sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.zigbee/ @@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the ZigBee binary sensor platform.""" + """Set up the Zigbee binary sensor platform.""" add_entities( [ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True) diff --git a/homeassistant/components/switch/xiaomi_aqara.py b/homeassistant/components/switch/xiaomi_aqara.py index d5502c0b6fa..166f51fb091 100644 --- a/homeassistant/components/switch/xiaomi_aqara.py +++ b/homeassistant/components/switch/xiaomi_aqara.py @@ -106,7 +106,7 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice): @property def should_poll(self): - """Return the polling state. Polling needed for zigbee plug only.""" + """Return the polling state. Polling needed for Zigbee plug only.""" return self._supports_power_consumption def turn_on(self, **kwargs): diff --git a/homeassistant/components/switch/zigbee.py b/homeassistant/components/switch/zigbee.py index 8d54892399a..81fb8348c4e 100644 --- a/homeassistant/components/switch/zigbee.py +++ b/homeassistant/components/switch/zigbee.py @@ -1,5 +1,5 @@ """ -Contains functionality to use a ZigBee device as a switch. +Contains functionality to use a Zigbee device as a switch. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.zigbee/ @@ -25,11 +25,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the ZigBee switch platform.""" + """Set up the Zigbee switch platform.""" add_entities([ZigBeeSwitch(hass, ZigBeeDigitalOutConfig(config))]) class ZigBeeSwitch(ZigBeeDigitalOut, SwitchDevice): - """Representation of a ZigBee Digital Out device.""" + """Representation of a Zigbee Digital Out device.""" pass diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py index 4c294e51231..8829a3bb928 100644 --- a/homeassistant/components/zigbee.py +++ b/homeassistant/components/zigbee.py @@ -1,5 +1,5 @@ """ -Support for ZigBee devices. +Support for Zigbee devices. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zigbee/ @@ -60,7 +60,7 @@ PLATFORM_SCHEMA = vol.Schema({ def setup(hass, config): - """Set up the connection to the ZigBee device.""" + """Set up the connection to the Zigbee device.""" global DEVICE global GPIO_DIGITAL_OUTPUT_LOW global GPIO_DIGITAL_OUTPUT_HIGH @@ -91,13 +91,13 @@ def setup(hass, config): try: ser = Serial(usb_device, baud) except SerialException as exc: - _LOGGER.exception("Unable to open serial port for ZigBee: %s", exc) + _LOGGER.exception("Unable to open serial port for Zigbee: %s", exc) return False DEVICE = ZigBee(ser) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_serial_port) def _frame_received(frame): - """Run when a ZigBee frame is received. + """Run when a Zigbee frame is received. Pickles the frame, then encodes it into base64 since it contains non JSON serializable binary. @@ -110,7 +110,7 @@ def setup(hass, config): def close_serial_port(*args): - """Close the serial port we're using to communicate with the ZigBee.""" + """Close the serial port we're using to communicate with the Zigbee.""" DEVICE.zb.serial.close() @@ -141,7 +141,7 @@ class ZigBeeConfig: """Return the address of the device. If an address has been provided, unhexlify it, otherwise return None - as we're talking to our local ZigBee device. + as we're talking to our local Zigbee device. """ address = self._config.get("address") if address is not None: @@ -167,7 +167,7 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): """A subclass of ZigBeePinConfig.""" def __init__(self, config): - """Initialise the ZigBee Digital input config.""" + """Initialise the Zigbee Digital input config.""" super(ZigBeeDigitalInConfig, self).__init__(config) self._bool2state, self._state2bool = self.boolean_maps @@ -194,7 +194,7 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): @property def bool2state(self): - """Return a dictionary mapping the internal value to the ZigBee value. + """Return a dictionary mapping the internal value to the Zigbee value. For the translation of on/off as being pin high or low. """ @@ -202,7 +202,7 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): @property def state2bool(self): - """Return a dictionary mapping the ZigBee value to the internal value. + """Return a dictionary mapping the Zigbee value to the internal value. For the translation of pin high/low as being on or off. """ @@ -217,7 +217,7 @@ class ZigBeeDigitalOutConfig(ZigBeePinConfig): """ def __init__(self, config): - """Initialize the ZigBee Digital out.""" + """Initialize the Zigbee Digital out.""" super(ZigBeeDigitalOutConfig, self).__init__(config) self._bool2state, self._state2bool = self.boolean_maps self._should_poll = config.get("poll", False) @@ -260,7 +260,7 @@ class ZigBeeDigitalOutConfig(ZigBeePinConfig): class ZigBeeAnalogInConfig(ZigBeePinConfig): - """Representation of a ZigBee GPIO pin set to analog in.""" + """Representation of a Zigbee GPIO pin set to analog in.""" @property def max_voltage(self): @@ -321,7 +321,7 @@ class ZigBeeDigitalIn(Entity): return self._state def update(self): - """Ask the ZigBee device what state its input pin is in.""" + """Ask the Zigbee device what state its input pin is in.""" try: sample = DEVICE.get_sample(self._config.address) except ZIGBEE_TX_FAILURE: @@ -331,12 +331,12 @@ class ZigBeeDigitalIn(Entity): return except ZIGBEE_EXCEPTION as exc: _LOGGER.exception( - "Unable to get sample from ZigBee device: %s", exc) + "Unable to get sample from Zigbee device: %s", exc) return pin_name = DIGITAL_PINS[self._config.pin] if pin_name not in sample: _LOGGER.warning( - "Pin %s (%s) was not in the sample provided by ZigBee device " + "Pin %s (%s) was not in the sample provided by Zigbee device " "%s.", self._config.pin, pin_name, hexlify(self._config.address)) return From 52974ff742575eecf3a9d5a2133ac2d0ac487ede Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 24 Oct 2018 22:15:21 +0200 Subject: [PATCH 040/230] Update frontend to 20181024.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 55aa0700bef..c155bcf81e3 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181023.0'] +REQUIREMENTS = ['home-assistant-frontend==20181024.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index d67e512af62..f673bde5da0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -472,7 +472,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181023.0 +home-assistant-frontend==20181024.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cb3f008c4a9..1eb3d3d5fe5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181023.0 +home-assistant-frontend==20181024.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From c099c259ea55995c0325e9e85eb65806c181f1e5 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Wed, 24 Oct 2018 22:20:25 +0200 Subject: [PATCH 041/230] Async tests for MQTT lock (#17763) --- tests/components/lock/test_mqtt.py | 244 ++++++++++++----------------- 1 file changed, 100 insertions(+), 144 deletions(-) diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index a7bb06fc223..fd9bbad2dc4 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -1,181 +1,137 @@ """The tests for the MQTT lock platform.""" -import unittest - -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_LOCKED, STATE_UNLOCKED, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE) import homeassistant.components.lock as lock from homeassistant.components.mqtt.discovery import async_start -from tests.common import ( - mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message, - get_test_home_assistant) -from tests.components.lock import common +from tests.common import async_fire_mqtt_message -class TestLockMQTT(unittest.TestCase): - """Test the MQTT lock.""" +async def test_controlling_state_via_topic(hass, mqtt_mock): + """Test the controlling state via topic.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK' + } + }) - 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_publish = mock_mqtt_component(self.hass) + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED + assert not state.attributes.get(ATTR_ASSUMED_STATE) - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() + async_fire_mqtt_message(hass, 'state-topic', 'LOCK') + await hass.async_block_till_done() - def test_controlling_state_via_topic(self): - """Test the controlling state via topic.""" - assert setup_component(self.hass, lock.DOMAIN, { - lock.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'payload_lock': 'LOCK', - 'payload_unlock': 'UNLOCK' - } - }) + state = hass.states.get('lock.test') + assert state.state is STATE_LOCKED - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state - assert not state.attributes.get(ATTR_ASSUMED_STATE) + async_fire_mqtt_message(hass, 'state-topic', 'UNLOCK') + await hass.async_block_till_done() + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'state-topic', 'LOCK') - self.hass.block_till_done() + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED - state = self.hass.states.get('lock.test') - assert STATE_LOCKED == state.state - fire_mqtt_message(self.hass, 'state-topic', 'UNLOCK') - self.hass.block_till_done() +async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): + """Test the controlling state via topic and JSON message.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK', + 'value_template': '{{ value_json.val }}' + } + }) - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED - def test_sending_mqtt_commands_and_optimistic(self): - """Test the sending MQTT commands in optimistic mode.""" - assert setup_component(self.hass, lock.DOMAIN, { - lock.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'command-topic', - 'payload_lock': 'LOCK', - 'payload_unlock': 'UNLOCK', - 'qos': 2 - } - }) + async_fire_mqtt_message(hass, 'state-topic', '{"val":"LOCK"}') + await hass.async_block_till_done() - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state - assert state.attributes.get(ATTR_ASSUMED_STATE) + state = hass.states.get('lock.test') + assert state.state is STATE_LOCKED - common.lock(self.hass, 'lock.test') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'state-topic', '{"val":"UNLOCK"}') + await hass.async_block_till_done() + await hass.async_block_till_done() - self.mock_publish.async_publish.assert_called_once_with( - 'command-topic', 'LOCK', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('lock.test') - assert STATE_LOCKED == state.state + state = hass.states.get('lock.test') + assert state.state is STATE_UNLOCKED - common.unlock(self.hass, 'lock.test') - self.hass.block_till_done() - self.mock_publish.async_publish.assert_called_once_with( - 'command-topic', 'UNLOCK', 2, False) - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state +async def test_default_availability_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK', + 'availability_topic': 'availability-topic' + } + }) - def test_controlling_state_via_topic_and_json_message(self): - """Test the controlling state via topic and JSON message.""" - assert setup_component(self.hass, lock.DOMAIN, { - lock.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'payload_lock': 'LOCK', - 'payload_unlock': 'UNLOCK', - 'value_template': '{{ value_json.val }}' - } - }) + state = hass.states.get('lock.test') + assert state.state is STATE_UNAVAILABLE - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state + async_fire_mqtt_message(hass, 'availability-topic', 'online') + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'state-topic', '{"val":"LOCK"}') - self.hass.block_till_done() + state = hass.states.get('lock.test') + assert state.state is not STATE_UNAVAILABLE - state = self.hass.states.get('lock.test') - assert STATE_LOCKED == state.state + async_fire_mqtt_message(hass, 'availability-topic', 'offline') + await hass.async_block_till_done() + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'state-topic', '{"val":"UNLOCK"}') - self.hass.block_till_done() + state = hass.states.get('lock.test') + assert state.state is STATE_UNAVAILABLE - state = self.hass.states.get('lock.test') - assert STATE_UNLOCKED == state.state - def test_default_availability_payload(self): - """Test availability by default payload with defined topic.""" - assert setup_component(self.hass, lock.DOMAIN, { - lock.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'payload_lock': 'LOCK', - 'payload_unlock': 'UNLOCK', - 'availability_topic': 'availability-topic' - } - }) +async def test_custom_availability_payload(hass, mqtt_mock): + """Test availability by custom payload with defined topic.""" + assert await async_setup_component(hass, lock.DOMAIN, { + lock.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'payload_lock': 'LOCK', + 'payload_unlock': 'UNLOCK', + 'availability_topic': 'availability-topic', + 'payload_available': 'good', + 'payload_not_available': 'nogood' + } + }) - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('lock.test') + assert state.state is STATE_UNAVAILABLE - fire_mqtt_message(self.hass, 'availability-topic', 'online') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'availability-topic', 'good') + await hass.async_block_till_done() - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE != state.state + state = hass.states.get('lock.test') + assert state.state is not STATE_UNAVAILABLE - fire_mqtt_message(self.hass, 'availability-topic', 'offline') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'availability-topic', 'nogood') + await hass.async_block_till_done() + await hass.async_block_till_done() - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE == state.state - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - assert setup_component(self.hass, lock.DOMAIN, { - lock.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'payload_lock': 'LOCK', - 'payload_unlock': 'UNLOCK', - 'availability_topic': 'availability-topic', - 'payload_available': 'good', - 'payload_not_available': 'nogood' - } - }) - - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE == state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'good') - self.hass.block_till_done() - - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'nogood') - self.hass.block_till_done() - - state = self.hass.states.get('lock.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('lock.test') + assert state.state is STATE_UNAVAILABLE async def test_discovery_removal_lock(hass, mqtt_mock, caplog): From ec7d33f2772c279cf6d3300df45afe38411abf56 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Wed, 24 Oct 2018 22:20:52 +0200 Subject: [PATCH 042/230] Async MQTT sensor room (#17765) --- tests/components/sensor/test_mqtt_room.py | 96 ++++++++++------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py index 74852abff4d..980655bac78 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -1,18 +1,16 @@ """The tests for the MQTT room presence sensor.""" import json import datetime -import unittest from unittest.mock import patch -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component import homeassistant.components.sensor as sensor from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS, DEFAULT_QOS) from homeassistant.const import (CONF_NAME, CONF_PLATFORM) from homeassistant.util import dt -from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) +from tests.common import async_fire_mqtt_message DEVICE_ID = '123TESTMAC' NAME = 'test_device' @@ -46,63 +44,53 @@ REALLY_FAR_MESSAGE = { } -class TestMQTTRoomSensor(unittest.TestCase): - """Test the room presence sensor.""" +async def send_message(hass, topic, message): + """Test the sending of a message.""" + async_fire_mqtt_message( + hass, topic, json.dumps(message)) + await hass.async_block_till_done() + await hass.async_block_till_done() - def setup_method(self, method): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_mqtt_component(self.hass) - assert setup_component(self.hass, sensor.DOMAIN, { - sensor.DOMAIN: { - CONF_PLATFORM: 'mqtt_room', - CONF_NAME: NAME, - CONF_DEVICE_ID: DEVICE_ID, - CONF_STATE_TOPIC: 'room_presence', - CONF_QOS: DEFAULT_QOS, - CONF_TIMEOUT: 5 - }}) - # Clear state between tests - self.hass.states.set(SENSOR_STATE, None) +async def assert_state(hass, room): + """Test the assertion of a room state.""" + state = hass.states.get(SENSOR_STATE) + assert state.state == room - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - def send_message(self, topic, message): - """Test the sending of a message.""" - fire_mqtt_message( - self.hass, topic, json.dumps(message)) - self.hass.block_till_done() +async def assert_distance(hass, distance): + """Test the assertion of a distance state.""" + state = hass.states.get(SENSOR_STATE) + assert state.attributes.get('distance') == distance - def assert_state(self, room): - """Test the assertion of a room state.""" - state = self.hass.states.get(SENSOR_STATE) - assert state.state == room - def assert_distance(self, distance): - """Test the assertion of a distance state.""" - state = self.hass.states.get(SENSOR_STATE) - assert state.attributes.get('distance') == distance +async def test_room_update(hass, mqtt_mock): + """Test the updating between rooms.""" + assert await async_setup_component(hass, sensor.DOMAIN, { + sensor.DOMAIN: { + CONF_PLATFORM: 'mqtt_room', + CONF_NAME: NAME, + CONF_DEVICE_ID: DEVICE_ID, + CONF_STATE_TOPIC: 'room_presence', + CONF_QOS: DEFAULT_QOS, + CONF_TIMEOUT: 5 + }}) - def test_room_update(self): - """Test the updating between rooms.""" - self.send_message(BEDROOM_TOPIC, FAR_MESSAGE) - self.assert_state(BEDROOM) - self.assert_distance(10) + await send_message(hass, BEDROOM_TOPIC, FAR_MESSAGE) + await assert_state(hass, BEDROOM) + await assert_distance(hass, 10) - self.send_message(LIVING_ROOM_TOPIC, NEAR_MESSAGE) - self.assert_state(LIVING_ROOM) - self.assert_distance(1) + await send_message(hass, LIVING_ROOM_TOPIC, NEAR_MESSAGE) + await assert_state(hass, LIVING_ROOM) + await assert_distance(hass, 1) - self.send_message(BEDROOM_TOPIC, FAR_MESSAGE) - self.assert_state(LIVING_ROOM) - self.assert_distance(1) + await send_message(hass, BEDROOM_TOPIC, FAR_MESSAGE) + await assert_state(hass, LIVING_ROOM) + await assert_distance(hass, 1) - time = dt.utcnow() + datetime.timedelta(seconds=7) - with patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=time): - self.send_message(BEDROOM_TOPIC, FAR_MESSAGE) - self.assert_state(BEDROOM) - self.assert_distance(10) + time = dt.utcnow() + datetime.timedelta(seconds=7) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=time): + await send_message(hass, BEDROOM_TOPIC, FAR_MESSAGE) + await assert_state(hass, BEDROOM) + await assert_distance(hass, 10) From a629e1bec2c4cf8aa711722c60efb726c13b16dd Mon Sep 17 00:00:00 2001 From: Manuel de la Rosa Date: Wed, 24 Oct 2018 15:56:14 -0500 Subject: [PATCH 043/230] Add Mexican Spanish language (#17735) --- homeassistant/components/tts/google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tts/google.py b/homeassistant/components/tts/google.py index 35a07ed8d22..5e1da2595af 100644 --- a/homeassistant/components/tts/google.py +++ b/homeassistant/components/tts/google.py @@ -29,7 +29,7 @@ SUPPORT_LANGUAGES = [ 'hr', 'cs', 'da', 'nl', 'en', 'en-au', 'en-uk', 'en-us', 'eo', 'fi', 'fr', 'de', 'el', 'hi', 'hu', 'is', 'id', 'it', 'ja', 'ko', 'la', 'lv', 'mk', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 'sr', 'sk', 'es', 'es-es', - 'es-us', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy', 'uk', 'bg-BG' + 'es-mx', 'es-us', 'sw', 'sv', 'ta', 'th', 'tr', 'vi', 'cy', 'uk', 'bg-BG' ] DEFAULT_LANG = 'en' From bd23145331c2a3497160d311da5853393852df61 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 24 Oct 2018 21:53:18 -0600 Subject: [PATCH 044/230] Fixed an incorrect reference in the entity registry (#17775) --- homeassistant/helpers/entity_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 4a5daa182fa..5adf748dc58 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -185,7 +185,7 @@ class EntityRegistry: for listener_ref in new.update_listeners: listener = listener_ref() if listener is None: - to_remove.append(listener) + to_remove.append(listener_ref) else: try: listener.async_registry_updated(old, new) From aa157e17f9795868738d0f91064703e95d0b6240 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Oct 2018 09:33:23 +0200 Subject: [PATCH 045/230] Add wind gust (fixes #17766) (#17774) --- homeassistant/components/sensor/darksky.py | 47 ++++++++++------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index a6c602602f4..ad59f28adb3 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -4,20 +4,20 @@ Support for Dark Sky weather service. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.darksky/ """ -import logging from datetime import timedelta +import logging +from requests.exceptions import ( + ConnectionError as ConnectError, HTTPError, Timeout) import voluptuous as vol -from requests.exceptions import ConnectionError as ConnectError, \ - HTTPError, Timeout from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION, - CONF_LATITUDE, CONF_LONGITUDE, UNIT_UV_INDEX) + ATTR_ATTRIBUTION, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, + CONF_MONITORED_CONDITIONS, CONF_NAME, UNIT_UV_INDEX) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['python-forecastio==1.4.0'] @@ -82,6 +82,9 @@ SENSOR_TYPES = { 'mdi:weather-windy', ['currently', 'hourly', 'daily']], 'wind_bearing': ['Wind Bearing', '°', '°', '°', '°', '°', 'mdi:compass', ['currently', 'hourly', 'daily']], + 'wind_gust': ['Wind Gust', 'm/s', 'mph', 'km/h', 'mph', 'mph', + 'mdi:weather-windy-variant', + ['currently', 'hourly', 'daily']], 'cloud_cover': ['Cloud Coverage', '%', '%', '%', '%', '%', 'mdi:weather-partlycloudy', ['currently', 'hourly', 'daily']], @@ -146,12 +149,9 @@ CONDITION_PICTURES = { # Language Supported Codes LANGUAGE_CODES = [ - 'ar', 'az', 'be', 'bg', 'bs', 'ca', - 'cs', 'da', 'de', 'el', 'en', 'es', - 'et', 'fi', 'fr', 'hr', 'hu', 'id', - 'is', 'it', 'ja', 'ka', 'kw', 'nb', - 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', - 'sl', 'sr', 'sv', 'tet', 'tr', 'uk', + 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', + 'et', 'fi', 'fr', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'kw', 'nb', + 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tet', 'tr', 'uk', 'x-pig-latin', 'zh', 'zh-tw', ] @@ -179,6 +179,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) language = config.get(CONF_LANGUAGE) + interval = config.get(CONF_UPDATE_INTERVAL) if CONF_UNITS in config: units = config[CONF_UNITS] @@ -188,18 +189,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): units = 'us' forecast_data = DarkSkyData( - api_key=config.get(CONF_API_KEY, None), - latitude=latitude, - longitude=longitude, - units=units, - language=language, - interval=config.get(CONF_UPDATE_INTERVAL)) + api_key=config.get(CONF_API_KEY, None), latitude=latitude, + longitude=longitude, units=units, language=language, interval=interval) forecast_data.update() forecast_data.update_currently() # If connection failed don't setup platform. if forecast_data.data is None: - return False + return name = config.get(CONF_NAME) @@ -207,8 +204,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: if variable in DEPRECATED_SENSOR_TYPES: - _LOGGER.warning("Monitored condition %s is deprecated", - variable) + _LOGGER.warning("Monitored condition %s is deprecated", variable) sensors.append(DarkSkySensor(forecast_data, variable, name)) if forecast is not None and 'daily' in SENSOR_TYPES[variable][7]: for forecast_day in forecast: @@ -360,8 +356,7 @@ class DarkSkySensor(Entity): if self.type in ['dew_point', 'temperature', 'apparent_temperature', 'temperature_min', 'temperature_max', 'apparent_temperature_min', - 'apparent_temperature_max', - 'precip_accumulation', + 'apparent_temperature_max', 'precip_accumulation', 'pressure', 'ozone', 'uvIndex']: return round(state, 1) return state @@ -371,7 +366,7 @@ def convert_to_camel(data): """ Convert snake case (foo_bar_bat) to camel case (fooBarBat). - This is not pythonic, but needed for certain situations + This is not pythonic, but needed for certain situations. """ components = data.split('_') return components[0] + "".join(x.title() for x in components[1:]) @@ -380,8 +375,8 @@ def convert_to_camel(data): class DarkSkyData: """Get the latest data from Darksky.""" - def __init__(self, api_key, latitude, longitude, units, language, - interval): + def __init__( + self, api_key, latitude, longitude, units, language, interval): """Initialize the data object.""" self._api_key = api_key self.latitude = latitude From 577cf0991fe8b8bb56ad662bc5baf20905961c35 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Oct 2018 09:43:40 +0200 Subject: [PATCH 046/230] Remove username from log entry (#17777) --- homeassistant/components/vacuum/roomba.py | 30 +++++++++-------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/vacuum/roomba.py b/homeassistant/components/vacuum/roomba.py index 72d564909a8..d06ecc5141f 100644 --- a/homeassistant/components/vacuum/roomba.py +++ b/homeassistant/components/vacuum/roomba.py @@ -6,20 +6,19 @@ https://home-assistant.io/components/vacuum.roomba/ """ import asyncio import logging -import voluptuous as vol import async_timeout +import voluptuous as vol from homeassistant.components.vacuum import ( - VacuumDevice, PLATFORM_SCHEMA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, - SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, - SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON) + PLATFORM_SCHEMA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_PAUSE, + SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, + SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VacuumDevice) from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv - REQUIREMENTS = ['roombapy==1.3.1'] _LOGGER = logging.getLogger(__name__) @@ -68,8 +67,8 @@ SUPPORT_ROOMBA = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \ SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_ROOMBA | SUPPORT_FAN_SPEED -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the iRobot Roomba vacuum cleaner platform.""" from roomba import Roomba if PLATFORM not in hass.data: @@ -82,16 +81,10 @@ async def async_setup_platform(hass, config, async_add_entities, certificate = config.get(CONF_CERT) continuous = config.get(CONF_CONTINUOUS) - # Create handler roomba = Roomba( - address=host, - blid=username, - password=password, - cert_name=certificate, - continuous=continuous - ) - _LOGGER.info("Initializing communication with host %s (username: %s)", - host, username) + address=host, blid=username, password=password, cert_name=certificate, + continuous=continuous) + _LOGGER.debug("Initializing communication with host %s", host) try: with async_timeout.timeout(9): @@ -102,7 +95,7 @@ async def async_setup_platform(hass, config, async_add_entities, roomba_vac = RoombaVacuum(name, roomba) hass.data[PLATFORM][host] = roomba_vac - async_add_entities([roomba_vac], update_before_add=True) + async_add_entities([roomba_vac], True) class RoombaVacuum(VacuumDevice): @@ -248,8 +241,7 @@ class RoombaVacuum(VacuumDevice): """Fetch state from the device.""" # No data, no update if not self.vacuum.master_state: - _LOGGER.debug("Roomba %s has no data yet. Skip update.", - self.name) + _LOGGER.debug("Roomba %s has no data yet. Skip update", self.name) return state = self.vacuum.master_state.get('state', {}).get('reported', {}) _LOGGER.debug("Got new state from the vacuum: %s", state) From 599390d98517f34b1eea93e2c8dbd1ead2015725 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Thu, 25 Oct 2018 09:45:56 +0200 Subject: [PATCH 047/230] Update HAP-python to 2.3.0 (#17778) * Update HAP-python to 2.3.0 * Fix tests --- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/homekit/type_switches.py | 8 +++----- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_type_locks.py | 1 + tests/components/homekit/test_type_media_players.py | 1 + 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 1c30de918e3..d4d8fe0216c 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -29,7 +29,7 @@ from .const import ( from .util import ( show_setup_message, validate_entity_config, validate_media_player_features) -REQUIREMENTS = ['HAP-python==2.2.2'] +REQUIREMENTS = ['HAP-python==2.3.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 59ae17b5d9d..839abe5a580 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -1,7 +1,9 @@ """Class to hold all switch accessories.""" import logging -from pyhap.const import CATEGORY_OUTLET, CATEGORY_SWITCH +from pyhap.const import ( + CATEGORY_FAUCET, CATEGORY_OUTLET, CATEGORY_SHOWER_HEAD, + CATEGORY_SPRINKLER, CATEGORY_SWITCH) from homeassistant.components.switch import DOMAIN from homeassistant.const import ( @@ -17,10 +19,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -CATEGORY_SPRINKLER = 28 -CATEGORY_FAUCET = 29 -CATEGORY_SHOWER_HEAD = 30 - VALVE_TYPE = { TYPE_FAUCET: (CATEGORY_FAUCET, 3), TYPE_SHOWER: (CATEGORY_SHOWER_HEAD, 2), diff --git a/requirements_all.txt b/requirements_all.txt index f673bde5da0..afd5b4e9380 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -34,7 +34,7 @@ Adafruit-SHT31==1.0.2 DoorBirdPy==0.1.3 # homeassistant.components.homekit -HAP-python==2.2.2 +HAP-python==2.3.0 # homeassistant.components.notify.mastodon Mastodon.py==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1eb3d3d5fe5..90d7756e69c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -19,7 +19,7 @@ requests_mock==1.5.2 # homeassistant.components.homekit -HAP-python==2.2.2 +HAP-python==2.3.0 # homeassistant.components.sensor.rmvtransport PyRMVtransport==0.1.3 diff --git a/tests/components/homekit/test_type_locks.py b/tests/components/homekit/test_type_locks.py index e7e52c65559..8132099bd3e 100644 --- a/tests/components/homekit/test_type_locks.py +++ b/tests/components/homekit/test_type_locks.py @@ -82,6 +82,7 @@ async def test_no_code(hass, hk_driver, config, events): # Set from HomeKit call_lock = async_mock_service(hass, DOMAIN, 'lock') + acc.char_target_state.value = 0 await hass.async_add_job(acc.char_target_state.client_update_value, 1) await hass.async_block_till_done() assert call_lock diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 6b23b3cc58e..745e4c162bc 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -64,6 +64,7 @@ async def test_media_player_set_state(hass, hk_driver, events): call_media_stop = async_mock_service(hass, DOMAIN, 'media_stop') call_toggle_mute = async_mock_service(hass, DOMAIN, 'volume_mute') + acc.chars[FEATURE_ON_OFF].value = False await hass.async_add_job(acc.chars[FEATURE_ON_OFF] .client_update_value, True) await hass.async_block_till_done() From 5024a80d61e9307fab55e034a22bcf97deb6bc42 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Thu, 25 Oct 2018 00:46:22 -0700 Subject: [PATCH 048/230] Migrate twilio webhooks to the webhook component (#17715) * Migrate twilio webhooks to the webhook component * Fix typos in twilio * Mock out twilio in the tests * Lint * Fix regression in twilio response --- .coveragerc | 1 - homeassistant/components/twilio.py | 58 -------------- .../components/twilio/.translations/en.json | 18 +++++ homeassistant/components/twilio/__init__.py | 76 +++++++++++++++++++ homeassistant/components/twilio/strings.json | 18 +++++ homeassistant/config_entries.py | 1 + tests/components/twilio/__init__.py | 1 + tests/components/twilio/test_init.py | 41 ++++++++++ 8 files changed, 155 insertions(+), 59 deletions(-) delete mode 100644 homeassistant/components/twilio.py create mode 100644 homeassistant/components/twilio/.translations/en.json create mode 100644 homeassistant/components/twilio/__init__.py create mode 100644 homeassistant/components/twilio/strings.json create mode 100644 tests/components/twilio/__init__.py create mode 100644 tests/components/twilio/test_init.py diff --git a/.coveragerc b/.coveragerc index 1da53f21cb1..599e155f8f3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -333,7 +333,6 @@ omit = homeassistant/components/tradfri.py homeassistant/components/*/tradfri.py - homeassistant/components/twilio.py homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_call.py diff --git a/homeassistant/components/twilio.py b/homeassistant/components/twilio.py deleted file mode 100644 index 9f9767e4675..00000000000 --- a/homeassistant/components/twilio.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Support for Twilio. - -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/twilio/ -""" -import voluptuous as vol - -import homeassistant.helpers.config_validation as cv -from homeassistant.core import callback -from homeassistant.components.http import HomeAssistantView - -REQUIREMENTS = ['twilio==6.19.1'] - -DOMAIN = 'twilio' - -API_PATH = '/api/{}'.format(DOMAIN) - -CONF_ACCOUNT_SID = 'account_sid' -CONF_AUTH_TOKEN = 'auth_token' - -DATA_TWILIO = DOMAIN -DEPENDENCIES = ['http'] - -RECEIVED_DATA = '{}_data_received'.format(DOMAIN) - -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_ACCOUNT_SID): cv.string, - vol.Required(CONF_AUTH_TOKEN): cv.string - }), -}, extra=vol.ALLOW_EXTRA) - - -def setup(hass, config): - """Set up the Twilio component.""" - from twilio.rest import TwilioRestClient - conf = config[DOMAIN] - hass.data[DATA_TWILIO] = TwilioRestClient( - conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN)) - hass.http.register_view(TwilioReceiveDataView()) - return True - - -class TwilioReceiveDataView(HomeAssistantView): - """Handle data from Twilio inbound messages and calls.""" - - url = API_PATH - name = 'api:{}'.format(DOMAIN) - - @callback - def post(self, request): # pylint: disable=no-self-use - """Handle Twilio data post.""" - from twilio.twiml import TwiML - hass = request.app['hass'] - data = yield from request.post() - hass.bus.async_fire(RECEIVED_DATA, dict(data)) - return TwiML().to_xml() diff --git a/homeassistant/components/twilio/.translations/en.json b/homeassistant/components/twilio/.translations/en.json new file mode 100644 index 00000000000..ca75fff0737 --- /dev/null +++ b/homeassistant/components/twilio/.translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Twilio", + "step": { + "user": { + "title": "Set up the Twilio Webhook", + "description": "Are you sure you want to set up Twilio?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + } + } +} diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py new file mode 100644 index 00000000000..d7196a034ce --- /dev/null +++ b/homeassistant/components/twilio/__init__.py @@ -0,0 +1,76 @@ +""" +Support for Twilio. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/twilio/ +""" +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.helpers import config_entry_flow + +REQUIREMENTS = ['twilio==6.19.1'] +DEPENDENCIES = ['webhook'] + +DOMAIN = 'twilio' + +CONF_ACCOUNT_SID = 'account_sid' +CONF_AUTH_TOKEN = 'auth_token' + +DATA_TWILIO = DOMAIN + +RECEIVED_DATA = '{}_data_received'.format(DOMAIN) + +CONFIG_SCHEMA = vol.Schema({ + vol.Optional(DOMAIN): vol.Schema({ + vol.Required(CONF_ACCOUNT_SID): cv.string, + vol.Required(CONF_AUTH_TOKEN): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the Twilio component.""" + from twilio.rest import TwilioRestClient + if DOMAIN not in config: + return True + + conf = config[DOMAIN] + hass.data[DATA_TWILIO] = TwilioRestClient( + conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN)) + return True + + +async def handle_webhook(hass, webhook_id, request): + """Handle incoming webhook from Twilio for inbound messages and calls.""" + from twilio.twiml import TwiML + + data = dict(await request.post()) + data['webhook_id'] = webhook_id + hass.bus.async_fire(RECEIVED_DATA, dict(data)) + + return TwiML().to_xml() + + +async def async_setup_entry(hass, entry): + """Configure based on config entry.""" + hass.components.webhook.async_register( + entry.data[CONF_WEBHOOK_ID], handle_webhook) + return True + + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) + return True + +config_entry_flow.register_webhook_flow( + DOMAIN, + 'Twilio Webhook', + { + 'twilio_url': + 'https://www.twilio.com/docs/glossary/what-is-a-webhook', + 'docs_url': 'https://www.home-assistant.io/components/twilio/' + } +) diff --git a/homeassistant/components/twilio/strings.json b/homeassistant/components/twilio/strings.json new file mode 100644 index 00000000000..ca75fff0737 --- /dev/null +++ b/homeassistant/components/twilio/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Twilio", + "step": { + "user": { + "title": "Set up the Twilio Webhook", + "description": "Are you sure you want to set up Twilio?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + } + } +} diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e00215b8126..d37bd8cb558 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -151,6 +151,7 @@ FLOWS = [ 'smhi', 'sonos', 'tradfri', + 'twilio', 'unifi', 'upnp', 'zone', diff --git a/tests/components/twilio/__init__.py b/tests/components/twilio/__init__.py new file mode 100644 index 00000000000..641a509ff4d --- /dev/null +++ b/tests/components/twilio/__init__.py @@ -0,0 +1 @@ +"""Tests for the Twilio component.""" diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py new file mode 100644 index 00000000000..c740783f4c0 --- /dev/null +++ b/tests/components/twilio/test_init.py @@ -0,0 +1,41 @@ +"""Test the init file of Twilio.""" +from unittest.mock import patch + +from homeassistant import data_entry_flow +from homeassistant.components import twilio +from homeassistant.core import callback +from tests.common import MockDependency + + +@MockDependency('twilio', 'rest') +@MockDependency('twilio', 'twiml') +async def test_config_flow_registers_webhook(hass, aiohttp_client): + """Test setting up Twilio and sending webhook.""" + with patch('homeassistant.util.get_local_ip', return_value='example.com'): + result = await hass.config_entries.flow.async_init('twilio', context={ + 'source': 'user' + }) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], {}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + webhook_id = result['result'].data['webhook_id'] + + twilio_events = [] + + @callback + def handle_event(event): + """Handle Twilio event.""" + twilio_events.append(event) + + hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event) + + client = await aiohttp_client(hass.http.app) + await client.post('/api/webhook/{}'.format(webhook_id), data={ + 'hello': 'twilio' + }) + + assert len(twilio_events) == 1 + assert twilio_events[0].data['webhook_id'] == webhook_id + assert twilio_events[0].data['hello'] == 'twilio' From 8bebfba21a545a41a0d95e4ce3215ed73bacc6f9 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Thu, 25 Oct 2018 09:54:53 +0200 Subject: [PATCH 049/230] Testing async in MQTT_json lights (#17768) --- tests/components/light/test_mqtt_json.py | 956 ++++++++++------------- 1 file changed, 406 insertions(+), 550 deletions(-) diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 9a8d4ce73fb..9ae12bb1ca1 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -87,12 +87,9 @@ light: brightness: true brightness_scale: 99 """ - -import json -import unittest from unittest.mock import patch -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE, ATTR_SUPPORTED_FEATURES) @@ -100,570 +97,429 @@ import homeassistant.components.light as light from homeassistant.components.mqtt.discovery import async_start import homeassistant.core as ha -from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message, - assert_setup_component, mock_coro, async_fire_mqtt_message) -from tests.components.light import common +from tests.common import mock_coro, async_fire_mqtt_message -class TestLightMQTTJSON(unittest.TestCase): - """Test the MQTT JSON light.""" +async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): + """Test if setup fails with no command topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + } + }) + assert hass.states.get('light.test') is None - 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_publish = mock_mqtt_component(self.hass) - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() +async def test_no_color_brightness_color_temp_white_val_if_no_topics( + hass, mqtt_mock): + """Test for no RGB, brightness, color temp, effect, white val or XY.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + } + }) - def test_fail_setup_if_no_command_topic(self): - """Test if setup fails with no command topic.""" - with assert_setup_component(0, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - } - }) - assert self.hass.states.get('light.test') is None + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None - def test_no_color_brightness_color_temp_white_val_if_no_topics(self): - """Test for no RGB, brightness, color temp, effect, white val or XY.""" - assert setup_component(self.hass, light.DOMAIN, { + async_fire_mqtt_message(hass, 'test_light_rgb', '{"state":"ON"}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None + + +async def test_controlling_state_via_topic(hass, mqtt_mock): + """Test the controlling of the state via topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'brightness': True, + 'color_temp': True, + 'effect': True, + 'rgb': True, + 'white_value': True, + 'xy': True, + 'hs': True, + 'qos': '0' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert state.attributes.get('hs_color') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # Turn on the light, full white + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON",' + '"color":{"r":255,"g":255,"b":255},' + '"brightness":255,' + '"color_temp":155,' + '"effect":"colorloop",' + '"white_value":150}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 155 == state.attributes.get('color_temp') + assert 'colorloop' == state.attributes.get('effect') + assert 150 == state.attributes.get('white_value') + assert (0.323, 0.329) == state.attributes.get('xy_color') + assert (0.0, 0.0) == state.attributes.get('hs_color') + + # Turn the light off + async_fire_mqtt_message(hass, 'test_light_rgb', '{"state":"OFF"}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "brightness":100}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + + assert 100 == \ + light_state.attributes['brightness'] + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", ' + '"color":{"r":125,"g":125,"b":125}}') + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (255, 255, 255) == \ + light_state.attributes.get('rgb_color') + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "color":{"x":0.135,"y":0.135}}') + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (0.141, 0.14) == \ + light_state.attributes.get('xy_color') + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "color":{"h":180,"s":50}}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (180.0, 50.0) == \ + light_state.attributes.get('hs_color') + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "color_temp":155}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 155 == light_state.attributes.get('color_temp') + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "effect":"colorloop"}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 'colorloop' == light_state.attributes.get('effect') + + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON", "white_value":155}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 155 == light_state.attributes.get('white_value') + + +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test the sending of command in optimistic mode.""" + fake_state = ha.State('light.test', 'on', {'brightness': 95, + 'hs_color': [100, 100], + 'effect': 'random', + 'color_temp': 100, + 'white_value': 50}) + + with patch('homeassistant.components.light.mqtt_json' + '.async_get_last_state', + return_value=mock_coro(fake_state)): + assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_json', 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - assert state.attributes.get('hs_color') is None - - fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON"}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - assert state.attributes.get('hs_color') is None - - def test_controlling_state_via_topic(self): - """Test the controlling of the state via topic.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', 'brightness': True, 'color_temp': True, 'effect': True, 'rgb': True, 'white_value': True, - 'xy': True, - 'hs': True, - 'qos': '0' + 'qos': 2 } }) - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - assert state.attributes.get('hs_color') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Turn on the light, full white - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"r":255,"g":255,"b":255},' - '"brightness":255,' - '"color_temp":155,' - '"effect":"colorloop",' - '"white_value":150}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 255, 255) == state.attributes.get('rgb_color') - assert 255 == state.attributes.get('brightness') - assert 155 == state.attributes.get('color_temp') - assert 'colorloop' == state.attributes.get('effect') - assert 150 == state.attributes.get('white_value') - assert (0.323, 0.329) == state.attributes.get('xy_color') - assert (0.0, 0.0) == state.attributes.get('hs_color') - - # Turn the light off - fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"OFF"}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"brightness":100}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 100 == \ - light_state.attributes['brightness'] - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"r":125,"g":125,"b":125}}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (255, 255, 255) == \ - light_state.attributes.get('rgb_color') - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"x":0.135,"y":0.135}}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (0.141, 0.14) == \ - light_state.attributes.get('xy_color') - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"h":180,"s":50}}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (180.0, 50.0) == \ - light_state.attributes.get('hs_color') - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color_temp":155}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert 155 == light_state.attributes.get('color_temp') - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"effect":"colorloop"}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert 'colorloop' == light_state.attributes.get('effect') - - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"white_value":155}') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert 155 == light_state.attributes.get('white_value') - - def test_sending_mqtt_commands_and_optimistic(self): - """Test the sending of command in optimistic mode.""" - fake_state = ha.State('light.test', 'on', {'brightness': 95, - 'hs_color': [100, 100], - 'effect': 'random', - 'color_temp': 100, - 'white_value': 50}) - - with patch('homeassistant.components.light.mqtt_json' - '.async_get_last_state', - return_value=mock_coro(fake_state)): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'brightness': True, - 'color_temp': True, - 'effect': True, - 'rgb': True, - 'white_value': True, - 'qos': 2 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 95 == state.attributes.get('brightness') - assert (100, 100) == state.attributes.get('hs_color') - assert 'random' == state.attributes.get('effect') - assert 100 == state.attributes.get('color_temp') - assert 50 == state.attributes.get('white_value') - assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - assert state.attributes.get(ATTR_ASSUMED_STATE) - - common.turn_on(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', '{"state": "ON"}', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', '{"state": "OFF"}', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - common.turn_on(self.hass, 'light.test', - brightness=50, color_temp=155, effect='colorloop', - white_value=170) - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[0][1][0] - assert 2 == \ - self.mock_publish.async_publish.mock_calls[0][1][2] - assert self.mock_publish.async_publish.mock_calls[0][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[0][1][1]) - assert 50 == message_json["brightness"] - assert 155 == message_json["color_temp"] - assert 'colorloop' == message_json["effect"] - assert 170 == message_json["white_value"] - assert "ON" == message_json["state"] - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 50 == state.attributes['brightness'] - assert 155 == state.attributes['color_temp'] - assert 'colorloop' == state.attributes['effect'] - assert 170 == state.attributes['white_value'] - - # Test a color command - common.turn_on(self.hass, 'light.test', - brightness=50, hs_color=(125, 100)) - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[0][1][0] - assert 2 == \ - self.mock_publish.async_publish.mock_calls[0][1][2] - assert self.mock_publish.async_publish.mock_calls[0][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[1][1][1]) - assert 50 == message_json["brightness"] - assert { - 'r': 0, - 'g': 255, - 'b': 21, - } == message_json["color"] - assert "ON" == message_json["state"] - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 50 == state.attributes['brightness'] - assert (125, 100) == state.attributes['hs_color'] - - def test_sending_hs_color(self): - """Test light.turn_on with hs color sends hs color parameters.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'hs': True, - } - }) - - common.turn_on(self.hass, 'light.test', hs_color=(180.0, 50.0)) - self.hass.block_till_done() - - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[0][1][1]) - assert "ON" == message_json["state"] - assert { - 'h': 180.0, - 's': 50.0, - } == message_json["color"] - - def test_flash_short_and_long(self): - """Test for flash length being sent when included.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'flash_time_short': 5, - 'flash_time_long': 15, - 'qos': 0 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - - common.turn_on(self.hass, 'light.test', flash="short") - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[0][1][0] - assert 0 == \ - self.mock_publish.async_publish.mock_calls[0][1][2] - assert self.mock_publish.async_publish.mock_calls[0][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[0][1][1]) - assert 5 == message_json["flash"] - assert "ON" == message_json["state"] - - self.mock_publish.async_publish.reset_mock() - common.turn_on(self.hass, 'light.test', flash="long") - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[0][1][0] - assert 0 == \ - self.mock_publish.async_publish.mock_calls[0][1][2] - assert self.mock_publish.async_publish.mock_calls[0][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[0][1][1]) - assert 15 == message_json["flash"] - assert "ON" == message_json["state"] - - def test_transition(self): - """Test for transition time being sent when included.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'qos': 0 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - - common.turn_on(self.hass, 'light.test', transition=10) - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[0][1][0] - assert 0 == \ - self.mock_publish.async_publish.mock_calls[0][1][2] - assert self.mock_publish.async_publish.mock_calls[0][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[0][1][1]) - assert 10 == message_json["transition"] - assert "ON" == message_json["state"] - - # Transition back off - common.turn_off(self.hass, 'light.test', transition=10) - self.hass.block_till_done() - - assert 'test_light_rgb/set' == \ - self.mock_publish.async_publish.mock_calls[1][1][0] - assert 0 == \ - self.mock_publish.async_publish.mock_calls[1][1][2] - assert self.mock_publish.async_publish.mock_calls[1][1][3] is False - # Get the sent message - message_json = json.loads( - self.mock_publish.async_publish.mock_calls[1][1][1]) - assert 10 == message_json["transition"] - assert "OFF" == message_json["state"] - - def test_brightness_scale(self): - """Test for brightness scaling.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_bright_scale', - 'command_topic': 'test_light_bright_scale/set', - 'brightness': True, - 'brightness_scale': 99 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('brightness') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Turn on the light - fire_mqtt_message(self.hass, 'test_light_bright_scale', - '{"state":"ON"}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - - # Turn on the light with brightness - fire_mqtt_message(self.hass, 'test_light_bright_scale', - '{"state":"ON",' - '"brightness": 99}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - - def test_invalid_color_brightness_and_white_values(self): - """Test that invalid color/brightness/white values are ignored.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'brightness': True, - 'rgb': True, - 'white_value': True, - 'qos': '0' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert 185 == state.attributes.get(ATTR_SUPPORTED_FEATURES) - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('white_value') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Turn on the light - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"r":255,"g":255,"b":255},' - '"brightness": 255,' - '"white_value": 255}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 255, 255) == state.attributes.get('rgb_color') - assert 255 == state.attributes.get('brightness') - assert 255 == state.attributes.get('white_value') - - # Bad color values - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"color":{"r":"bad","g":"val","b":"test"}}') - self.hass.block_till_done() - - # Color should not have changed - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 255, 255) == state.attributes.get('rgb_color') - - # Bad brightness values - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"brightness": "badValue"}') - self.hass.block_till_done() - - # Brightness should not have changed - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - - # Bad white value - fire_mqtt_message(self.hass, 'test_light_rgb', - '{"state":"ON",' - '"white_value": "badValue"}') - self.hass.block_till_done() - - # White value should not have changed - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('white_value') - - def test_default_availability_payload(self): - """Test availability by default payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'availability_topic': 'availability-topic' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'online') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'offline') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_json', - 'name': 'test', - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'availability_topic': 'availability-topic', - 'payload_available': 'good', - 'payload_not_available': 'nogood' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'good') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'nogood') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert 191 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get(ATTR_ASSUMED_STATE) + + +async def test_sending_hs_color(hass, mqtt_mock): + """Test light.turn_on with hs color sends hs color parameters.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'hs': True, + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + +async def test_flash_short_and_long(hass, mqtt_mock): + """Test for flash length being sent when included.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'flash_time_short': 5, + 'flash_time_long': 15, + 'qos': 0 + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + + +async def test_transition(hass, mqtt_mock): + """Test for transition time being sent when included.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'qos': 0 + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert 40 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + + +async def test_brightness_scale(hass, mqtt_mock): + """Test for brightness scaling.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_bright_scale', + 'command_topic': 'test_light_bright_scale/set', + 'brightness': True, + 'brightness_scale': 99 + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # Turn on the light + async_fire_mqtt_message(hass, 'test_light_bright_scale', '{"state":"ON"}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + + # Turn on the light with brightness + async_fire_mqtt_message(hass, 'test_light_bright_scale', + '{"state":"ON", "brightness": 99}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + + +async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): + """Test that invalid color/brightness/white values are ignored.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'brightness': True, + 'rgb': True, + 'white_value': True, + 'qos': '0' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert 185 == state.attributes.get(ATTR_SUPPORTED_FEATURES) + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # Turn on the light + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON",' + '"color":{"r":255,"g":255,"b":255},' + '"brightness": 255,' + '"white_value": 255}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 255 == state.attributes.get('white_value') + + # Bad color values + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON",' + '"color":{"r":"bad","g":"val","b":"test"}}') + await hass.async_block_till_done() + + # Color should not have changed + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + + # Bad brightness values + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON",' + '"brightness": "badValue"}') + await hass.async_block_till_done() + + # Brightness should not have changed + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + + # Bad white value + async_fire_mqtt_message(hass, 'test_light_rgb', + '{"state":"ON",' + '"white_value": "badValue"}') + await hass.async_block_till_done() + + # White value should not have changed + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') + + +async def test_default_availability_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'availability_topic': 'availability-topic' + } + }) + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'online') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'offline') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + +async def test_custom_availability_payload(hass, mqtt_mock): + """Test availability by custom payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_json', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'availability_topic': 'availability-topic', + 'payload_available': 'good', + 'payload_not_available': 'nogood' + } + }) + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'good') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'nogood') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal(hass, mqtt_mock, caplog): From 67d92c4f5da479feb31f2f902ed79fa6128e6829 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Thu, 25 Oct 2018 10:00:07 +0200 Subject: [PATCH 050/230] This makes mqtt_template tests async (#17784) --- tests/components/light/test_mqtt_template.py | 784 +++++++++---------- 1 file changed, 354 insertions(+), 430 deletions(-) diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index 2d702fcb2ef..6bc0b4536ea 100644 --- a/tests/components/light/test_mqtt_template.py +++ b/tests/components/light/test_mqtt_template.py @@ -26,52 +26,193 @@ If your light doesn't support white value feature, omit `white_value_template`. If your light doesn't support RGB feature, omit `(red|green|blue)_template`. """ -import unittest from unittest.mock import patch -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE) import homeassistant.components.light as light import homeassistant.core as ha from tests.common import ( - get_test_home_assistant, mock_mqtt_component, fire_mqtt_message, - assert_setup_component, mock_coro) -from tests.components.light import common + async_fire_mqtt_message, assert_setup_component, mock_coro) -class TestLightMQTTTemplate(unittest.TestCase): - """Test the MQTT Template light.""" +async def test_setup_fails(hass, mqtt_mock): + """Test that setup fails with missing required configuration items.""" + with assert_setup_component(0, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + } + }) + assert hass.states.get('light.test') is None - 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_publish = mock_mqtt_component(self.hass) - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() +async def test_state_change_via_topic(hass, mqtt_mock): + """Test state change via topic.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,' + '{{ brightness|d }},' + '{{ color_temp|d }},' + '{{ white_value|d }},' + '{{ red|d }}-' + '{{ green|d }}-' + '{{ blue|d }}', + 'command_off_template': 'off', + 'state_template': '{{ value.split(",")[0] }}' + } + }) - def test_setup_fails(self): - """Test that setup fails with missing required configuration items.""" - with assert_setup_component(0, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - } - }) - assert self.hass.states.get('light.test') is None + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) - def test_state_change_via_topic(self): - """Test state change via topic.""" + async_fire_mqtt_message(hass, 'test_light_rgb', 'on') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + + +async def test_state_brightness_color_effect_temp_white_change_via_topic( + hass, mqtt_mock): + """Test state, bri, color, effect, color temp, white val change.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'effect_list': ['rainbow', 'colorloop'], + 'state_topic': 'test_light_rgb', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,' + '{{ brightness|d }},' + '{{ color_temp|d }},' + '{{ white_value|d }},' + '{{ red|d }}-' + '{{ green|d }}-' + '{{ blue|d }},' + '{{ effect|d }}', + 'command_off_template': 'off', + 'state_template': '{{ value.split(",")[0] }}', + 'brightness_template': '{{ value.split(",")[1] }}', + 'color_temp_template': '{{ value.split(",")[2] }}', + 'white_value_template': '{{ value.split(",")[3] }}', + 'red_template': '{{ value.split(",")[4].' + 'split("-")[0] }}', + 'green_template': '{{ value.split(",")[4].' + 'split("-")[1] }}', + 'blue_template': '{{ value.split(",")[4].' + 'split("-")[2] }}', + 'effect_template': '{{ value.split(",")[5] }}' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # turn on the light, full white + async_fire_mqtt_message(hass, 'test_light_rgb', + 'on,255,145,123,255-128-64,') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 128, 63) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 145 == state.attributes.get('color_temp') + assert 123 == state.attributes.get('white_value') + assert state.attributes.get('effect') is None + + # turn the light off + async_fire_mqtt_message(hass, 'test_light_rgb', 'off') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + # lower the brightness + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,100') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 100 == light_state.attributes['brightness'] + + # change the color temp + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,,195') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 195 == light_state.attributes['color_temp'] + + # change the color + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,,,,41-42-43') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (243, 249, 255) == \ + light_state.attributes.get('rgb_color') + + # change the white value + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,,,134') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 134 == light_state.attributes['white_value'] + + # change the effect + async_fire_mqtt_message(hass, 'test_light_rgb', + 'on,,,,41-42-43,rainbow') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert 'rainbow' == light_state.attributes.get('effect') + + +async def test_optimistic(hass, mqtt_mock): + """Test optimistic mode.""" + fake_state = ha.State('light.test', 'on', {'brightness': 95, + 'hs_color': [100, 100], + 'effect': 'random', + 'color_temp': 100, + 'white_value': 50}) + + with patch('homeassistant.components.light.mqtt_template' + '.async_get_last_state', + return_value=mock_coro(fake_state)): with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { + assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_template', 'name': 'test', - 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,' '{{ brightness|d }},' @@ -81,434 +222,217 @@ class TestLightMQTTTemplate(unittest.TestCase): '{{ green|d }}-' '{{ blue|d }}', 'command_off_template': 'off', - 'state_template': '{{ value.split(",")[0] }}' + 'effect_list': ['colorloop', 'random'], + 'effect_command_topic': 'test_light_rgb/effect/set', + 'qos': 2 } }) - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('white_value') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert state.attributes.get(ATTR_ASSUMED_STATE) - fire_mqtt_message(self.hass, 'test_light_rgb', 'on') - self.hass.block_till_done() - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('white_value') is None +async def test_flash(hass, mqtt_mock): + """Test flash.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,{{ flash }}', + 'command_off_template': 'off', + 'qos': 0 + } + }) - def test_state_brightness_color_effect_temp_white_change_via_topic(self): - """Test state, bri, color, effect, color temp, white val change.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - 'effect_list': ['rainbow', 'colorloop'], - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,' - '{{ brightness|d }},' - '{{ color_temp|d }},' - '{{ white_value|d }},' - '{{ red|d }}-' - '{{ green|d }}-' - '{{ blue|d }},' - '{{ effect|d }}', - 'command_off_template': 'off', - 'state_template': '{{ value.split(",")[0] }}', - 'brightness_template': '{{ value.split(",")[1] }}', - 'color_temp_template': '{{ value.split(",")[2] }}', - 'white_value_template': '{{ value.split(",")[3] }}', - 'red_template': '{{ value.split(",")[4].' - 'split("-")[0] }}', - 'green_template': '{{ value.split(",")[4].' - 'split("-")[1] }}', - 'blue_template': '{{ value.split(",")[4].' - 'split("-")[2] }}', - 'effect_template': '{{ value.split(",")[5] }}' - } - }) + state = hass.states.get('light.test') + assert STATE_OFF == state.state - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('white_value') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - # turn on the light, full white - fire_mqtt_message(self.hass, 'test_light_rgb', - 'on,255,145,123,255-128-64,') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 128, 63) == state.attributes.get('rgb_color') - assert 255 == state.attributes.get('brightness') - assert 145 == state.attributes.get('color_temp') - assert 123 == state.attributes.get('white_value') - assert state.attributes.get('effect') is None - - # turn the light off - fire_mqtt_message(self.hass, 'test_light_rgb', 'off') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - # lower the brightness - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,100') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 100 == light_state.attributes['brightness'] - - # change the color temp - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,195') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 195 == light_state.attributes['color_temp'] - - # change the color - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,,41-42-43') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (243, 249, 255) == \ - light_state.attributes.get('rgb_color') - - # change the white value - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,134') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 134 == light_state.attributes['white_value'] - - # change the effect - fire_mqtt_message(self.hass, 'test_light_rgb', - 'on,,,,41-42-43,rainbow') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert 'rainbow' == light_state.attributes.get('effect') - - def test_optimistic(self): - """Test optimistic mode.""" - fake_state = ha.State('light.test', 'on', {'brightness': 95, - 'hs_color': [100, 100], - 'effect': 'random', - 'color_temp': 100, - 'white_value': 50}) - - with patch('homeassistant.components.light.mqtt_template' - '.async_get_last_state', - return_value=mock_coro(fake_state)): - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,' - '{{ brightness|d }},' - '{{ color_temp|d }},' - '{{ white_value|d }},' - '{{ red|d }}-' - '{{ green|d }}-' - '{{ blue|d }}', - 'command_off_template': 'off', - 'effect_list': ['colorloop', 'random'], - 'effect_command_topic': 'test_light_rgb/effect/set', - 'qos': 2 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 95 == state.attributes.get('brightness') - assert (100, 100) == state.attributes.get('hs_color') - assert 'random' == state.attributes.get('effect') - assert 100 == state.attributes.get('color_temp') - assert 50 == state.attributes.get('white_value') - assert state.attributes.get(ATTR_ASSUMED_STATE) - - # turn on the light - common.turn_on(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,,,,--', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - - # turn the light off - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'off', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - # turn on the light with brightness, color - common.turn_on(self.hass, 'light.test', brightness=50, - rgb_color=[75, 75, 75]) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,50,,,50-50-50', 2, False) - self.mock_publish.async_publish.reset_mock() - - # turn on the light with color temp and white val - common.turn_on(self.hass, 'light.test', - color_temp=200, white_value=139) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,,200,139,--', 2, False) - - # check the state - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 255, 255) == state.attributes['rgb_color'] - assert 50 == state.attributes['brightness'] - assert 200 == state.attributes['color_temp'] - assert 139 == state.attributes['white_value'] - - def test_flash(self): - """Test flash.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,{{ flash }}', - 'command_off_template': 'off', - 'qos': 0 - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - # short flash - common.turn_on(self.hass, 'light.test', flash='short') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,short', 0, False) - self.mock_publish.async_publish.reset_mock() - - # long flash - common.turn_on(self.hass, 'light.test', flash='long') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,long', 0, False) - - def test_transition(self): - """Test for transition time being sent when included.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,{{ transition }}', - 'command_off_template': 'off,{{ transition|d }}' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - # transition on - common.turn_on(self.hass, 'light.test', transition=10) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on,10', 0, False) - self.mock_publish.async_publish.reset_mock() - - # transition off - common.turn_off(self.hass, 'light.test', transition=4) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'off,4', 0, False) - - def test_invalid_values(self): - """Test that invalid values are ignored.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt_template', - 'name': 'test', - 'effect_list': ['rainbow', 'colorloop'], - 'state_topic': 'test_light_rgb', - 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,' - '{{ brightness|d }},' - '{{ color_temp|d }},' - '{{ red|d }}-' - '{{ green|d }}-' - '{{ blue|d }},' - '{{ effect|d }}', - 'command_off_template': 'off', - 'state_template': '{{ value.split(",")[0] }}', - 'brightness_template': '{{ value.split(",")[1] }}', - 'color_temp_template': '{{ value.split(",")[2] }}', - 'white_value_template': '{{ value.split(",")[3] }}', - 'red_template': '{{ value.split(",")[4].' - 'split("-")[0] }}', - 'green_template': '{{ value.split(",")[4].' - 'split("-")[1] }}', - 'blue_template': '{{ value.split(",")[4].' - 'split("-")[2] }}', - 'effect_template': '{{ value.split(",")[5] }}', - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('white_value') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # turn on the light, full white - fire_mqtt_message(self.hass, 'test_light_rgb', - 'on,255,215,222,255-255-255,rainbow') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - assert 215 == state.attributes.get('color_temp') - assert (255, 255, 255) == state.attributes.get('rgb_color') - assert 222 == state.attributes.get('white_value') - assert 'rainbow' == state.attributes.get('effect') - - # bad state value - fire_mqtt_message(self.hass, 'test_light_rgb', 'offf') - self.hass.block_till_done() - - # state should not have changed - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - - # bad brightness values - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,off,255-255-255') - self.hass.block_till_done() - - # brightness should not have changed - state = self.hass.states.get('light.test') - assert 255 == state.attributes.get('brightness') - - # bad color temp values - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,off,255-255-255') - self.hass.block_till_done() - - # color temp should not have changed - state = self.hass.states.get('light.test') - assert 215 == state.attributes.get('color_temp') - - # bad color values - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,255,a-b-c') - self.hass.block_till_done() - - # color should not have changed - state = self.hass.states.get('light.test') - assert (255, 255, 255) == state.attributes.get('rgb_color') - - # bad white value values - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,,,off,255-255-255') - self.hass.block_till_done() - - # white value should not have changed - state = self.hass.states.get('light.test') - assert 222 == state.attributes.get('white_value') - - # bad effect value - fire_mqtt_message(self.hass, 'test_light_rgb', 'on,255,a-b-c,white') - self.hass.block_till_done() - - # effect should not have changed - state = self.hass.states.get('light.test') - assert 'rainbow' == state.attributes.get('effect') - - def test_default_availability_payload(self): - """Test availability by default payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { +async def test_transition(hass, mqtt_mock): + """Test for transition time being sent when included.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_template', 'name': 'test', 'command_topic': 'test_light_rgb/set', 'command_on_template': 'on,{{ transition }}', - 'command_off_template': 'off,{{ transition|d }}', - 'availability_topic': 'availability-topic' + 'command_off_template': 'off,{{ transition|d }}' } }) - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('light.test') + assert STATE_OFF == state.state - fire_mqtt_message(self.hass, 'availability-topic', 'online') - self.hass.block_till_done() - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'offline') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { +async def test_invalid_values(hass, mqtt_mock): + """Test that invalid values are ignored.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { light.DOMAIN: { 'platform': 'mqtt_template', 'name': 'test', + 'effect_list': ['rainbow', 'colorloop'], + 'state_topic': 'test_light_rgb', 'command_topic': 'test_light_rgb/set', - 'command_on_template': 'on,{{ transition }}', - 'command_off_template': 'off,{{ transition|d }}', - 'availability_topic': 'availability-topic', - 'payload_available': 'good', - 'payload_not_available': 'nogood' + 'command_on_template': 'on,' + '{{ brightness|d }},' + '{{ color_temp|d }},' + '{{ red|d }}-' + '{{ green|d }}-' + '{{ blue|d }},' + '{{ effect|d }}', + 'command_off_template': 'off', + 'state_template': '{{ value.split(",")[0] }}', + 'brightness_template': '{{ value.split(",")[1] }}', + 'color_temp_template': '{{ value.split(",")[2] }}', + 'white_value_template': '{{ value.split(",")[3] }}', + 'red_template': '{{ value.split(",")[4].' + 'split("-")[0] }}', + 'green_template': '{{ value.split(",")[4].' + 'split("-")[1] }}', + 'blue_template': '{{ value.split(",")[4].' + 'split("-")[2] }}', + 'effect_template': '{{ value.split(",")[5] }}', } }) - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) - fire_mqtt_message(self.hass, 'availability-topic', 'good') - self.hass.block_till_done() + # turn on the light, full white + async_fire_mqtt_message(hass, 'test_light_rgb', + 'on,255,215,222,255-255-255,rainbow') + await hass.async_block_till_done() - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + assert 215 == state.attributes.get('color_temp') + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 222 == state.attributes.get('white_value') + assert 'rainbow' == state.attributes.get('effect') - fire_mqtt_message(self.hass, 'availability-topic', 'nogood') - self.hass.block_till_done() + # bad state value + async_fire_mqtt_message(hass, 'test_light_rgb', 'offf') + await hass.async_block_till_done() - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state + # state should not have changed + state = hass.states.get('light.test') + assert STATE_ON == state.state + + # bad brightness values + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,off,255-255-255') + await hass.async_block_till_done() + + # brightness should not have changed + state = hass.states.get('light.test') + assert 255 == state.attributes.get('brightness') + + # bad color temp values + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,,off,255-255-255') + await hass.async_block_till_done() + + # color temp should not have changed + state = hass.states.get('light.test') + assert 215 == state.attributes.get('color_temp') + + # bad color values + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,255,a-b-c') + await hass.async_block_till_done() + + # color should not have changed + state = hass.states.get('light.test') + assert (255, 255, 255) == state.attributes.get('rgb_color') + + # bad white value values + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,,,off,255-255-255') + await hass.async_block_till_done() + + # white value should not have changed + state = hass.states.get('light.test') + assert 222 == state.attributes.get('white_value') + + # bad effect value + async_fire_mqtt_message(hass, 'test_light_rgb', 'on,255,a-b-c,white') + await hass.async_block_till_done() + + # effect should not have changed + state = hass.states.get('light.test') + assert 'rainbow' == state.attributes.get('effect') + + +async def test_default_availability_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,{{ transition }}', + 'command_off_template': 'off,{{ transition|d }}', + 'availability_topic': 'availability-topic' + } + }) + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'online') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'offline') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + +async def test_custom_availability_payload(hass, mqtt_mock): + """Test availability by custom payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt_template', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'command_on_template': 'on,{{ transition }}', + 'command_off_template': 'off,{{ transition|d }}', + 'availability_topic': 'availability-topic', + 'payload_available': 'good', + 'payload_not_available': 'nogood' + } + }) + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'good') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state + + async_fire_mqtt_message(hass, 'availability-topic', 'nogood') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state From 544a3b929f4c9db309aa4e91f03ea40c9d461753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Thu, 25 Oct 2018 10:16:09 +0200 Subject: [PATCH 051/230] SMHI weather component not showing correct values in current forecast (#17783) * fixes not showing current forecast correctly * Update config_flow.py * Update smhi.py * Update requirements_all.txt * Update requirements_test_all.txt --- homeassistant/components/smhi/__init__.py | 2 +- homeassistant/components/smhi/config_flow.py | 2 -- homeassistant/components/weather/smhi.py | 1 - requirements_all.txt | 4 +--- requirements_test_all.txt | 4 +--- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py index d0e4b6ef487..2421addfd0c 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -12,7 +12,7 @@ from homeassistant.core import Config, HomeAssistant from .config_flow import smhi_locations # noqa: F401 from .const import DOMAIN # noqa: F401 -REQUIREMENTS = ['smhi-pkg==1.0.4'] +REQUIREMENTS = ['smhi-pkg==1.0.5'] DEFAULT_NAME = 'smhi' diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index 3c9875ab797..e461c6d195d 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -20,8 +20,6 @@ from homeassistant.util import slugify from .const import DOMAIN, HOME_LOCATION_NAME -REQUIREMENTS = ['smhi-pkg==1.0.4'] - @callback def smhi_locations(hass: HomeAssistant): diff --git a/homeassistant/components/weather/smhi.py b/homeassistant/components/weather/smhi.py index c24d3f8f091..3bbaab3f8ec 100644 --- a/homeassistant/components/weather/smhi.py +++ b/homeassistant/components/weather/smhi.py @@ -30,7 +30,6 @@ from homeassistant.components.smhi.const import ( ENTITY_ID_SENSOR_FORMAT, ATTR_SMHI_CLOUDINESS) DEPENDENCIES = ['smhi'] -REQUIREMENTS = ['smhi-pkg==1.0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index afd5b4e9380..1fe3ce3372f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1383,9 +1383,7 @@ smappy==0.2.16 # smbus-cffi==0.5.1 # homeassistant.components.smhi -# homeassistant.components.smhi.config_flow -# homeassistant.components.weather.smhi -smhi-pkg==1.0.4 +smhi-pkg==1.0.5 # homeassistant.components.media_player.snapcast snapcast==2.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 90d7756e69c..61bd3db0276 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -227,9 +227,7 @@ simplisafe-python==3.1.13 sleepyq==0.6 # homeassistant.components.smhi -# homeassistant.components.smhi.config_flow -# homeassistant.components.weather.smhi -smhi-pkg==1.0.4 +smhi-pkg==1.0.5 # homeassistant.components.climate.honeywell somecomfort==0.5.2 From 3c68db32d6e80fa774ca72d4ac82361ffab66876 Mon Sep 17 00:00:00 2001 From: liaanvdm <43240119+liaanvdm@users.noreply.github.com> Date: Thu, 25 Oct 2018 12:21:20 +0200 Subject: [PATCH 052/230] Restore manual alarm-control-panel state using async_get_last_state (#17521) * Restore manual alarm-control-panel state using async_get_last_state This is to address issue #10793 * Added tests for restoring manual alarm state on startup * Cleaned up test formatting * Fixed more linting errors * Changed async methods to use asynch/await syntax * Removed unused asyncio import --- .../components/alarm_control_panel/manual.py | 8 +++ .../alarm_control_panel/test_manual.py | 55 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index 41f7d6988a8..362923a4ce2 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -21,6 +21,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time import homeassistant.util.dt as dt_util +from homeassistant.helpers.restore_state import async_get_last_state _LOGGER = logging.getLogger(__name__) @@ -306,3 +307,10 @@ class ManualAlarm(alarm.AlarmControlPanel): state_attr[ATTR_POST_PENDING_STATE] = self._state return state_attr + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + state = await async_get_last_state(self.hass, self.entity_id) + if state: + self._state = state.state + self._state_ts = state.last_updated diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 7d87d05b58f..b39d4ecbbe9 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -3,18 +3,17 @@ from datetime import timedelta import unittest from unittest.mock import patch, MagicMock from homeassistant.components.alarm_control_panel import demo - - -from homeassistant.setup import setup_component +from homeassistant.setup import setup_component, async_setup_component from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) from homeassistant.components import alarm_control_panel import homeassistant.util.dt as dt_util - -from tests.common import fire_time_changed, get_test_home_assistant +from tests.common import (fire_time_changed, get_test_home_assistant, + mock_component, mock_restore_cache) from tests.components.alarm_control_panel import common +from homeassistant.core import State, CoreState CODE = 'HELLO_CODE' @@ -1319,3 +1318,49 @@ class TestAlarmControlPanelManual(unittest.TestCase): state = self.hass.states.get(entity_id) assert STATE_ALARM_TRIGGERED == state.state + + +async def test_restore_armed_state(hass): + """Ensure armed state is restored on startup.""" + mock_restore_cache(hass, ( + State('alarm_control_panel.test', STATE_ALARM_ARMED_AWAY), + )) + + hass.state = CoreState.starting + mock_component(hass, 'recorder') + + assert await async_setup_component(hass, alarm_control_panel.DOMAIN, { + 'alarm_control_panel': { + 'platform': 'manual', + 'name': 'test', + 'pending_time': 0, + 'trigger_time': 0, + 'disarm_after_trigger': False + }}) + + state = hass.states.get('alarm_control_panel.test') + assert state + assert state.state == STATE_ALARM_ARMED_AWAY + + +async def test_restore_disarmed_state(hass): + """Ensure disarmed state is restored on startup.""" + mock_restore_cache(hass, ( + State('alarm_control_panel.test', STATE_ALARM_DISARMED), + )) + + hass.state = CoreState.starting + mock_component(hass, 'recorder') + + assert await async_setup_component(hass, alarm_control_panel.DOMAIN, { + 'alarm_control_panel': { + 'platform': 'manual', + 'name': 'test', + 'pending_time': 0, + 'trigger_time': 0, + 'disarm_after_trigger': False + }}) + + state = hass.states.get('alarm_control_panel.test') + assert state + assert state.state == STATE_ALARM_DISARMED From b6e8cafdea60e92371dd7908ef57aed2971d0879 Mon Sep 17 00:00:00 2001 From: Jan van Helvoort Date: Thu, 25 Oct 2018 15:58:09 +0200 Subject: [PATCH 053/230] typo (#17787) --- homeassistant/components/zwave/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 35703d64974..1743abaf810 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -387,7 +387,7 @@ async def async_setup_entry(hass, config_entry): def network_complete_some_dead(): """Handle the querying of all nodes on network.""" _LOGGER.info("Z-Wave network is complete. All nodes on the network " - "have been queried, but some node are marked dead") + "have been queried, but some nodes are marked dead") hass.bus.fire(const.EVENT_NETWORK_COMPLETE_SOME_DEAD) dispatcher.connect( From b5284aa4458c170c8c3a1238b3029a12e58f5eff Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Oct 2018 16:43:11 +0200 Subject: [PATCH 054/230] Fix device reg considered changed (#17764) * Fix device reg considered changed * Better syntax --- homeassistant/helpers/device_registry.py | 7 ++++--- tests/helpers/test_device_registry.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 478b29c75b2..69a3f234c22 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -87,8 +87,8 @@ class DeviceRegistry: device.id, add_config_entry_id=config_entry_id, hub_device_id=hub_device_id, - merge_connections=connections, - merge_identifiers=identifiers, + merge_connections=connections or _UNDEF, + merge_identifiers=identifiers or _UNDEF, manufacturer=manufacturer, model=model, name=name, @@ -128,7 +128,8 @@ class DeviceRegistry: ('identifiers', merge_identifiers), ): old_value = getattr(old, attr_name) - if value is not _UNDEF and value != old_value: + # If not undefined, check if `value` contains new items. + if value is not _UNDEF and not value.issubset(old_value): changes[attr_name] = old_value | value for attr_name, value in ( diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index a87ad3d483a..2f203ceb963 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,4 +1,6 @@ """Tests for the Device Registry.""" +from unittest.mock import patch + import pytest from homeassistant.helpers import device_registry @@ -239,3 +241,21 @@ async def test_loading_saving_data(hass, registry): assert orig_hub == new_hub assert orig_light == new_light + + +async def test_no_unnecessary_changes(registry): + """Make sure we do not consider devices changes.""" + entry = registry.async_get_or_create( + config_entry_id='1234', + connections={('ethernet', '12:34:56:78:90:AB:CD:EF')}, + identifiers={('hue', '456'), ('bla', '123')}, + ) + with patch('homeassistant.helpers.device_registry' + '.DeviceRegistry.async_schedule_save') as mock_save: + entry2 = registry.async_get_or_create( + config_entry_id='1234', + identifiers={('hue', '456')}, + ) + + assert entry.id == entry2.id + assert len(mock_save.mock_calls) == 0 From 312d49caecd0afcc9ac51cd891bf0ae27c166891 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Oct 2018 16:44:57 +0200 Subject: [PATCH 055/230] Allow creating signed urls (#17759) * Allow creating signed urls * Fix parameter * Lint --- homeassistant/auth/__init__.py | 1 - homeassistant/components/auth/__init__.py | 25 +++++++++ homeassistant/components/http/auth.py | 66 +++++++++++++++++++++++ tests/components/auth/test_init.py | 27 ++++++++++ tests/components/http/test_auth.py | 59 ++++++++++++++++++-- 5 files changed, 174 insertions(+), 4 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index e584d5b70e5..9fd9bf3fa50 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -342,7 +342,6 @@ class AuthManager: """Create a new access token.""" self._store.async_log_refresh_token_usage(refresh_token, remote_ip) - # pylint: disable=no-self-use now = dt_util.utcnow() return jwt.encode({ 'iss': refresh_token.id, diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index f4b314918f7..2e74961d11b 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -129,6 +129,7 @@ from homeassistant.auth.models import User, Credentials, \ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN from homeassistant.components import websocket_api from homeassistant.components.http import KEY_REAL_IP +from homeassistant.components.http.auth import async_sign_path from homeassistant.components.http.ban import log_invalid_auth from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView @@ -169,6 +170,14 @@ SCHEMA_WS_DELETE_REFRESH_TOKEN = \ vol.Required('refresh_token_id'): str, }) +WS_TYPE_SIGN_PATH = 'auth/sign_path' +SCHEMA_WS_SIGN_PATH = \ + websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_SIGN_PATH, + vol.Required('path'): str, + vol.Optional('expires', default=30): int, + }) + RESULT_TYPE_CREDENTIALS = 'credentials' RESULT_TYPE_USER = 'user' @@ -201,6 +210,11 @@ async def async_setup(hass, config): websocket_delete_refresh_token, SCHEMA_WS_DELETE_REFRESH_TOKEN ) + hass.components.websocket_api.async_register_command( + WS_TYPE_SIGN_PATH, + websocket_sign_path, + SCHEMA_WS_SIGN_PATH + ) await login_flow.async_setup(hass, store_result) await mfa_setup_flow.async_setup(hass) @@ -500,3 +514,14 @@ async def websocket_delete_refresh_token( connection.send_message( websocket_api.result_message(msg['id'], {})) + + +@websocket_api.ws_require_user() +@callback +def websocket_sign_path( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + """Handle a sign path request.""" + connection.send_message(websocket_api.result_message(msg['id'], { + 'path': async_sign_path(hass, connection.refresh_token_id, msg['path'], + timedelta(seconds=msg['expires'])) + })) diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index bcc86b36dbe..64ee7fb8a3f 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -6,16 +6,39 @@ import logging from aiohttp import hdrs from aiohttp.web import middleware +import jwt from homeassistant.core import callback from homeassistant.const import HTTP_HEADER_HA_AUTH +from homeassistant.auth.util import generate_secret +from homeassistant.util import dt as dt_util + from .const import KEY_AUTHENTICATED, KEY_REAL_IP DATA_API_PASSWORD = 'api_password' +DATA_SIGN_SECRET = 'http.auth.sign_secret' +SIGN_QUERY_PARAM = 'authSig' _LOGGER = logging.getLogger(__name__) +@callback +def async_sign_path(hass, refresh_token_id, path, expiration): + """Sign a path for temporary access without auth header.""" + secret = hass.data.get(DATA_SIGN_SECRET) + + if secret is None: + secret = hass.data[DATA_SIGN_SECRET] = generate_secret() + + now = dt_util.utcnow() + return "{}?{}={}".format(path, SIGN_QUERY_PARAM, jwt.encode({ + 'iss': refresh_token_id, + 'path': path, + 'iat': now, + 'exp': now + expiration, + }, secret, algorithm='HS256').decode()) + + @callback def setup_auth(app, trusted_networks, use_auth, support_legacy=False, api_password=None): @@ -43,6 +66,12 @@ def setup_auth(app, trusted_networks, use_auth, # it included both use_auth and api_password Basic auth authenticated = True + # We first start with a string check to avoid parsing query params + # for every request. + elif (request.method == "GET" and SIGN_QUERY_PARAM in request.query and + await async_validate_signed_request(request)): + authenticated = True + elif (legacy_auth and HTTP_HEADER_HA_AUTH in request.headers and hmac.compare_digest( api_password.encode('utf-8'), @@ -131,3 +160,40 @@ async def async_validate_auth_header(request, api_password=None): password.encode('utf-8')) return False + + +async def async_validate_signed_request(request): + """Validate a signed request.""" + hass = request.app['hass'] + secret = hass.data.get(DATA_SIGN_SECRET) + + if secret is None: + return False + + signature = request.query.get(SIGN_QUERY_PARAM) + + if signature is None: + return False + + try: + claims = jwt.decode( + signature, + secret, + algorithms=['HS256'], + options={'verify_iss': False} + ) + except jwt.InvalidTokenError: + return False + + if claims['path'] != request.path: + return False + + refresh_token = await hass.auth.async_get_refresh_token(claims['iss']) + + if refresh_token is None: + return False + + request['hass_refresh_token'] = refresh_token + request['hass_user'] = refresh_token.user + + return True diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index a3974553661..e28f7be4341 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -347,3 +347,30 @@ async def test_ws_delete_refresh_token(hass, hass_ws_client, refresh_token = await hass.auth.async_validate_access_token( hass_access_token) assert refresh_token is None + + +async def test_ws_sign_path(hass, hass_ws_client, hass_access_token): + """Test signing a path.""" + assert await async_setup_component(hass, 'auth', {'http': {}}) + ws_client = await hass_ws_client(hass, hass_access_token) + + refresh_token = await hass.auth.async_validate_access_token( + hass_access_token) + + with patch('homeassistant.components.auth.async_sign_path', + return_value='hello_world') as mock_sign: + await ws_client.send_json({ + 'id': 5, + 'type': auth.WS_TYPE_SIGN_PATH, + 'path': '/api/hello', + 'expires': 20 + }) + + result = await ws_client.receive_json() + assert result['success'], result + assert result['result'] == {'path': 'hello_world'} + assert len(mock_sign.mock_calls) == 1 + hass, p_refresh_token, path, expires = mock_sign.mock_calls[0][1] + assert p_refresh_token == refresh_token.id + assert path == '/api/hello' + assert expires.total_seconds() == 20 diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index e96531c0961..2746abcf15c 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -1,5 +1,5 @@ """The tests for the Home Assistant HTTP component.""" -# pylint: disable=protected-access +from datetime import timedelta from ipaddress import ip_network from unittest.mock import patch @@ -7,7 +7,7 @@ import pytest from aiohttp import BasicAuth, web from aiohttp.web_exceptions import HTTPUnauthorized -from homeassistant.components.http.auth import setup_auth +from homeassistant.components.http.auth import setup_auth, async_sign_path from homeassistant.components.http.const import KEY_AUTHENTICATED from homeassistant.components.http.real_ip import setup_real_ip from homeassistant.const import HTTP_HEADER_HA_AUTH @@ -33,7 +33,16 @@ async def mock_handler(request): """Return if request was authenticated.""" if not request[KEY_AUTHENTICATED]: raise HTTPUnauthorized - return web.Response(status=200) + + token = request.get('hass_refresh_token') + token_id = token.id if token else None + user = request.get('hass_user') + user_id = user.id if user else None + + return web.json_response(status=200, data={ + 'refresh_token_id': token_id, + 'user_id': user_id, + }) @pytest.fixture @@ -248,3 +257,47 @@ async def test_auth_legacy_support_api_password_access(app, aiohttp_client): '/', auth=BasicAuth('homeassistant', API_PASSWORD)) assert req.status == 200 + + +async def test_auth_access_signed_path( + hass, app, aiohttp_client, hass_access_token): + """Test access with signed url.""" + app.router.add_post('/', mock_handler) + app.router.add_get('/another_path', mock_handler) + setup_auth(app, [], True, api_password=None) + client = await aiohttp_client(app) + + refresh_token = await hass.auth.async_validate_access_token( + hass_access_token) + + signed_path = async_sign_path( + hass, refresh_token.id, '/', timedelta(seconds=5) + ) + + req = await client.get(signed_path) + assert req.status == 200 + data = await req.json() + assert data['refresh_token_id'] == refresh_token.id + assert data['user_id'] == refresh_token.user.id + + # Use signature on other path + req = await client.get( + '/another_path?{}'.format(signed_path.split('?')[1])) + assert req.status == 401 + + # We only allow GET + req = await client.post(signed_path) + assert req.status == 401 + + # Never valid as expired in the past. + expired_signed_path = async_sign_path( + hass, refresh_token.id, '/', timedelta(seconds=-5) + ) + + req = await client.get(expired_signed_path) + assert req.status == 401 + + # refresh token gone should also invalidate signature + await hass.auth.async_remove_refresh_token(refresh_token) + req = await client.get(signed_path) + assert req.status == 401 From 9c7d3c2a63b9850ffaad7d298816b533979dee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Thu, 25 Oct 2018 15:46:43 +0100 Subject: [PATCH 056/230] Add contact sensors to alexa smart home (#17704) * add contact sensors to alexa smart home * door sensors are binary_sensors * fix tests; cleanup * lint * fix state * Add missing doc string --- homeassistant/components/alexa/smart_home.py | 61 ++++++++++++++++++-- tests/components/alexa/test_smart_home.py | 31 ++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index f88d81ab851..df477a3b653 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -5,15 +5,16 @@ from datetime import datetime from uuid import uuid4 from homeassistant.components import ( - alert, automation, cover, climate, fan, group, input_boolean, light, lock, - media_player, scene, script, switch, http, sensor) + alert, automation, binary_sensor, cover, climate, fan, group, + input_boolean, light, lock, media_player, scene, script, switch, http, + sensor) import homeassistant.core as ha import homeassistant.util.color as color_util from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.decorator import Registry from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, - ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK, + ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -71,6 +72,9 @@ class _DisplayCategory: # Indicates media devices with video or photo capabilities. CAMERA = "CAMERA" + # Indicates an endpoint that detects and reports contact. + CONTACT_SENSOR = "CONTACT_SENSOR" + # Indicates a door. DOOR = "DOOR" @@ -414,6 +418,29 @@ class _AlexaTemperatureSensor(_AlexaInterface): } +class _AlexaContactSensor(_AlexaInterface): + def __init__(self, hass, entity): + _AlexaInterface.__init__(self, entity) + self.hass = hass + + def name(self): + return 'Alexa.ContactSensor' + + def properties_supported(self): + return [{'name': 'detectionState'}] + + def properties_retrievable(self): + return True + + def get_property(self, name): + if name != 'detectionState': + raise _UnsupportedProperty(name) + + if self.entity.state == STATE_ON: + return 'DETECTED' + return 'NOT_DETECTED' + + class _AlexaThermostatController(_AlexaInterface): def __init__(self, hass, entity): _AlexaInterface.__init__(self, entity) @@ -625,6 +652,32 @@ class _SensorCapabilities(_AlexaEntity): yield _AlexaTemperatureSensor(self.hass, self.entity) +@ENTITY_ADAPTERS.register(binary_sensor.DOMAIN) +class _BinarySensorCapabilities(_AlexaEntity): + TYPE_CONTACT = 'contact' + + def default_display_categories(self): + sensor_type = self.get_type() + if sensor_type is self.TYPE_CONTACT: + return [_DisplayCategory.CONTACT_SENSOR] + + def interfaces(self): + sensor_type = self.get_type() + if sensor_type is self.TYPE_CONTACT: + yield _AlexaContactSensor(self.hass, self.entity) + + def get_type(self): + """Return the type of binary sensor.""" + attrs = self.entity.attributes + if attrs.get(ATTR_DEVICE_CLASS) in ( + 'door', + 'garage_door', + 'opening', + 'window', + ): + return self.TYPE_CONTACT + + class _Cause: """Possible causes for property changes. diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 88c69194407..9596f341447 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -694,6 +694,37 @@ def test_temp_sensor(hass): {'value': 42.0, 'scale': 'FAHRENHEIT'}) +@asyncio.coroutine +def test_contact_sensor(hass): + """Test contact sensor discovery.""" + device = ( + 'binary_sensor.test_contact', + 'on', + { + 'friendly_name': "Test Contact Sensor", + 'device_class': 'door', + } + ) + appliance = yield from discovery_test(device, hass) + + assert appliance['endpointId'] == 'binary_sensor#test_contact' + assert appliance['displayCategories'][0] == 'CONTACT_SENSOR' + assert appliance['friendlyName'] == 'Test Contact Sensor' + + (capability,) = assert_endpoint_capabilities( + appliance, + 'Alexa.ContactSensor') + assert capability['interface'] == 'Alexa.ContactSensor' + properties = capability['properties'] + assert properties['retrievable'] is True + assert {'name': 'detectionState'} in properties['supported'] + + properties = yield from reported_properties(hass, + 'binary_sensor#test_contact') + properties.assert_equal('Alexa.ContactSensor', 'detectionState', + 'DETECTED') + + @asyncio.coroutine def test_unknown_sensor(hass): """Test sensors of unknown quantities are not discovered.""" From 77bf10e37c8947d2c2ab7c0a0517dbd2372ca3da Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 25 Oct 2018 19:57:36 +0200 Subject: [PATCH 057/230] Fix unloading an entry can leave states around (#17786) * Add test that tests unloading on remove * Add more test things * Untangle entity remove code from entity platform * Don't add default implementation of async_will_remove * Keep entity weakref alive --- homeassistant/helpers/entity.py | 10 ++-- homeassistant/helpers/entity_platform.py | 19 ++---- tests/test_config_entries.py | 75 ++++++++++++++++++++---- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 987bdeae6ca..687ed0b6f8b 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -363,14 +363,16 @@ class Entity: async def async_remove(self): """Remove entity from Home Assistant.""" + will_remove = getattr(self, 'async_will_remove_from_hass', None) + + if will_remove: + await will_remove() # pylint: disable=not-callable + if self._on_remove is not None: while self._on_remove: self._on_remove.pop()() - if self.platform is not None: - await self.platform.async_remove_entity(self.entity_id) - else: - self.hass.states.async_remove(self.entity_id) + self.hass.states.async_remove(self.entity_id) @callback def async_registry_updated(self, old, new): diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 3ab45577236..5fd580a33f0 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -345,8 +345,10 @@ class EntityPlatform: raise HomeAssistantError( msg) - self.entities[entity.entity_id] = entity - component_entities.add(entity.entity_id) + entity_id = entity.entity_id + self.entities[entity_id] = entity + component_entities.add(entity_id) + entity.async_on_remove(lambda: self.entities.pop(entity_id)) if hasattr(entity, 'async_added_to_hass'): await entity.async_added_to_hass() @@ -365,7 +367,7 @@ class EntityPlatform: if not self.entities: return - tasks = [self._async_remove_entity(entity_id) + tasks = [self.async_remove_entity(entity_id) for entity_id in self.entities] await asyncio.wait(tasks, loop=self.hass.loop) @@ -376,7 +378,7 @@ class EntityPlatform: async def async_remove_entity(self, entity_id): """Remove entity id from platform.""" - await self._async_remove_entity(entity_id) + await self.entities[entity_id].async_remove() # Clean up polling job if no longer needed if (self._async_unsub_polling is not None and @@ -385,15 +387,6 @@ class EntityPlatform: self._async_unsub_polling() self._async_unsub_polling = None - async def _async_remove_entity(self, entity_id): - """Remove entity id from platform.""" - entity = self.entities.pop(entity_id) - - if hasattr(entity, 'async_will_remove_from_hass'): - await entity.async_will_remove_from_hass() - - self.hass.states.async_remove(entity_id) - async def _update_entity_states(self, now): """Update the states of all the polling entities. diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 340118502b1..59777e2e6bb 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -11,7 +11,8 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt from tests.common import ( - MockModule, mock_coro, MockConfigEntry, async_fire_time_changed) + MockModule, mock_coro, MockConfigEntry, async_fire_time_changed, + MockPlatform, MockEntity) @pytest.fixture @@ -40,35 +41,87 @@ def test_call_setup_entry(hass): assert len(mock_setup_entry.mock_calls) == 1 -@asyncio.coroutine -def test_remove_entry(hass, manager): +async def test_remove_entry(hass, manager): """Test that we can remove an entry.""" - mock_unload_entry = MagicMock(return_value=mock_coro(True)) + async def mock_setup_entry(hass, entry): + """Mock setting up entry.""" + hass.loop.create_task(hass.config_entries.async_forward_entry_setup( + entry, 'light')) + return True + async def mock_unload_entry(hass, entry): + """Mock unloading an entry.""" + result = await hass.config_entries.async_forward_entry_unload( + entry, 'light') + assert result + return result + + entity = MockEntity( + unique_id='1234', + name='Test Entity', + ) + + async def mock_setup_entry_platform(hass, entry, async_add_entities): + """Mock setting up platform.""" + async_add_entities([entity]) + + loader.set_component(hass, 'test', MockModule( + 'test', + async_setup_entry=mock_setup_entry, + async_unload_entry=mock_unload_entry + )) loader.set_component( - hass, 'test', - MockModule('comp', async_unload_entry=mock_unload_entry)) + hass, 'light.test', + MockPlatform(async_setup_entry=mock_setup_entry_platform)) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) - MockConfigEntry( + entry = MockConfigEntry( domain='test', entry_id='test2', - state=config_entries.ENTRY_STATE_LOADED - ).add_to_manager(manager) + ) + entry.add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager) + # Check all config entries exist assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test2', 'test3'] - result = yield from manager.async_remove('test2') + # Setup entry + await entry.async_setup(hass) + await hass.async_block_till_done() + # Check entity state got added + assert hass.states.get('light.test_entity') is not None + # Group all_lights, light.test_entity + assert len(hass.states.async_all()) == 2 + + # Check entity got added to entity registry + ent_reg = await hass.helpers.entity_registry.async_get_registry() + assert len(ent_reg.entities) == 1 + entity_entry = list(ent_reg.entities.values())[0] + assert entity_entry.config_entry_id == entry.entry_id + + # Remove entry + result = await manager.async_remove('test2') + await hass.async_block_till_done() + + # Check that unload went well and so no need to restart assert result == { 'require_restart': False } + + # Check that config entry was removed. assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test3'] - assert len(mock_unload_entry.mock_calls) == 1 + # Check that entity state has been removed + assert hass.states.get('light.test_entity') is None + # Just Group all_lights + assert len(hass.states.async_all()) == 1 + + # Check that entity registry entry no longer references config_entry_id + entity_entry = list(ent_reg.entities.values())[0] + assert entity_entry.config_entry_id is None @asyncio.coroutine From 43048962f25bac7a59e169740b7a4ae1c7a925a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 25 Oct 2018 23:15:20 +0300 Subject: [PATCH 058/230] Upgrade flake8 to 3.6.0 (#17770) * Upgrade flake8 to 3.6.0 * flake8/pylint comment tweaks * flake8 F841 fixes * flake8 W605 fix * Ignore pyflakes bug #373 false positives https://github.com/PyCQA/pyflakes/issues/373 * pycodestyle bug #811 workaround https://github.com/PyCQA/pycodestyle/issues/811 --- homeassistant/components/cloud/auth_api.py | 2 +- homeassistant/components/cover/garadget.py | 4 ++-- homeassistant/components/device_tracker/__init__.py | 4 ++-- homeassistant/components/device_tracker/tplink.py | 2 +- homeassistant/components/google_assistant/smart_home.py | 2 +- homeassistant/components/light/tplink.py | 3 ++- homeassistant/components/light/yeelight.py | 3 ++- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/sensor/tcp.py | 2 +- homeassistant/components/snips.py | 2 +- homeassistant/components/zwave/__init__.py | 2 +- homeassistant/helpers/service.py | 2 +- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- script/gen_requirements_all.py | 2 +- 15 files changed, 19 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/cloud/auth_api.py b/homeassistant/components/cloud/auth_api.py index 042b90bf9cb..954d28b803f 100644 --- a/homeassistant/components/cloud/auth_api.py +++ b/homeassistant/components/cloud/auth_api.py @@ -144,7 +144,7 @@ def _authenticate(cloud, email, password): cognito.authenticate(password=password) return cognito - except ForceChangePasswordException as err: + except ForceChangePasswordException: raise PasswordChangeRequired except ClientError as err: diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/cover/garadget.py index 7a04aa4c71a..03756a971bc 100644 --- a/homeassistant/components/cover/garadget.py +++ b/homeassistant/components/cover/garadget.py @@ -106,7 +106,7 @@ class GaradgetCover(CoverDevice): self._state = STATE_OFFLINE self._available = False self._name = DEFAULT_NAME - except KeyError as ex: + except KeyError: _LOGGER.warning("Garadget device %(device)s seems to be offline", dict(device=self.device_id)) self._name = DEFAULT_NAME @@ -235,7 +235,7 @@ class GaradgetCover(CoverDevice): _LOGGER.error( "Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE - except KeyError as ex: + except KeyError: _LOGGER.warning("Garadget device %(device)s seems to be offline", dict(device=self.device_id)) self._state = STATE_OFFLINE diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index cbf32b4cd5a..82a9fefbb71 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -699,8 +699,8 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, seen.add(mac) try: - extra_attributes = (await - scanner.async_get_extra_attributes(mac)) + extra_attributes = \ + await scanner.async_get_extra_attributes(mac) except NotImplementedError: extra_attributes = dict() diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 9bf85e39faf..78f16a82d56 100644 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -254,7 +254,7 @@ class Tplink3DeviceScanner(Tplink1DeviceScanner): self.sysauth = regex_result.group(1) _LOGGER.info(self.sysauth) return True - except (ValueError, KeyError) as _: + except (ValueError, KeyError): _LOGGER.error("Couldn't fetch auth tokens! Response was: %s", response.text) return False diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 1cb4bf4cb32..9c8f949b175 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -213,7 +213,7 @@ async def _process(hass, config, message): 'requestId': request_id, 'payload': {'errorCode': err.code} } - except Exception as err: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except _LOGGER.exception('Unexpected error') return { 'requestId': request_id, diff --git a/homeassistant/components/light/tplink.py b/homeassistant/components/light/tplink.py index 0cc02e82b65..b0f4c6d1a3c 100644 --- a/homeassistant/components/light/tplink.py +++ b/homeassistant/components/light/tplink.py @@ -56,7 +56,8 @@ def brightness_from_percentage(percent): class TPLinkSmartBulb(Light): """Representation of a TPLink Smart Bulb.""" - def __init__(self, smartbulb: 'SmartBulb', name) -> None: + # F821: https://github.com/PyCQA/pyflakes/issues/373 + def __init__(self, smartbulb: 'SmartBulb', name) -> None: # noqa: F821 """Initialize the bulb.""" self.smartbulb = smartbulb self._name = name diff --git a/homeassistant/components/light/yeelight.py b/homeassistant/components/light/yeelight.py index 7efd62e3de5..1275d854e0b 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/light/yeelight.py @@ -278,8 +278,9 @@ class YeelightLight(Light): def _properties(self) -> dict: return self._bulb.last_properties + # F821: https://github.com/PyCQA/pyflakes/issues/373 @property - def _bulb(self) -> 'yeelight.Bulb': + def _bulb(self) -> 'yeelight.Bulb': # noqa: F821 import yeelight if self._bulb_device is None: try: diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 2f1895019dd..e06e025c7ab 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -35,7 +35,7 @@ from homeassistant.util.async_ import ( run_callback_threadsafe, run_coroutine_threadsafe) # Loading the config flow file will register the flow -from . import config_flow # noqa # pylint: disable=unused-import +from . import config_flow # noqa pylint: disable=unused-import from .const import CONF_BROKER, CONF_DISCOVERY, DEFAULT_DISCOVERY from .server import HBMQTT_CONFIG_SCHEMA diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 19197c06295..d214bd3d425 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -130,7 +130,7 @@ class TcpSensor(Entity): self._state = self._config[CONF_VALUE_TEMPLATE].render( value=value) return - except TemplateError as err: + except TemplateError: _LOGGER.error( "Unable to render template of %r with value: %r", self._config[CONF_VALUE_TEMPLATE], value) diff --git a/homeassistant/components/snips.py b/homeassistant/components/snips.py index 88a93408056..1aebeae59cb 100644 --- a/homeassistant/components/snips.py +++ b/homeassistant/components/snips.py @@ -142,7 +142,7 @@ async def async_setup(hass, config): hass, DOMAIN, intent_type, slots, request['input']) if 'plain' in intent_response.speech: snips_response = intent_response.speech['plain']['speech'] - except intent.UnknownIntent as err: + except intent.UnknownIntent: _LOGGER.warning("Received unknown intent %s", request['intent']['intentName']) except intent.IntentError: diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 1743abaf810..e91ba244ce8 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -29,7 +29,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send) from . import const -from . import config_flow # noqa # pylint: disable=unused-import +from . import config_flow # noqa pylint: disable=unused-import from .const import ( CONF_AUTOHEAL, CONF_DEBUG, CONF_POLLING_INTERVAL, CONF_USB_STICK_PATH, CONF_CONFIG_PATH, CONF_NETWORK_KEY, diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 3abf1dab59e..0f394a6f153 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -56,7 +56,7 @@ async def async_call_from_config(hass, config, blocking=False, variables=None, except TemplateError as ex: _LOGGER.error('Error rendering service name template: %s', ex) return - except vol.Invalid as ex: + except vol.Invalid: _LOGGER.error('Template rendered invalid service: %s', domain_service) return diff --git a/requirements_test.txt b/requirements_test.txt index 5d5d1c4fd04..29e31c977ac 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,7 +4,7 @@ asynctest==0.12.2 coveralls==1.2.0 flake8-docstrings==1.3.0 -flake8==3.5 +flake8==3.6.0 mock-open==1.3.1 mypy==0.641 pydocstyle==2.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 61bd3db0276..e81e8664856 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -5,7 +5,7 @@ asynctest==0.12.2 coveralls==1.2.0 flake8-docstrings==1.3.0 -flake8==3.5 +flake8==3.6.0 mock-open==1.3.1 mypy==0.641 pydocstyle==2.1.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 491531ee12b..945bc60b4ea 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -90,7 +90,7 @@ TEST_REQUIREMENTS = ( 'pyspcwebgw', 'python-forecastio', 'python-nest', - 'pytradfri\[async\]', + 'pytradfri\\[async\\]', 'pyunifi', 'pyupnp-async', 'pywebpush', From 901c4f18cb441b04db30b4f883292f1d73e4ae89 Mon Sep 17 00:00:00 2001 From: Ben Schattinger Date: Thu, 25 Oct 2018 16:56:10 -0400 Subject: [PATCH 059/230] Install face_recognition on Docker build (#17502) * Install face_recognition on Docker * Update setup_docker_prereqs --- Dockerfile | 1 + virtualization/Docker/Dockerfile.dev | 1 + virtualization/Docker/setup_docker_prereqs | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index c84e6162d04..92b85c29325 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ LABEL maintainer="Paulus Schoutsen " #ENV INSTALL_FFMPEG no #ENV INSTALL_LIBCEC no #ENV INSTALL_SSOCR no +#ENV INSTALL_DLIB no #ENV INSTALL_IPERF3 no VOLUME /config diff --git a/virtualization/Docker/Dockerfile.dev b/virtualization/Docker/Dockerfile.dev index 79072703031..3046fe51ba3 100644 --- a/virtualization/Docker/Dockerfile.dev +++ b/virtualization/Docker/Dockerfile.dev @@ -12,6 +12,7 @@ LABEL maintainer="Paulus Schoutsen " #ENV INSTALL_LIBCEC no #ENV INSTALL_COAP no #ENV INSTALL_SSOCR no +#ENV INSTALL_DLIB no #ENV INSTALL_IPERF3 no VOLUME /config diff --git a/virtualization/Docker/setup_docker_prereqs b/virtualization/Docker/setup_docker_prereqs index 65acf92b855..713bbfffba4 100755 --- a/virtualization/Docker/setup_docker_prereqs +++ b/virtualization/Docker/setup_docker_prereqs @@ -8,6 +8,7 @@ INSTALL_TELLSTICK="${INSTALL_TELLSTICK:-yes}" INSTALL_OPENALPR="${INSTALL_OPENALPR:-yes}" INSTALL_LIBCEC="${INSTALL_LIBCEC:-yes}" INSTALL_SSOCR="${INSTALL_SSOCR:-yes}" +INSTALL_DLIB="${INSTALL_DLIB:-yes}" # Required debian packages for running hass or components PACKAGES=( @@ -62,6 +63,10 @@ if [ "$INSTALL_SSOCR" == "yes" ]; then virtualization/Docker/scripts/ssocr fi +if [ "$INSTALL_DLIB" == "yes" ]; then + pip3 install --no-cache-dir "dlib>=19.5" +fi + # Remove packages apt-get remove -y --purge ${PACKAGES_DEV[@]} apt-get -y --purge autoremove From b7b62a90e209328e1c79fa3937de30cf23a48dea Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Thu, 25 Oct 2018 23:29:41 -0700 Subject: [PATCH 060/230] Delete sensor.yahoo_finance (#17805) --- .../components/sensor/yahoo_finance.py | 127 ------------------ requirements_all.txt | 3 - requirements_test_all.txt | 3 - script/gen_requirements_all.py | 1 - tests/components/sensor/test_yahoo_finance.py | 44 ------ 5 files changed, 178 deletions(-) delete mode 100644 homeassistant/components/sensor/yahoo_finance.py delete mode 100644 tests/components/sensor/test_yahoo_finance.py diff --git a/homeassistant/components/sensor/yahoo_finance.py b/homeassistant/components/sensor/yahoo_finance.py deleted file mode 100644 index 4358dba2b25..00000000000 --- a/homeassistant/components/sensor/yahoo_finance.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Currency exchange rate support that comes from Yahoo Finance. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/sensor.yahoo_finance/ -""" -import logging -from datetime import timedelta - -import voluptuous as vol - -import homeassistant.helpers.config_validation as cv -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION -from homeassistant.helpers.entity import Entity - -REQUIREMENTS = ['yahoo-finance==1.4.0'] - -_LOGGER = logging.getLogger(__name__) - -ATTR_CHANGE = 'Change' -ATTR_OPEN = 'open' -ATTR_PREV_CLOSE = 'prev_close' - -CONF_ATTRIBUTION = "Stock market information provided by Yahoo! Inc." -CONF_SYMBOLS = 'symbols' - -DEFAULT_NAME = 'Yahoo Stock' -DEFAULT_SYMBOL = 'YHOO' - -ICON = 'mdi:currency-usd' - -SCAN_INTERVAL = timedelta(minutes=5) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SYMBOLS, default=[DEFAULT_SYMBOL]): - vol.All(cv.ensure_list, [cv.string]), -}) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Yahoo Finance sensor.""" - from yahoo_finance import Share - - symbols = config.get(CONF_SYMBOLS) - - dev = [] - for symbol in symbols: - if Share(symbol).get_price() is None: - _LOGGER.warning("Symbol %s unknown", symbol) - break - data = YahooFinanceData(symbol) - dev.append(YahooFinanceSensor(data, symbol)) - - add_entities(dev, True) - - -class YahooFinanceSensor(Entity): - """Representation of a Yahoo Finance sensor.""" - - def __init__(self, data, symbol): - """Initialize the sensor.""" - self._name = symbol - self.data = data - self._symbol = symbol - self._state = None - self._unit_of_measurement = None - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._symbol - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def device_state_attributes(self): - """Return the state attributes.""" - if self._state is not None: - return { - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_CHANGE: self.data.price_change, - ATTR_OPEN: self.data.price_open, - ATTR_PREV_CLOSE: self.data.prev_close, - } - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return ICON - - def update(self): - """Get the latest data and updates the states.""" - _LOGGER.debug("Updating sensor %s - %s", self._name, self._state) - self.data.update() - self._state = self.data.state - - -class YahooFinanceData: - """Get data from Yahoo Finance.""" - - def __init__(self, symbol): - """Initialize the data object.""" - from yahoo_finance import Share - - self._symbol = symbol - self.state = None - self.price_change = None - self.price_open = None - self.prev_close = None - self.stock = Share(self._symbol) - - def update(self): - """Get the latest data and updates the states.""" - self.stock.refresh() - self.state = self.stock.get_price() - self.price_change = self.stock.get_change() - self.price_open = self.stock.get_open() - self.prev_close = self.stock.get_prev_close() diff --git a/requirements_all.txt b/requirements_all.txt index 1fe3ce3372f..b1443a7815a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1567,9 +1567,6 @@ xknx==0.8.5 # homeassistant.components.sensor.zestimate xmltodict==0.11.0 -# homeassistant.components.sensor.yahoo_finance -yahoo-finance==1.4.0 - # homeassistant.components.sensor.yweather # homeassistant.components.weather.yweather yahooweather==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e81e8664856..09e4c765fd3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -254,6 +254,3 @@ wakeonlan==1.1.6 # homeassistant.components.cloud warrant==0.6.1 - -# homeassistant.components.sensor.yahoo_finance -yahoo-finance==1.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 945bc60b4ea..97711b5e893 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -106,7 +106,6 @@ TEST_REQUIREMENTS = ( 'statsd', 'uvcclient', 'warrant', - 'yahoo-finance', 'pythonwhois', 'wakeonlan', 'vultr', diff --git a/tests/components/sensor/test_yahoo_finance.py b/tests/components/sensor/test_yahoo_finance.py deleted file mode 100644 index d442b9c9b22..00000000000 --- a/tests/components/sensor/test_yahoo_finance.py +++ /dev/null @@ -1,44 +0,0 @@ -"""The tests for the Yahoo Finance platform.""" -import json - -import unittest -from unittest.mock import patch - -import homeassistant.components.sensor as sensor -from homeassistant.setup import setup_component -from tests.common import ( - get_test_home_assistant, load_fixture, assert_setup_component) - -VALID_CONFIG = { - 'platform': 'yahoo_finance', - 'symbols': [ - 'YHOO', - ] -} - - -# pylint: disable=invalid-name -class TestYahooFinanceSetup(unittest.TestCase): - """Test the Yahoo Finance platform.""" - - def setUp(self): - """Initialize values for this testcase class.""" - self.hass = get_test_home_assistant() - self.config = VALID_CONFIG - - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - @patch('yahoo_finance.Base._request', - return_value=json.loads(load_fixture('yahoo_finance.json'))) - def test_default_setup(self, mock_request): - """Test the default setup.""" - with assert_setup_component(1, sensor.DOMAIN): - assert setup_component(self.hass, sensor.DOMAIN, { - 'sensor': VALID_CONFIG}) - - state = self.hass.states.get('sensor.yhoo') - assert '41.69' == state.attributes.get('open') - assert '41.79' == state.attributes.get('prev_close') - assert 'YHOO' == state.attributes.get('unit_of_measurement') From 47003fc04f3d3dcea2e81e38da59b382d45c9fc5 Mon Sep 17 00:00:00 2001 From: Leonardo Brondani Schenkel Date: Fri, 26 Oct 2018 09:15:26 +0200 Subject: [PATCH 061/230] deCONZ: configure service can now use 'field' as a subpath together with 'entity' (#17722) Allow both 'entity' and 'field' to be used simultaneously, where 'field' is used as a sub-path of the device path that is defined by 'entity'. --- homeassistant/components/deconz/__init__.py | 27 ++++----- homeassistant/components/deconz/services.yaml | 9 ++- tests/components/deconz/test_init.py | 55 ++++++++++++++++++- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 56b03c89a37..648aebc8c89 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -43,11 +43,11 @@ SERVICE_FIELD = 'field' SERVICE_ENTITY = 'entity' SERVICE_DATA = 'data' -SERVICE_SCHEMA = vol.Schema({ - vol.Exclusive(SERVICE_FIELD, 'deconz_id'): cv.string, - vol.Exclusive(SERVICE_ENTITY, 'deconz_id'): cv.entity_id, +SERVICE_SCHEMA = vol.All(vol.Schema({ + vol.Optional(SERVICE_ENTITY): cv.entity_id, + vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'), vol.Required(SERVICE_DATA): dict, -}) +}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD)) SERVICE_DEVICE_REFRESH = 'device_refresh' @@ -139,9 +139,10 @@ async def async_setup_entry(hass, config_entry): async def async_configure(call): """Set attribute of device in deCONZ. - Field is a string representing a specific device in deCONZ - e.g. field='/lights/1/state'. - Entity_id can be used to retrieve the proper field. + Entity is used to resolve to a device path (e.g. '/lights/1'). + Field is a string representing either a full path + (e.g. '/lights/1/state') when entity is not specified, or a + subpath (e.g. '/state') when used together with entity. Data is a json object with what data you want to alter e.g. data={'on': true}. { @@ -151,18 +152,14 @@ async def async_setup_entry(hass, config_entry): See Dresden Elektroniks REST API documentation for details: http://dresden-elektronik.github.io/deconz-rest-doc/rest/ """ - field = call.data.get(SERVICE_FIELD) + field = call.data.get(SERVICE_FIELD, '') entity_id = call.data.get(SERVICE_ENTITY) data = call.data.get(SERVICE_DATA) deconz = hass.data[DOMAIN] if entity_id: - - entities = hass.data.get(DATA_DECONZ_ID) - - if entities: - field = entities.get(entity_id) - - if field is None: + try: + field = hass.data[DATA_DECONZ_ID][entity_id] + field + except KeyError: _LOGGER.error('Could not find the entity %s', entity_id) return diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml index fa0fb8e14a4..cde7ac79f4c 100644 --- a/homeassistant/components/deconz/services.yaml +++ b/homeassistant/components/deconz/services.yaml @@ -1,12 +1,15 @@ configure: description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details. fields: - field: - description: Field is a string representing a specific device in deCONZ. - example: '/lights/1/state' entity: description: Entity id representing a specific device in deCONZ. example: 'light.rgb_light' + field: + description: >- + Field is a string representing a full path to deCONZ endpoint (when + entity is not specified) or a subpath of the device path for the + entity (when entity is specified). + example: '"/lights/1/state" or "/state"' data: description: Data is a json object with what data you want to alter. example: '{"on": true}' diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index cfda1232e93..8cc8c4bc242 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,10 +1,10 @@ """Test deCONZ component setup process.""" from unittest.mock import Mock, patch +from homeassistant.components import deconz +from homeassistant.components.deconz import DATA_DECONZ_ID from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from homeassistant.components import deconz - from tests.common import mock_coro CONFIG = { @@ -218,3 +218,54 @@ async def test_do_not_allow_clip_sensor(hass): async_dispatcher_send(hass, 'deconz_new_sensor', [remote]) await hass.async_block_till_done() assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0 + + +async def test_service_configure(hass): + """Test that service invokes pydeconz with the correct path and data.""" + entry = Mock() + entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} + with patch('pydeconz.DeconzSession.async_get_state', + return_value=mock_coro(CONFIG)), \ + patch('pydeconz.DeconzSession.start', return_value=True), \ + patch('homeassistant.helpers.device_registry.async_get_registry', + return_value=mock_coro(Mock())): + assert await deconz.async_setup_entry(hass, entry) is True + + hass.data[DATA_DECONZ_ID] = { + 'light.test': '/light/1' + } + data = {'on': True, 'attr1': 10, 'attr2': 20} + + # only field + with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + await hass.services.async_call('deconz', 'configure', service_data={ + 'field': '/light/42', 'data': data + }) + await hass.async_block_till_done() + async_put_state.assert_called_with('/light/42', data) + # only entity + with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + await hass.services.async_call('deconz', 'configure', service_data={ + 'entity': 'light.test', 'data': data + }) + await hass.async_block_till_done() + async_put_state.assert_called_with('/light/1', data) + # entity + field + with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + await hass.services.async_call('deconz', 'configure', service_data={ + 'entity': 'light.test', 'field': '/state', 'data': data}) + await hass.async_block_till_done() + async_put_state.assert_called_with('/light/1/state', data) + + # non-existing entity (or not from deCONZ) + with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + await hass.services.async_call('deconz', 'configure', service_data={ + 'entity': 'light.nonexisting', 'field': '/state', 'data': data}) + await hass.async_block_till_done() + async_put_state.assert_not_called() + # field does not start with / + with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + await hass.services.async_call('deconz', 'configure', service_data={ + 'entity': 'light.test', 'field': 'state', 'data': data}) + await hass.async_block_till_done() + async_put_state.assert_not_called() From 9e33398a7b112e749ecc4144310102be32bbe97a Mon Sep 17 00:00:00 2001 From: Andrey Kupreychik Date: Fri, 26 Oct 2018 15:06:53 +0700 Subject: [PATCH 062/230] Bumped NDMS2 client to 0.0.5 to fix unicode characters support (#17803) --- homeassistant/components/device_tracker/keenetic_ndms2.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/keenetic_ndms2.py b/homeassistant/components/device_tracker/keenetic_ndms2.py index 4be6d96eb5a..e9f9791b9f6 100644 --- a/homeassistant/components/device_tracker/keenetic_ndms2.py +++ b/homeassistant/components/device_tracker/keenetic_ndms2.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_PASSWORD, CONF_USERNAME ) -REQUIREMENTS = ['ndms2_client==0.0.4'] +REQUIREMENTS = ['ndms2_client==0.0.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index b1443a7815a..3e58af36c2e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -640,7 +640,7 @@ nad_receiver==0.0.9 nanoleaf==0.4.1 # homeassistant.components.device_tracker.keenetic_ndms2 -ndms2_client==0.0.4 +ndms2_client==0.0.5 # homeassistant.components.sensor.netdata netdata==0.1.2 From 9e286d7c1f876cdefd999813ae3218a9308c0fc9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Oct 2018 10:17:40 +0200 Subject: [PATCH 063/230] Bump frontend to 20181026.0 --- homeassistant/components/frontend/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index c155bcf81e3..0e92595ae78 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181024.0'] +REQUIREMENTS = ['home-assistant-frontend==20181026.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', From c00da509a1d0dc1619ca382bb32b48833986f56d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Oct 2018 10:18:10 +0200 Subject: [PATCH 064/230] Update translations --- .../components/cast/.translations/pt.json | 4 +-- .../components/deconz/.translations/pt.json | 2 +- .../components/hangouts/.translations/pt.json | 6 ++-- .../components/hue/.translations/pt.json | 2 +- .../components/ifttt/.translations/ko.json | 2 +- .../components/ifttt/.translations/pl.json | 6 ++-- .../components/ifttt/.translations/pt.json | 2 +- .../components/ios/.translations/pt.json | 4 +-- .../components/lifx/.translations/pt.json | 12 +++++-- .../components/mailgun/.translations/ca.json | 18 +++++++++++ .../components/mailgun/.translations/ko.json | 18 +++++++++++ .../components/mailgun/.translations/nl.json | 15 +++++++++ .../components/mailgun/.translations/pl.json | 18 +++++++++++ .../components/mailgun/.translations/pt.json | 18 +++++++++++ .../components/mailgun/.translations/ru.json | 18 +++++++++++ .../components/mailgun/.translations/sl.json | 18 +++++++++++ .../mailgun/.translations/zh-Hant.json | 18 +++++++++++ .../components/mqtt/.translations/pt.json | 6 ++-- .../components/nest/.translations/ko.json | 4 +-- .../components/nest/.translations/pt.json | 2 +- .../components/openuv/.translations/pt.json | 2 +- .../components/smhi/.translations/fr.json | 16 ++++++++++ .../components/smhi/.translations/pt.json | 6 ++++ .../components/tradfri/.translations/pt.json | 2 +- .../components/twilio/.translations/ca.json | 18 +++++++++++ .../components/twilio/.translations/en.json | 32 +++++++++---------- .../components/twilio/.translations/ko.json | 18 +++++++++++ .../components/twilio/.translations/nl.json | 14 ++++++++ .../components/twilio/.translations/no.json | 5 +++ .../components/twilio/.translations/pl.json | 18 +++++++++++ .../components/twilio/.translations/ru.json | 18 +++++++++++ .../components/twilio/.translations/sl.json | 18 +++++++++++ .../twilio/.translations/zh-Hant.json | 18 +++++++++++ .../components/unifi/.translations/fr.json | 22 +++++++++++++ .../components/unifi/.translations/nl.json | 5 ++- .../components/unifi/.translations/no.json | 13 ++++++++ .../components/unifi/.translations/pl.json | 2 +- .../components/upnp/.translations/pt.json | 6 ++-- .../components/zwave/.translations/fr.json | 21 ++++++++++++ .../components/zwave/.translations/ko.json | 2 +- 40 files changed, 403 insertions(+), 46 deletions(-) create mode 100644 homeassistant/components/mailgun/.translations/ca.json create mode 100644 homeassistant/components/mailgun/.translations/ko.json create mode 100644 homeassistant/components/mailgun/.translations/nl.json create mode 100644 homeassistant/components/mailgun/.translations/pl.json create mode 100644 homeassistant/components/mailgun/.translations/pt.json create mode 100644 homeassistant/components/mailgun/.translations/ru.json create mode 100644 homeassistant/components/mailgun/.translations/sl.json create mode 100644 homeassistant/components/mailgun/.translations/zh-Hant.json create mode 100644 homeassistant/components/smhi/.translations/fr.json create mode 100644 homeassistant/components/twilio/.translations/ca.json create mode 100644 homeassistant/components/twilio/.translations/ko.json create mode 100644 homeassistant/components/twilio/.translations/nl.json create mode 100644 homeassistant/components/twilio/.translations/no.json create mode 100644 homeassistant/components/twilio/.translations/pl.json create mode 100644 homeassistant/components/twilio/.translations/ru.json create mode 100644 homeassistant/components/twilio/.translations/sl.json create mode 100644 homeassistant/components/twilio/.translations/zh-Hant.json create mode 100644 homeassistant/components/unifi/.translations/fr.json create mode 100644 homeassistant/components/unifi/.translations/no.json create mode 100644 homeassistant/components/zwave/.translations/fr.json diff --git a/homeassistant/components/cast/.translations/pt.json b/homeassistant/components/cast/.translations/pt.json index a6d28538396..85d1b14484d 100644 --- a/homeassistant/components/cast/.translations/pt.json +++ b/homeassistant/components/cast/.translations/pt.json @@ -7,9 +7,9 @@ "step": { "confirm": { "description": "Deseja configurar o Google Cast?", - "title": "" + "title": "Google Cast" } }, - "title": "" + "title": "Google Cast" } } \ No newline at end of file diff --git a/homeassistant/components/deconz/.translations/pt.json b/homeassistant/components/deconz/.translations/pt.json index 1f7b8209089..eef2d5ce946 100644 --- a/homeassistant/components/deconz/.translations/pt.json +++ b/homeassistant/components/deconz/.translations/pt.json @@ -28,6 +28,6 @@ "title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ" } }, - "title": "deCONZ" + "title": "Gateway Zigbee deCONZ" } } \ No newline at end of file diff --git a/homeassistant/components/hangouts/.translations/pt.json b/homeassistant/components/hangouts/.translations/pt.json index 64c960a121a..a16c60128c1 100644 --- a/homeassistant/components/hangouts/.translations/pt.json +++ b/homeassistant/components/hangouts/.translations/pt.json @@ -5,7 +5,7 @@ "unknown": "Ocorreu um erro desconhecido." }, "error": { - "invalid_2fa": "Autoriza\u00e7\u00e3o por 2 factores inv\u00e1lida, por favor, tente novamente.", + "invalid_2fa": "Autentica\u00e7\u00e3o por 2 fatores inv\u00e1lida, por favor, tente novamente.", "invalid_2fa_method": "M\u00e9todo 2FA inv\u00e1lido (verificar no telefone).", "invalid_login": "Login inv\u00e1lido, por favor, tente novamente." }, @@ -15,7 +15,7 @@ "2fa": "Pin 2FA" }, "description": "Vazio", - "title": "" + "title": "Autentica\u00e7\u00e3o de 2 Fatores" }, "user": { "data": { @@ -26,6 +26,6 @@ "title": "Login Google Hangouts" } }, - "title": "" + "title": "Google Hangouts" } } \ No newline at end of file diff --git a/homeassistant/components/hue/.translations/pt.json b/homeassistant/components/hue/.translations/pt.json index f7988d82d8c..d52540b0921 100644 --- a/homeassistant/components/hue/.translations/pt.json +++ b/homeassistant/components/hue/.translations/pt.json @@ -24,6 +24,6 @@ "title": "Link Hub" } }, - "title": "" + "title": "Philips Hue" } } \ No newline at end of file diff --git a/homeassistant/components/ifttt/.translations/ko.json b/homeassistant/components/ifttt/.translations/ko.json index 57ad8037753..2f033e4f4ee 100644 --- a/homeassistant/components/ifttt/.translations/ko.json +++ b/homeassistant/components/ifttt/.translations/ko.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n \ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url})\ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n \ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." }, "step": { "user": { diff --git a/homeassistant/components/ifttt/.translations/pl.json b/homeassistant/components/ifttt/.translations/pl.json index 3c3c2182503..7064364ebe6 100644 --- a/homeassistant/components/ifttt/.translations/pl.json +++ b/homeassistant/components/ifttt/.translations/pl.json @@ -5,12 +5,12 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wys\u0142a\u0107 zdarzenia do Home Assistant'a, b\u0119dziesz musia\u0142 u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Podaj nast\u0119puj\u0105ce informacje:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wys\u0142a\u0107 zdarzenia do Home Assistant'a, b\u0119dziesz musia\u0142 u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { - "description": "Jeste\u015b pewny, \u017ce chcesz skonfigurowa\u0107 IFTTT?", - "title": "Konfigurowanie apletu Webhook IFTTT" + "description": "Czy chcesz skonfigurowa\u0107 IFTTT?", + "title": "Konfiguracja apletu Webhook IFTTT" } }, "title": "IFTTT" diff --git a/homeassistant/components/ifttt/.translations/pt.json b/homeassistant/components/ifttt/.translations/pt.json index 34c6496d7b1..08b7aee6a08 100644 --- a/homeassistant/components/ifttt/.translations/pt.json +++ b/homeassistant/components/ifttt/.translations/pt.json @@ -13,6 +13,6 @@ "title": "Configurar o IFTTT Webhook Applet" } }, - "title": "" + "title": "IFTTT" } } \ No newline at end of file diff --git a/homeassistant/components/ios/.translations/pt.json b/homeassistant/components/ios/.translations/pt.json index 6752606d9f5..d38b9abb70b 100644 --- a/homeassistant/components/ios/.translations/pt.json +++ b/homeassistant/components/ios/.translations/pt.json @@ -6,9 +6,9 @@ "step": { "confirm": { "description": "Deseja configurar o componente iOS do Home Assistant?", - "title": "" + "title": "Home Assistant iOS" } }, - "title": "" + "title": "Home Assistant iOS" } } \ No newline at end of file diff --git a/homeassistant/components/lifx/.translations/pt.json b/homeassistant/components/lifx/.translations/pt.json index 5d7fdf356ef..d5c93c33993 100644 --- a/homeassistant/components/lifx/.translations/pt.json +++ b/homeassistant/components/lifx/.translations/pt.json @@ -1,7 +1,15 @@ { "config": { "abort": { - "no_devices_found": "Nenhum dispositivo LIFX encontrado na rede." - } + "no_devices_found": "Nenhum dispositivo LIFX encontrado na rede.", + "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do LIFX \u00e9 permitida." + }, + "step": { + "confirm": { + "description": "Deseja configurar o LIFX?", + "title": "LIFX" + } + }, + "title": "LIFX" } } \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ca.json b/homeassistant/components/mailgun/.translations/ca.json new file mode 100644 index 00000000000..d644b9b8c73 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "La vostra inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Mailgun.", + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + }, + "create_entry": { + "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Mailgun] ({mailgun_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." + }, + "step": { + "user": { + "description": "Esteu segur que voleu configurar Mailgun?", + "title": "Configureu el Webhook de Mailgun" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ko.json b/homeassistant/components/mailgun/.translations/ko.json new file mode 100644 index 00000000000..0dd8cbdb47d --- /dev/null +++ b/homeassistant/components/mailgun/.translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Mailgun \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c\ud569\ub2c8\ub2e4.", + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({mailgun_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + }, + "step": { + "user": { + "description": "Mailgun \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Mailgun Webhook \uc124\uc815" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/nl.json b/homeassistant/components/mailgun/.translations/nl.json new file mode 100644 index 00000000000..d71c311b7f8 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/nl.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Uw Home Assistant instantie moet toegankelijk zijn vanaf het internet om Mailgun-berichten te ontvangen.", + "one_instance_allowed": "Slechts \u00e9\u00e9n enkele instantie is nodig." + }, + "step": { + "user": { + "description": "Weet u zeker dat u Mailgun wilt instellen?", + "title": "Stel de Mailgun Webhook in" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/pl.json b/homeassistant/components/mailgun/.translations/pl.json new file mode 100644 index 00000000000..ba89efab0c2 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty Mailgun.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Mailgun?", + "title": "Konfiguracja Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/pt.json b/homeassistant/components/mailgun/.translations/pt.json new file mode 100644 index 00000000000..963d3322d84 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/pt.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "A sua inst\u00e2ncia Home Assistent precisa de ser acess\u00edvel a partir da internet para receber mensagens Mailgun.", + "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." + }, + "create_entry": { + "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks with Mailgun] ({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." + }, + "step": { + "user": { + "description": "Tem certeza de que deseja configurar o Mailgun?", + "title": "Configurar o Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ru.json b/homeassistant/components/mailgun/.translations/ru.json new file mode 100644 index 00000000000..62007a95809 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/ru.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Mailgun.", + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "create_entry": { + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c [Webhooks \u0434\u043b\u044f Mailgun]({mailgun_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Mailgun?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/sl.json b/homeassistant/components/mailgun/.translations/sl.json new file mode 100644 index 00000000000..12dad4d8c7e --- /dev/null +++ b/homeassistant/components/mailgun/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila Mailgun, mora biti Home Assistent dostopen prek interneta.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "Za po\u0161iljanje dogodkov Home Assistentu boste morali nastaviti [Webhooks z Mailgun]({mailgun_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite automations za obravnavo dohodnih podatkov." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti Mailgun?", + "title": "Nastavite Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/zh-Hant.json b/homeassistant/components/mailgun/.translations/zh-Hant.json new file mode 100644 index 00000000000..4b9ab3a7abb --- /dev/null +++ b/homeassistant/components/mailgun/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Mailgun \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Mailgun]({mailgun_url}) \u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Mailgun\uff1f", + "title": "\u8a2d\u5b9a Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/pt.json b/homeassistant/components/mqtt/.translations/pt.json index 3b36345994d..21b9cbdf755 100644 --- a/homeassistant/components/mqtt/.translations/pt.json +++ b/homeassistant/components/mqtt/.translations/pt.json @@ -9,14 +9,14 @@ "step": { "broker": { "data": { - "broker": "", + "broker": "Broker", "discovery": "Ativar descoberta", "password": "Palavra-passe", "port": "Porto", "username": "Utilizador" }, "description": "Por favor, insira os detalhes de liga\u00e7\u00e3o ao seu broker MQTT.", - "title": "" + "title": "MQTT" }, "hassio_confirm": { "data": { @@ -26,6 +26,6 @@ "title": "MQTT Broker atrav\u00e9s do add-on Hass.io" } }, - "title": "" + "title": "MQTT" } } \ No newline at end of file diff --git a/homeassistant/components/nest/.translations/ko.json b/homeassistant/components/nest/.translations/ko.json index 0caa70aeff2..a53a26bca5a 100644 --- a/homeassistant/components/nest/.translations/ko.json +++ b/homeassistant/components/nest/.translations/ko.json @@ -4,7 +4,7 @@ "already_setup": "\ud558\ub098\uc758 Nest \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "no_flows": "Nest \ub97c \uc778\uc99d\ud558\uae30 \uc804\uc5d0 Nest \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/nest/)\ub97c \uc77d\uc5b4\ubcf4\uc138\uc694." + "no_flows": "Nest \ub97c \uc778\uc99d\ud558\uae30 \uc804\uc5d0 Nest \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/nest/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694." }, "error": { "internal_error": "\ucf54\ub4dc \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \ub0b4\ubd80 \uc624\ub958 \ubc1c\uc0dd", @@ -24,7 +24,7 @@ "data": { "code": "\ud540 \ucf54\ub4dc" }, - "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74, [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url})\uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4, \uc544\ub798\uc758 \ud540 \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", + "description": "Nest \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74, [\uacc4\uc815 \uc5f0\uacb0 \uc2b9\uc778]({url}) \uc744 \ud574\uc8fc\uc138\uc694.\n\n\uc2b9\uc778 \ud6c4, \uc544\ub798\uc758 \ud540 \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", "title": "Nest \uacc4\uc815 \uc5f0\uacb0" } }, diff --git a/homeassistant/components/nest/.translations/pt.json b/homeassistant/components/nest/.translations/pt.json index 40743fe3ddb..5ea970d9fb3 100644 --- a/homeassistant/components/nest/.translations/pt.json +++ b/homeassistant/components/nest/.translations/pt.json @@ -28,6 +28,6 @@ "title": "Associar conta Nest" } }, - "title": "" + "title": "Nest" } } \ No newline at end of file diff --git a/homeassistant/components/openuv/.translations/pt.json b/homeassistant/components/openuv/.translations/pt.json index 36f875efc00..48283a74106 100644 --- a/homeassistant/components/openuv/.translations/pt.json +++ b/homeassistant/components/openuv/.translations/pt.json @@ -15,6 +15,6 @@ "title": "Preencha com as suas informa\u00e7\u00f5es" } }, - "title": "" + "title": "OpenUV" } } \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/fr.json b/homeassistant/components/smhi/.translations/fr.json new file mode 100644 index 00000000000..d1378f183d5 --- /dev/null +++ b/homeassistant/components/smhi/.translations/fr.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "name_exists": "Ce nom est d\u00e9j\u00e0 utilis\u00e9" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/pt.json b/homeassistant/components/smhi/.translations/pt.json index a5c71885906..e814ffd5046 100644 --- a/homeassistant/components/smhi/.translations/pt.json +++ b/homeassistant/components/smhi/.translations/pt.json @@ -1,8 +1,14 @@ { "config": { + "error": { + "name_exists": "Nome j\u00e1 existe", + "wrong_location": "Localiza\u00e7\u00e3o apenas na Su\u00e9cia" + }, "step": { "user": { "data": { + "latitude": "Latitude", + "longitude": "Longitude", "name": "Nome" }, "title": "Localiza\u00e7\u00e3o na Su\u00e9cia" diff --git a/homeassistant/components/tradfri/.translations/pt.json b/homeassistant/components/tradfri/.translations/pt.json index 05d3cbb57fe..e89cb6ac620 100644 --- a/homeassistant/components/tradfri/.translations/pt.json +++ b/homeassistant/components/tradfri/.translations/pt.json @@ -18,6 +18,6 @@ "title": "Introduzir c\u00f3digo de seguran\u00e7a" } }, - "title": "" + "title": "IKEA TR\u00c5DFRI" } } \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/ca.json b/homeassistant/components/twilio/.translations/ca.json new file mode 100644 index 00000000000..3179f420ede --- /dev/null +++ b/homeassistant/components/twilio/.translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "La vostra inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Twilio.", + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + }, + "create_entry": { + "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Twilio] ({twilio_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." + }, + "step": { + "user": { + "description": "Esteu segur que voleu configurar Twilio?", + "title": "Configureu el Webhook de Twilio" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/en.json b/homeassistant/components/twilio/.translations/en.json index ca75fff0737..3ee0421469c 100644 --- a/homeassistant/components/twilio/.translations/en.json +++ b/homeassistant/components/twilio/.translations/en.json @@ -1,18 +1,18 @@ { - "config": { - "title": "Twilio", - "step": { - "user": { - "title": "Set up the Twilio Webhook", - "description": "Are you sure you want to set up Twilio?" - } - }, - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages." - }, - "create_entry": { - "default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + "config": { + "abort": { + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages.", + "one_instance_allowed": "Only a single instance is necessary." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + }, + "step": { + "user": { + "description": "Are you sure you want to set up Twilio?", + "title": "Set up the Twilio Webhook" + } + }, + "title": "Twilio" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/ko.json b/homeassistant/components/twilio/.translations/ko.json new file mode 100644 index 00000000000..028919bff90 --- /dev/null +++ b/homeassistant/components/twilio/.translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Twilio \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c\ud569\ub2c8\ub2e4.", + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio Webhook]({twilio_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/x-www-form-urlencoded\n \n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + }, + "step": { + "user": { + "description": "Twilio \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Twilio Webhook \uc124\uc815" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/nl.json b/homeassistant/components/twilio/.translations/nl.json new file mode 100644 index 00000000000..a053bf372a5 --- /dev/null +++ b/homeassistant/components/twilio/.translations/nl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Slechts \u00e9\u00e9n exemplaar is nodig." + }, + "step": { + "user": { + "description": "Weet u zeker dat u Twilio wilt instellen?", + "title": "Stel de Twilio Webhook in" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/no.json b/homeassistant/components/twilio/.translations/no.json new file mode 100644 index 00000000000..86e5d9051b3 --- /dev/null +++ b/homeassistant/components/twilio/.translations/no.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/pl.json b/homeassistant/components/twilio/.translations/pl.json new file mode 100644 index 00000000000..19c835c4b8c --- /dev/null +++ b/homeassistant/components/twilio/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty Twilio.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Twilio Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Twilio?", + "title": "Konfiguracja Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/ru.json b/homeassistant/components/twilio/.translations/ru.json new file mode 100644 index 00000000000..e758a47064e --- /dev/null +++ b/homeassistant/components/twilio/.translations/ru.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Twilio.", + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "create_entry": { + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c [Webhooks \u0434\u043b\u044f Twilio]({twilio_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Twilio?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/sl.json b/homeassistant/components/twilio/.translations/sl.json new file mode 100644 index 00000000000..0321cb05452 --- /dev/null +++ b/homeassistant/components/twilio/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila Twilio, mora biti Home Assistent dostopen prek interneta.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [Webhooks z Twilio]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) o tem, kako nastavite avtomatizacijo za obravnavo dohodnih podatkov." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti Twilio?", + "title": "Nastavite Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/zh-Hant.json b/homeassistant/components/twilio/.translations/zh-Hant.json new file mode 100644 index 00000000000..2e85ef7b2de --- /dev/null +++ b/homeassistant/components/twilio/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Twilio \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Twilio]({twilio_url})\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Twilio\uff1f", + "title": "\u8a2d\u5b9a Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/fr.json b/homeassistant/components/unifi/.translations/fr.json new file mode 100644 index 00000000000..68e90811a3e --- /dev/null +++ b/homeassistant/components/unifi/.translations/fr.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "user_privilege": "L'utilisateur doit \u00eatre administrateur" + }, + "error": { + "faulty_credentials": "Mauvaises informations d'identification de l'utilisateur", + "service_unavailable": "Aucun service disponible" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "port": "Port", + "site": "ID du site", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/nl.json b/homeassistant/components/unifi/.translations/nl.json index 8e87dc4b2a6..7a1eea546a2 100644 --- a/homeassistant/components/unifi/.translations/nl.json +++ b/homeassistant/components/unifi/.translations/nl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured": "Controller site is al geconfigureerd", "user_privilege": "Gebruiker moet beheerder zijn" }, "error": { @@ -13,7 +14,9 @@ "host": "Host", "password": "Wachtwoord", "port": "Poort", - "username": "Gebruikersnaam" + "site": "Site ID", + "username": "Gebruikersnaam", + "verify_ssl": "Controller gebruik van het juiste certificaat" }, "title": "Stel de UniFi-controller in" } diff --git a/homeassistant/components/unifi/.translations/no.json b/homeassistant/components/unifi/.translations/no.json new file mode 100644 index 00000000000..7e9251dc026 --- /dev/null +++ b/homeassistant/components/unifi/.translations/no.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passord", + "port": "Port", + "username": "Brukernavn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/pl.json b/homeassistant/components/unifi/.translations/pl.json index f2f8082ac76..5382adcbf7d 100644 --- a/homeassistant/components/unifi/.translations/pl.json +++ b/homeassistant/components/unifi/.translations/pl.json @@ -18,7 +18,7 @@ "username": "Nazwa u\u017cytkownika", "verify_ssl": "Kontroler u\u017cywa prawid\u0142owego certyfikatu" }, - "title": "Skonfiguruj kontroler UniFi" + "title": "Konfiguracja kontrolera UniFi" } }, "title": "Kontroler UniFi" diff --git a/homeassistant/components/upnp/.translations/pt.json b/homeassistant/components/upnp/.translations/pt.json index 5e9b516d1c2..899a5def479 100644 --- a/homeassistant/components/upnp/.translations/pt.json +++ b/homeassistant/components/upnp/.translations/pt.json @@ -11,17 +11,17 @@ }, "step": { "init": { - "title": "" + "title": "UPnP/IGD" }, "user": { "data": { "enable_port_mapping": "Ativar o mapeamento de porta para o Home Assistant", "enable_sensors": "Adicionar sensores de tr\u00e1fego", - "igd": "" + "igd": "UPnP/IGD" }, "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o para o UPnP/IGD" } }, - "title": "" + "title": "UPnP/IGD" } } \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/fr.json b/homeassistant/components/zwave/.translations/fr.json new file mode 100644 index 00000000000..c667965bebc --- /dev/null +++ b/homeassistant/components/zwave/.translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Z-Wave est d\u00e9j\u00e0 configur\u00e9", + "one_instance_only": "Le composant ne prend en charge qu'une seule instance Z-Wave" + }, + "error": { + "option_error": "La validation Z-Wave a \u00e9chou\u00e9. Le chemin d'acc\u00e8s \u00e0 la cl\u00e9 USB est-il correct?" + }, + "step": { + "user": { + "data": { + "network_key": "Cl\u00e9 r\u00e9seau (laisser vide pour g\u00e9n\u00e9rer automatiquement)", + "usb_path": "Chemin USB" + }, + "title": "Configurer Z-Wave" + } + }, + "title": "Z-Wave" + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/ko.json b/homeassistant/components/zwave/.translations/ko.json index d57f758ce25..e288019de0c 100644 --- a/homeassistant/components/zwave/.translations/ko.json +++ b/homeassistant/components/zwave/.translations/ko.json @@ -13,7 +13,7 @@ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4 (\uacf5\ub780\uc73c\ub85c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uc0dd\uc131\ud569\ub2c8\ub2e4)", "usb_path": "USB \uacbd\ub85c" }, - "description": "\uad6c\uc131 \ubcc0\uc218\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/docs/z-wave/installation/)\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", + "description": "\uad6c\uc131 \ubcc0\uc218\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/docs/z-wave/installation/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", "title": "Z-Wave \uc124\uc815" } }, From 714d44c50344f615427c5d0bb069fc54b42df1cd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 26 Oct 2018 10:35:21 +0200 Subject: [PATCH 065/230] Upgrade numpy to 1.15.3 (#17796) --- homeassistant/components/binary_sensor/trend.py | 8 +++----- homeassistant/components/image_processing/opencv.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index d332c668703..08838be3ea6 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -15,14 +15,14 @@ from homeassistant.components.binary_sensor import ( BinarySensorDevice) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, CONF_DEVICE_CLASS, CONF_ENTITY_ID, - CONF_FRIENDLY_NAME, STATE_UNKNOWN) + CONF_FRIENDLY_NAME, STATE_UNKNOWN, CONF_SENSORS) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.util import utcnow -REQUIREMENTS = ['numpy==1.15.2'] +REQUIREMENTS = ['numpy==1.15.3'] _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,6 @@ CONF_INVERT = 'invert' CONF_MAX_SAMPLES = 'max_samples' CONF_MIN_GRADIENT = 'min_gradient' CONF_SAMPLE_DURATION = 'sample_duration' -CONF_SENSORS = 'sensors' SENSOR_SCHEMA = vol.Schema({ vol.Required(CONF_ENTITY_ID): cv.entity_id, @@ -78,9 +77,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) if not sensors: _LOGGER.error("No sensors added") - return False + return add_entities(sensors) - return True class SensorTrend(BinarySensorDevice): diff --git a/homeassistant/components/image_processing/opencv.py b/homeassistant/components/image_processing/opencv.py index 062c18bb730..e44ae6e1ae3 100644 --- a/homeassistant/components/image_processing/opencv.py +++ b/homeassistant/components/image_processing/opencv.py @@ -16,7 +16,7 @@ from homeassistant.components.image_processing import ( from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['numpy==1.15.2'] +REQUIREMENTS = ['numpy==1.15.3'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 3e58af36c2e..39de689dfcb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -662,7 +662,7 @@ nuheat==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.15.2 +numpy==1.15.3 # homeassistant.components.google oauth2client==4.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 09e4c765fd3..718d2475672 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -118,7 +118,7 @@ mficlient==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.15.2 +numpy==1.15.3 # homeassistant.components.mqtt # homeassistant.components.shiftr From 3f4798b5c376a48fe2f4cf551420a0c38db60e2d Mon Sep 17 00:00:00 2001 From: Jeroen ter Heerdt Date: Fri, 26 Oct 2018 11:07:39 +0200 Subject: [PATCH 066/230] MQTT Vacuum now passes error messages. (#17685) * MQTT Vacuum now passes error messages. * Fixing pylint error * Use string formatting --- homeassistant/components/vacuum/mqtt.py | 43 ++++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/vacuum/mqtt.py b/homeassistant/components/vacuum/mqtt.py index fcb77e10732..a017745a715 100644 --- a/homeassistant/components/vacuum/mqtt.py +++ b/homeassistant/components/vacuum/mqtt.py @@ -80,6 +80,8 @@ CONF_CLEANING_TOPIC = 'cleaning_topic' CONF_CLEANING_TEMPLATE = 'cleaning_template' CONF_DOCKED_TOPIC = 'docked_topic' CONF_DOCKED_TEMPLATE = 'docked_template' +CONF_ERROR_TOPIC = 'error_topic' +CONF_ERROR_TEMPLATE = 'error_template' CONF_STATE_TOPIC = 'state_topic' CONF_STATE_TEMPLATE = 'state_template' CONF_FAN_SPEED_TOPIC = 'fan_speed_topic' @@ -127,6 +129,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_CLEANING_TEMPLATE): cv.template, vol.Optional(CONF_DOCKED_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_DOCKED_TEMPLATE): cv.template, + vol.Optional(CONF_ERROR_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_ERROR_TEMPLATE): cv.template, vol.Optional(CONF_STATE_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_STATE_TEMPLATE): cv.template, vol.Optional(CONF_FAN_SPEED_TOPIC): mqtt.valid_publish_topic, @@ -177,6 +181,11 @@ async def async_setup_platform(hass, config, async_add_entities, if docked_template: docked_template.hass = hass + error_topic = config.get(CONF_ERROR_TOPIC) + error_template = config.get(CONF_ERROR_TEMPLATE) + if error_template: + error_template.hass = hass + fan_speed_topic = config.get(CONF_FAN_SPEED_TOPIC) fan_speed_template = config.get(CONF_FAN_SPEED_TEMPLATE) if fan_speed_template: @@ -198,7 +207,8 @@ async def async_setup_platform(hass, config, async_add_entities, payload_stop, payload_clean_spot, payload_locate, payload_start_pause, battery_level_topic, battery_level_template, charging_topic, charging_template, cleaning_topic, - cleaning_template, docked_topic, docked_template, fan_speed_topic, + cleaning_template, docked_topic, docked_template, + error_topic, error_template, fan_speed_topic, fan_speed_template, set_fan_speed_topic, fan_speed_list, send_command_topic, availability_topic, payload_available, payload_not_available @@ -215,7 +225,8 @@ class MqttVacuum(MqttAvailability, VacuumDevice): payload_stop, payload_clean_spot, payload_locate, payload_start_pause, battery_level_topic, battery_level_template, charging_topic, charging_template, cleaning_topic, - cleaning_template, docked_topic, docked_template, fan_speed_topic, + cleaning_template, docked_topic, docked_template, + error_topic, error_template, fan_speed_topic, fan_speed_template, set_fan_speed_topic, fan_speed_list, send_command_topic, availability_topic, payload_available, payload_not_available): @@ -249,6 +260,9 @@ class MqttVacuum(MqttAvailability, VacuumDevice): self._docked_topic = docked_topic self._docked_template = docked_template + self._error_topic = error_topic + self._error_template = error_template + self._fan_speed_topic = fan_speed_topic self._fan_speed_template = fan_speed_template @@ -259,6 +273,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice): self._cleaning = False self._charging = False self._docked = False + self._error = None self._status = 'Unknown' self._battery_level = 0 self._fan_speed = 'unknown' @@ -274,35 +289,38 @@ class MqttVacuum(MqttAvailability, VacuumDevice): self._battery_level_template: battery_level = self._battery_level_template\ .async_render_with_possible_json_value( - payload, - error_value=None) + payload, error_value=None) if battery_level is not None: self._battery_level = int(battery_level) if topic == self._charging_topic and self._charging_template: charging = self._charging_template\ .async_render_with_possible_json_value( - payload, - error_value=None) + payload, error_value=None) if charging is not None: self._charging = cv.boolean(charging) if topic == self._cleaning_topic and self._cleaning_template: cleaning = self._cleaning_template \ .async_render_with_possible_json_value( - payload, - error_value=None) + payload, error_value=None) if cleaning is not None: self._cleaning = cv.boolean(cleaning) if topic == self._docked_topic and self._docked_template: docked = self._docked_template \ .async_render_with_possible_json_value( - payload, - error_value=None) + payload, error_value=None) if docked is not None: self._docked = cv.boolean(docked) + if topic == self._error_topic and self._error_template: + error = self._error_template \ + .async_render_with_possible_json_value( + payload, error_value=None) + if error is not None: + self._error = cv.string(error) + if self._docked: if self._charging: self._status = "Docked & Charging" @@ -310,14 +328,15 @@ class MqttVacuum(MqttAvailability, VacuumDevice): self._status = "Docked" elif self._cleaning: self._status = "Cleaning" + elif self._error is not None and not self._error: + self._status = "Error: {}".format(self._error) else: self._status = "Stopped" if topic == self._fan_speed_topic and self._fan_speed_template: fan_speed = self._fan_speed_template\ .async_render_with_possible_json_value( - payload, - error_value=None) + payload, error_value=None) if fan_speed is not None: self._fan_speed = fan_speed From e276e899cf42ea624c325a3750795bfbc4daf03f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Oct 2018 11:31:14 +0200 Subject: [PATCH 067/230] Convert automation tests to async (#17794) * Convert automation tests to async * Fix 8 last tests * Lint --- tests/components/automation/common.py | 29 +- tests/components/automation/test_event.py | 277 ++- .../automation/test_geo_location.py | 457 +++-- tests/components/automation/test_init.py | 940 ++++----- tests/components/automation/test_mqtt.py | 154 +- .../automation/test_numeric_state.py | 1736 +++++++++-------- tests/components/automation/test_state.py | 1087 ++++++----- tests/components/automation/test_sun.py | 543 +++--- tests/components/automation/test_template.py | 811 ++++---- tests/components/automation/test_time.py | 684 +++---- tests/components/automation/test_zone.py | 340 ++-- 11 files changed, 3544 insertions(+), 3514 deletions(-) diff --git a/tests/components/automation/common.py b/tests/components/automation/common.py index 4c8f91849aa..2a5024c0c30 100644 --- a/tests/components/automation/common.py +++ b/tests/components/automation/common.py @@ -11,43 +11,34 @@ from homeassistant.loader import bind_hass @bind_hass -def turn_on(hass, entity_id=None): +async def async_turn_on(hass, entity_id=None): """Turn on specified automation or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TURN_ON, data) + await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data) @bind_hass -def turn_off(hass, entity_id=None): +async def async_turn_off(hass, entity_id=None): """Turn off specified automation or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) + await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data) @bind_hass -def toggle(hass, entity_id=None): +async def async_toggle(hass, entity_id=None): """Toggle specified automation or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TOGGLE, data) + await hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data) @bind_hass -def trigger(hass, entity_id=None): +async def async_trigger(hass, entity_id=None): """Trigger specified automation or all.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TRIGGER, data) + await hass.services.async_call(DOMAIN, SERVICE_TRIGGER, data) @bind_hass -def reload(hass): +async def async_reload(hass): """Reload the automation from config.""" - hass.services.call(DOMAIN, SERVICE_RELOAD) - - -@bind_hass -def async_reload(hass): - """Reload the automation from config. - - Returns a coroutine object. - """ - return hass.services.async_call(DOMAIN, SERVICE_RELOAD) + await hass.services.async_call(DOMAIN, SERVICE_RELOAD) diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index b925e5a809d..4b669fc1356 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,175 +1,172 @@ """The tests for the Event automation.""" -import unittest +import pytest -from homeassistant.core import Context, callback -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation -from tests.common import get_test_home_assistant, mock_component +from tests.common import mock_component from tests.components.automation import common +from tests.common import async_mock_service -# pylint: disable=invalid-name -class TestAutomationEvent(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - self.calls = [] - @callback - def record_call(service): - """Record the call.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() +async def test_if_fires_on_event(hass, calls): + """Test the firing of events.""" + context = Context() - def test_if_fires_on_event(self): - """Test the firing of events.""" - context = Context() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - } + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event', context=context) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context + hass.bus.async_fire('test_event', context=context) + await hass.async_block_till_done() + assert 1 == len(calls) + assert calls[0].context is context - common.turn_off(self.hass) - self.hass.block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_event_extra_data(self): - """Test the firing of events still matches with event data.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - } + +async def test_if_fires_on_event_extra_data(hass, calls): + """Test the firing of events still matches with event data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event', {'extra_key': 'extra_data'}) - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event', {'extra_key': 'extra_data'}) + await hass.async_block_till_done() + assert 1 == len(calls) - common.turn_off(self.hass) - self.hass.block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_event_with_data(self): - """Test the firing of events with data.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - 'event_data': {'some_attr': 'some_value'} - }, - 'action': { - 'service': 'test.automation', - } + +async def test_if_fires_on_event_with_data(hass, calls): + """Test the firing of events with data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'} + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event', {'some_attr': 'some_value', - 'another': 'value'}) - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event', {'some_attr': 'some_value', + 'another': 'value'}) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_event_with_empty_data_config(self): - """Test the firing of events with empty data config. - The frontend automation editor can produce configurations with an - empty dict for event_data instead of no key. - """ - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - 'event_data': {} - }, - 'action': { - 'service': 'test.automation', - } +async def test_if_fires_on_event_with_empty_data_config(hass, calls): + """Test the firing of events with empty data config. + + The frontend automation editor can produce configurations with an + empty dict for event_data instead of no key. + """ + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {} + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event', {'some_attr': 'some_value', - 'another': 'value'}) - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event', {'some_attr': 'some_value', + 'another': 'value'}) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_event_with_nested_data(self): - """Test the firing of events with nested data.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - 'event_data': { - 'parent_attr': { - 'some_attr': 'some_value' - } + +async def test_if_fires_on_event_with_nested_data(hass, calls): + """Test the firing of events with nested data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': { + 'parent_attr': { + 'some_attr': 'some_value' } - }, - 'action': { - 'service': 'test.automation', } + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event', { - 'parent_attr': { - 'some_attr': 'some_value', - 'another': 'value' + hass.bus.async_fire('test_event', { + 'parent_attr': { + 'some_attr': 'some_value', + 'another': 'value' + } + }) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_if_event_data_not_matches(hass, calls): + """Test firing of event if no match.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'} + }, + 'action': { + 'service': 'test.automation', } - }) - self.hass.block_till_done() - assert 1 == len(self.calls) + } + }) - def test_if_not_fires_if_event_data_not_matches(self): - """Test firing of event if no match.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - 'event_data': {'some_attr': 'some_value'} - }, - 'action': { - 'service': 'test.automation', - } - } - }) - - self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) - self.hass.block_till_done() - assert 0 == len(self.calls) + hass.bus.async_fire('test_event', {'some_attr': 'some_other_value'}) + await hass.async_block_till_done() + assert 0 == len(calls) diff --git a/tests/components/automation/test_geo_location.py b/tests/components/automation/test_geo_location.py index f14ea34d297..946c9a8abc6 100644 --- a/tests/components/automation/test_geo_location.py +++ b/tests/components/automation/test_geo_location.py @@ -1,268 +1,265 @@ """The tests for the geo location trigger.""" -import unittest +import pytest from homeassistant.components import automation, zone -from homeassistant.core import callback, Context -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component -from tests.common import get_test_home_assistant, mock_component +from tests.common import mock_component from tests.components.automation import common +from tests.common import async_mock_service -class TestAutomationGeoLocation(unittest.TestCase): - """Test the geo location trigger.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - assert setup_component(self.hass, zone.DOMAIN, { + +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.loop.run_until_complete(async_setup_component(hass, zone.DOMAIN, { 'zone': { 'name': 'test', 'latitude': 32.880837, 'longitude': -117.237561, 'radius': 250, } - }) + })) - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +async def test_if_fires_on_zone_enter(hass, calls): + """Test for firing on zone enter.""" + context = Context() + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + await hass.async_block_till_done() - self.hass.services.register('test', 'automation', record_call) - - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_if_fires_on_zone_enter(self): - """Test for firing on zone enter.""" - context = Context() - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758, - 'source': 'test_source' - }) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'enter', + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', - 'from_state.state', 'to_state.state', - 'zone.name')) - }, - } } - }) + } + }) - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }, context=context) - self.hass.block_till_done() + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }, context=context) + await hass.async_block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context - assert 'geo_location - geo_location.entity - hello - hello - test' == \ - self.calls[0].data['some'] + assert 1 == len(calls) + assert calls[0].context is context + assert 'geo_location - geo_location.entity - hello - hello - test' == \ + calls[0].data['some'] - # Set out of zone again so we can trigger call - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() + # Set out of zone again so we can trigger call + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() - common.turn_off(self.hass) - self.hass.block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() - assert 1 == len(self.calls) + assert 1 == len(calls) - def test_if_not_fires_for_enter_on_zone_leave(self): - """Test for not firing on zone leave.""" - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564, - 'source': 'test_source' - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'enter', +async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): + """Test for not firing on zone leave.""" + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() + + assert 0 == len(calls) + + +async def test_if_fires_on_zone_leave(hass, calls): + """Test for firing on zone leave.""" + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + await hass.async_block_till_done() + + assert 1 == len(calls) + + +async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): + """Test for not firing on zone enter.""" + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758, + 'source': 'test_source' + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() + + assert 0 == len(calls) + + +async def test_if_fires_on_zone_appear(hass, calls): + """Test for firing if entity appears in zone.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) }, - 'action': { - 'service': 'test.automation', - } + } - }) + } + }) - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() + # Entity appears in zone without previously existing outside the zone. + context = Context() + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }, context=context) + await hass.async_block_till_done() - assert 0 == len(self.calls) + assert 1 == len(calls) + assert calls[0].context is context + assert 'geo_location - geo_location.entity - - hello - test' == \ + calls[0].data['some'] - def test_if_fires_on_zone_leave(self): - """Test for firing on zone leave.""" - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564, - 'source': 'test_source' - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'leave', +async def test_if_fires_on_zone_disappear(hass, calls): + """Test for firing if entity disappears from zone.""" + hass.states.async_set('geo_location.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564, + 'source': 'test_source' + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'geo_location', + 'source': 'test_source', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) }, - 'action': { - 'service': 'test.automation', - } + } - }) + } + }) - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758, - 'source': 'test_source' - }) - self.hass.block_till_done() + # Entity disappears from zone without new coordinates outside the zone. + hass.states.async_remove('geo_location.entity') + await hass.async_block_till_done() - assert 1 == len(self.calls) - - def test_if_not_fires_for_leave_on_zone_enter(self): - """Test for not firing on zone enter.""" - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758, - 'source': 'test_source' - }) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'leave', - }, - 'action': { - 'service': 'test.automation', - } - } - }) - - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() - - assert 0 == len(self.calls) - - def test_if_fires_on_zone_appear(self): - """Test for firing if entity appears in zone.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'enter', - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', - 'from_state.state', 'to_state.state', - 'zone.name')) - }, - - } - } - }) - - # Entity appears in zone without previously existing outside the zone. - context = Context() - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564, - 'source': 'test_source' - }, context=context) - self.hass.block_till_done() - - assert 1 == len(self.calls) - assert self.calls[0].context is context - assert 'geo_location - geo_location.entity - - hello - test' == \ - self.calls[0].data['some'] - - def test_if_fires_on_zone_disappear(self): - """Test for firing if entity disappears from zone.""" - self.hass.states.set('geo_location.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564, - 'source': 'test_source' - }) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'geo_location', - 'source': 'test_source', - 'zone': 'zone.test', - 'event': 'leave', - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', - 'from_state.state', 'to_state.state', - 'zone.name')) - }, - - } - } - }) - - # Entity disappears from zone without new coordinates outside the zone. - self.hass.states.async_remove('geo_location.entity') - self.hass.block_till_done() - - assert 1 == len(self.calls) - assert 'geo_location - geo_location.entity - hello - - test' == \ - self.calls[0].data['some'] + assert 1 == len(calls) + assert 'geo_location - geo_location.entity - hello - - test' == \ + calls[0].data['some'] diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 015c318874a..28d4c0979c4 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,11 +1,12 @@ """The tests for the automation component.""" import asyncio from datetime import timedelta -import unittest from unittest.mock import patch +import pytest + from homeassistant.core import State, CoreState -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START) @@ -13,462 +14,428 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util from tests.common import ( - assert_setup_component, get_test_home_assistant, fire_time_changed, - mock_service, async_mock_service, mock_restore_cache) + assert_setup_component, async_fire_time_changed, + mock_restore_cache, async_mock_service) from tests.components.automation import common -# pylint: disable=invalid-name -class TestAutomation(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.calls = mock_service(self.hass, 'test', 'automation') - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_service_data_not_a_dict(self): - """Test service data not dict.""" - with assert_setup_component(0, automation.DOMAIN): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'data': 100, - } - } - }) - - def test_service_specify_data(self): - """Test service data.""" - assert setup_component(self.hass, automation.DOMAIN, { +async def test_service_data_not_a_dict(hass, calls): + """Test service data not dict.""" + with assert_setup_component(0, automation.DOMAIN): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { - 'alias': 'hello', 'trigger': { 'platform': 'event', 'event_type': 'test_event', }, 'action': { + 'service': 'test.automation', + 'data': 100, + } + } + }) + + +async def test_service_specify_data(hass, calls): + """Test service data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.platform }} - ' + '{{ trigger.event.event_type }}' + }, + } + } + }) + + time = dt_util.utcnow() + + with patch('homeassistant.components.automation.utcnow', + return_value=time): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data['some'] == 'event - test_event' + state = hass.states.get('automation.hello') + assert state is not None + assert state.attributes.get('last_triggered') == time + + state = hass.states.get('group.all_automations') + assert state is not None + assert state.attributes.get('entity_id') == ('automation.hello',) + + +async def test_action_delay(hass, calls): + """Test action delay.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': [ + { 'service': 'test.automation', 'data_template': { 'some': '{{ trigger.platform }} - ' '{{ trigger.event.event_type }}' - }, - } - } - }) - - time = dt_util.utcnow() - - with patch('homeassistant.components.automation.utcnow', - return_value=time): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data['some'] == 'event - test_event' - state = self.hass.states.get('automation.hello') - assert state is not None - assert state.attributes.get('last_triggered') == time - - state = self.hass.states.get('group.all_automations') - assert state is not None - assert state.attributes.get('entity_id') == ('automation.hello',) - - def test_action_delay(self): - """Test action delay.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': [ - { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.platform }} - ' - '{{ trigger.event.event_type }}' - } - }, - {'delay': {'minutes': '10'}}, - { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.platform }} - ' - '{{ trigger.event.event_type }}' - } - }, - ] - } - }) - - time = dt_util.utcnow() - - with patch('homeassistant.components.automation.utcnow', - return_value=time): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert len(self.calls) == 1 - assert self.calls[0].data['some'] == 'event - test_event' - - future = dt_util.utcnow() + timedelta(minutes=10) - fire_time_changed(self.hass, future) - self.hass.block_till_done() - - assert len(self.calls) == 2 - assert self.calls[1].data['some'] == 'event - test_event' - - state = self.hass.states.get('automation.hello') - assert state is not None - assert state.attributes.get('last_triggered') == time - state = self.hass.states.get('group.all_automations') - assert state is not None - assert state.attributes.get('entity_id') == ('automation.hello',) - - def test_service_specify_entity_id(self): - """Test service data.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'entity_id': 'hello.world' - } - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert ['hello.world'] == \ - self.calls[0].data.get(ATTR_ENTITY_ID) - - def test_service_specify_entity_id_list(self): - """Test service data.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'entity_id': ['hello.world', 'hello.world2'] - } - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert ['hello.world', 'hello.world2'] == \ - self.calls[0].data.get(ATTR_ENTITY_ID) - - def test_two_triggers(self): - """Test triggers.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': [ - { - 'platform': 'event', - 'event_type': 'test_event', - }, - { - 'platform': 'state', - 'entity_id': 'test.entity', } - ], - 'action': { - 'service': 'test.automation', - } - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - assert 2 == len(self.calls) - - def test_trigger_service_ignoring_condition(self): - """Test triggers.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'test', - 'trigger': [ - { - 'platform': 'event', - 'event_type': 'test_event', - }, - ], - 'condition': { - 'condition': 'state', - 'entity_id': 'non.existing', - 'state': 'beer', }, - 'action': { - 'service': 'test.automation', - } - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 0 - - self.hass.services.call('automation', 'trigger', - {'entity_id': 'automation.test'}, - blocking=True) - self.hass.block_till_done() - assert len(self.calls) == 1 - - def test_two_conditions_with_and(self): - """Test two and conditions.""" - entity_id = 'test.entity' - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': [ - { - 'platform': 'event', - 'event_type': 'test_event', - }, - ], - 'condition': [ - { - 'condition': 'state', - 'entity_id': entity_id, - 'state': '100' - }, - { - 'condition': 'numeric_state', - 'entity_id': entity_id, - 'below': 150 - } - ], - 'action': { - 'service': 'test.automation', - } - } - }) - - self.hass.states.set(entity_id, 100) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - self.hass.states.set(entity_id, 101) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - self.hass.states.set(entity_id, 151) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_automation_list_setting(self): - """Event is not a valid condition.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: [{ - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - - 'action': { - 'service': 'test.automation', - } - }, { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event_2', - }, - 'action': { - 'service': 'test.automation', - } - }] - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - self.hass.bus.fire('test_event_2') - self.hass.block_till_done() - assert 2 == len(self.calls) - - def test_automation_calling_two_actions(self): - """Test if we can call two actions from automation definition.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - - 'action': [{ - 'service': 'test.automation', - 'data': {'position': 0}, - }, { - 'service': 'test.automation', - 'data': {'position': 1}, - }], - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert len(self.calls) == 2 - assert self.calls[0].data['position'] == 0 - assert self.calls[1].data['position'] == 1 - - def test_services(self): - """Test the automation services for turning entities on/off.""" - entity_id = 'automation.hello' - - assert self.hass.states.get(entity_id) is None - assert not automation.is_on(self.hass, entity_id) - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - } - } - }) - - assert self.hass.states.get(entity_id) is not None - assert automation.is_on(self.hass, entity_id) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 1 - - common.turn_off(self.hass, entity_id) - self.hass.block_till_done() - - assert not automation.is_on(self.hass, entity_id) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 1 - - common.toggle(self.hass, entity_id) - self.hass.block_till_done() - - assert automation.is_on(self.hass, entity_id) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 2 - - common.trigger(self.hass, entity_id) - self.hass.block_till_done() - assert len(self.calls) == 3 - - common.turn_off(self.hass, entity_id) - self.hass.block_till_done() - common.trigger(self.hass, entity_id) - self.hass.block_till_done() - assert len(self.calls) == 4 - - common.turn_on(self.hass, entity_id) - self.hass.block_till_done() - assert automation.is_on(self.hass, entity_id) - - def test_reload_config_service(self): - """Test the reload config service.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { + {'delay': {'minutes': '10'}}, + { 'service': 'test.automation', 'data_template': { - 'event': '{{ trigger.event.event_type }}' + 'some': '{{ trigger.platform }} - ' + '{{ trigger.event.event_type }}' } + }, + ] + } + }) + + time = dt_util.utcnow() + + with patch('homeassistant.components.automation.utcnow', + return_value=time): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data['some'] == 'event - test_event' + + future = dt_util.utcnow() + timedelta(minutes=10) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert len(calls) == 2 + assert calls[1].data['some'] == 'event - test_event' + + state = hass.states.get('automation.hello') + assert state is not None + assert state.attributes.get('last_triggered') == time + state = hass.states.get('group.all_automations') + assert state is not None + assert state.attributes.get('entity_id') == ('automation.hello',) + + +async def test_service_specify_entity_id(hass, calls): + """Test service data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + assert ['hello.world'] == \ + calls[0].data.get(ATTR_ENTITY_ID) + + +async def test_service_specify_entity_id_list(hass, calls): + """Test service data.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': ['hello.world', 'hello.world2'] + } + } + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + assert ['hello.world', 'hello.world2'] == \ + calls[0].data.get(ATTR_ENTITY_ID) + + +async def test_two_triggers(hass, calls): + """Test triggers.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + { + 'platform': 'state', + 'entity_id': 'test.entity', + } + ], + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() + assert 2 == len(calls) + + +async def test_trigger_service_ignoring_condition(hass, calls): + """Test triggers.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'test', + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + ], + 'condition': { + 'condition': 'state', + 'entity_id': 'non.existing', + 'state': 'beer', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 0 + + await hass.services.async_call( + 'automation', 'trigger', + {'entity_id': 'automation.test'}, + blocking=True) + assert len(calls) == 1 + + +async def test_two_conditions_with_and(hass, calls): + """Test two and conditions.""" + entity_id = 'test.entity' + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + ], + 'condition': [ + { + 'condition': 'state', + 'entity_id': entity_id, + 'state': '100' + }, + { + 'condition': 'numeric_state', + 'entity_id': entity_id, + 'below': 150 + } + ], + 'action': { + 'service': 'test.automation', + } + } + }) + + hass.states.async_set(entity_id, 100) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + hass.states.async_set(entity_id, 101) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + hass.states.async_set(entity_id, 151) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_automation_list_setting(hass, calls): + """Event is not a valid condition.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: [{ + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + + 'action': { + 'service': 'test.automation', + } + }, { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event_2', + }, + 'action': { + 'service': 'test.automation', + } + }] + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + hass.bus.async_fire('test_event_2') + await hass.async_block_till_done() + assert 2 == len(calls) + + +async def test_automation_calling_two_actions(hass, calls): + """Test if we can call two actions from automation async definition.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + + 'action': [{ + 'service': 'test.automation', + 'data': {'position': 0}, + }, { + 'service': 'test.automation', + 'data': {'position': 1}, + }], + } + }) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert len(calls) == 2 + assert calls[0].data['position'] == 0 + assert calls[1].data['position'] == 1 + + +async def test_services(hass, calls): + """Test the automation services for turning entities on/off.""" + entity_id = 'automation.hello' + + assert hass.states.get(entity_id) is None + assert not automation.is_on(hass, entity_id) + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + assert hass.states.get(entity_id) is not None + assert automation.is_on(hass, entity_id) + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 1 + + await common.async_turn_off(hass, entity_id) + await hass.async_block_till_done() + + assert not automation.is_on(hass, entity_id) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 1 + + await common.async_toggle(hass, entity_id) + await hass.async_block_till_done() + + assert automation.is_on(hass, entity_id) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 2 + + await common.async_trigger(hass, entity_id) + await hass.async_block_till_done() + assert len(calls) == 3 + + await common.async_turn_off(hass, entity_id) + await hass.async_block_till_done() + await common.async_trigger(hass, entity_id) + await hass.async_block_till_done() + assert len(calls) == 4 + + await common.async_turn_on(hass, entity_id) + await hass.async_block_till_done() + assert automation.is_on(hass, entity_id) + + +async def test_reload_config_service(hass, calls): + """Test the reload config service.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'event': '{{ trigger.event.event_type }}' } } - }) - assert self.hass.states.get('automation.hello') is not None - assert self.hass.states.get('automation.bye') is None - listeners = self.hass.bus.listeners - assert listeners.get('test_event') == 1 - assert listeners.get('test_event2') is None + } + }) + assert hass.states.get('automation.hello') is not None + assert hass.states.get('automation.bye') is None + listeners = hass.bus.async_listeners() + assert listeners.get('test_event') == 1 + assert listeners.get('test_event2') is None - self.hass.bus.fire('test_event') - self.hass.block_till_done() + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data.get('event') == 'test_event' + assert len(calls) == 1 + assert calls[0].data.get('event') == 'test_event' - with patch('homeassistant.config.load_yaml_config_file', autospec=True, - return_value={ - automation.DOMAIN: { - 'alias': 'bye', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event2', - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'event': '{{ trigger.event.event_type }}' - } - } - }}): - with patch('homeassistant.config.find_config_file', - return_value=''): - common.reload(self.hass) - self.hass.block_till_done() - # De-flake ?! - self.hass.block_till_done() - - assert self.hass.states.get('automation.hello') is None - assert self.hass.states.get('automation.bye') is not None - listeners = self.hass.bus.listeners - assert listeners.get('test_event') is None - assert listeners.get('test_event2') == 1 - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 1 - - self.hass.bus.fire('test_event2') - self.hass.block_till_done() - assert len(self.calls) == 2 - assert self.calls[1].data.get('event') == 'test_event2' - - def test_reload_config_when_invalid_config(self): - """Test the reload config service handling invalid config.""" - with assert_setup_component(1, automation.DOMAIN): - assert setup_component(self.hass, automation.DOMAIN, { + with patch('homeassistant.config.load_yaml_config_file', autospec=True, + return_value={ automation.DOMAIN: { - 'alias': 'hello', + 'alias': 'bye', 'trigger': { 'platform': 'event', - 'event_type': 'test_event', + 'event_type': 'test_event2', }, 'action': { 'service': 'test.automation', @@ -476,32 +443,34 @@ class TestAutomation(unittest.TestCase): 'event': '{{ trigger.event.event_type }}' } } - } - }) - assert self.hass.states.get('automation.hello') is not None + }}): + with patch('homeassistant.config.find_config_file', + return_value=''): + await common.async_reload(hass) + await hass.async_block_till_done() + # De-flake ?! + await hass.async_block_till_done() - self.hass.bus.fire('test_event') - self.hass.block_till_done() + assert hass.states.get('automation.hello') is None + assert hass.states.get('automation.bye') is not None + listeners = hass.bus.async_listeners() + assert listeners.get('test_event') is None + assert listeners.get('test_event2') == 1 - assert len(self.calls) == 1 - assert self.calls[0].data.get('event') == 'test_event' + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 1 - with patch('homeassistant.config.load_yaml_config_file', autospec=True, - return_value={automation.DOMAIN: 'not valid'}): - with patch('homeassistant.config.find_config_file', - return_value=''): - common.reload(self.hass) - self.hass.block_till_done() + hass.bus.async_fire('test_event2') + await hass.async_block_till_done() + assert len(calls) == 2 + assert calls[1].data.get('event') == 'test_event2' - assert self.hass.states.get('automation.hello') is None - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 1 - - def test_reload_config_handles_load_fails(self): - """Test the reload config service.""" - assert setup_component(self.hass, automation.DOMAIN, { +async def test_reload_config_when_invalid_config(hass, calls): + """Test the reload config service handling invalid config.""" + with assert_setup_component(1, automation.DOMAIN): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'alias': 'hello', 'trigger': { @@ -516,26 +485,65 @@ class TestAutomation(unittest.TestCase): } } }) - assert self.hass.states.get('automation.hello') is not None + assert hass.states.get('automation.hello') is not None - self.hass.bus.fire('test_event') - self.hass.block_till_done() + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - assert len(self.calls) == 1 - assert self.calls[0].data.get('event') == 'test_event' + assert len(calls) == 1 + assert calls[0].data.get('event') == 'test_event' - with patch('homeassistant.config.load_yaml_config_file', - side_effect=HomeAssistantError('bla')): - with patch('homeassistant.config.find_config_file', - return_value=''): - common.reload(self.hass) - self.hass.block_till_done() + with patch('homeassistant.config.load_yaml_config_file', autospec=True, + return_value={automation.DOMAIN: 'not valid'}): + with patch('homeassistant.config.find_config_file', + return_value=''): + await common.async_reload(hass) + await hass.async_block_till_done() - assert self.hass.states.get('automation.hello') is not None + assert hass.states.get('automation.hello') is None - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert len(self.calls) == 2 + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 1 + + +async def test_reload_config_handles_load_fails(hass, calls): + """Test the reload config service.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'event': '{{ trigger.event.event_type }}' + } + } + } + }) + assert hass.states.get('automation.hello') is not None + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data.get('event') == 'test_event' + + with patch('homeassistant.config.load_yaml_config_file', + side_effect=HomeAssistantError('bla')): + with patch('homeassistant.config.find_config_file', + return_value=''): + await common.async_reload(hass) + await hass.async_block_till_done() + + assert hass.states.get('automation.hello') is not None + + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert len(calls) == 2 @asyncio.coroutine diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 2d43c4a6d65..196fdaa9a6f 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -1,102 +1,94 @@ """The tests for the MQTT automation.""" -import unittest +import pytest -from homeassistant.core import callback -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation from tests.common import ( - mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, - mock_component) + async_fire_mqtt_message, + mock_component, async_mock_service, async_mock_mqtt_component) from tests.components.automation import common -# pylint: disable=invalid-name -class TestAutomationMQTT(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - mock_mqtt_component(self.hass) - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.loop.run_until_complete(async_mock_mqtt_component(hass)) - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_if_fires_on_topic_match(self): - """Test if message is fired on topic match.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'mqtt', - 'topic': 'test-topic' +async def test_if_fires_on_topic_match(hass, calls): + """Test if message is fired on topic match.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic' + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.platform }} - {{ trigger.topic }}' + ' - {{ trigger.payload }} - ' + '{{ trigger.payload_json.hello }}' }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.platform }} - {{ trigger.topic }}' - ' - {{ trigger.payload }} - ' - '{{ trigger.payload_json.hello }}' - }, - } } - }) + } + }) - fire_mqtt_message(self.hass, 'test-topic', '{ "hello": "world" }') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'mqtt - test-topic - { "hello": "world" } - world' == \ - self.calls[0].data['some'] + async_fire_mqtt_message(hass, 'test-topic', '{ "hello": "world" }') + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'mqtt - test-topic - { "hello": "world" } - world' == \ + calls[0].data['some'] - common.turn_off(self.hass) - self.hass.block_till_done() - fire_mqtt_message(self.hass, 'test-topic', 'test_payload') - self.hass.block_till_done() - assert 1 == len(self.calls) + await common.async_turn_off(hass) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, 'test-topic', 'test_payload') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_topic_and_payload_match(self): - """Test if message is fired on topic and payload match.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'mqtt', - 'topic': 'test-topic', - 'payload': 'hello' - }, - 'action': { - 'service': 'test.automation' - } + +async def test_if_fires_on_topic_and_payload_match(hass, calls): + """Test if message is fired on topic and payload match.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic', + 'payload': 'hello' + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_mqtt_message(self.hass, 'test-topic', 'hello') - self.hass.block_till_done() - assert 1 == len(self.calls) + async_fire_mqtt_message(hass, 'test-topic', 'hello') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_not_fires_on_topic_but_no_payload_match(self): - """Test if message is not fired on topic but no payload.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'mqtt', - 'topic': 'test-topic', - 'payload': 'hello' - }, - 'action': { - 'service': 'test.automation' - } + +async def test_if_not_fires_on_topic_but_no_payload_match(hass, calls): + """Test if message is not fired on topic but no payload.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic', + 'payload': 'hello' + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_mqtt_message(self.hass, 'test-topic', 'no-hello') - self.hass.block_till_done() - assert 0 == len(self.calls) + async_fire_mqtt_message(hass, 'test-topic', 'no-hello') + await hass.async_block_till_done() + assert 0 == len(calls) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index f7124be9dab..92a5f3b8b92 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,876 +1,908 @@ """The tests for numeric state automation.""" from datetime import timedelta -import unittest +import pytest from unittest.mock import patch import homeassistant.components.automation as automation -from homeassistant.core import Context, callback -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import ( - get_test_home_assistant, mock_component, fire_time_changed, - assert_setup_component) + mock_component, async_fire_time_changed, + assert_setup_component, async_mock_service) from tests.components.automation import common -# pylint: disable=invalid-name -class TestAutomationNumericState(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() - - def test_if_fires_on_entity_change_below(self): - """Test the firing with changed entity.""" - context = Context() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } +async def test_if_fires_on_entity_change_below(hass, calls): + """Test the firing with changed entity.""" + context = Context() + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' } - }) - # 9 is below 10 - self.hass.states.set('test.entity', 9, context=context) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context + } + }) + # 9 is below 10 + hass.states.async_set('test.entity', 9, context=context) + await hass.async_block_till_done() + assert 1 == len(calls) + assert calls[0].context is context - # Set above 12 so the automation will fire again - self.hass.states.set('test.entity', 12) - common.turn_off(self.hass) - self.hass.block_till_done() - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - assert 1 == len(self.calls) + # Set above 12 so the automation will fire again + hass.states.async_set('test.entity', 12) + await common.async_turn_off(hass) + await hass.async_block_till_done() + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_fires_on_entity_change_over_to_below(self): - """Test the firing with changed entity.""" - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } +async def test_if_fires_on_entity_change_over_to_below(hass, calls): + """Test the firing with changed entity.""" + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' } - }) - - # 9 is below 10 - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entities_change_over_to_below(self): - """Test the firing with changed entities.""" - self.hass.states.set('test.entity_1', 11) - self.hass.states.set('test.entity_2', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': [ - 'test.entity_1', - 'test.entity_2', - ], - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 9 is below 10 - self.hass.states.set('test.entity_1', 9) - self.hass.block_till_done() - assert 1 == len(self.calls) - self.hass.states.set('test.entity_2', 9) - self.hass.block_till_done() - assert 2 == len(self.calls) - - def test_if_not_fires_on_entity_change_below_to_below(self): - """Test the firing with changed entity.""" - context = Context() - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 9 is below 10 so this should fire - self.hass.states.set('test.entity', 9, context=context) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context - - # already below so should not fire again - self.hass.states.set('test.entity', 5) - self.hass.block_till_done() - assert 1 == len(self.calls) - - # still below so should not fire again - self.hass.states.set('test.entity', 3) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_below_fires_on_entity_change_to_equal(self): - """Test the firing with changed entity.""" - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 10 is not below 10 so this should not fire again - self.hass.states.set('test.entity', 10) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_initial_entity_below(self): - """Test the firing when starting with a match.""" - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # Fire on first update even if initial state was already below - self.hass.states.set('test.entity', 8) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_initial_entity_above(self): - """Test the firing when starting with a match.""" - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # Fire on first update even if initial state was already above - self.hass.states.set('test.entity', 12) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_above(self): - """Test the firing with changed entity.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is above 10 - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_below_to_above(self): - """Test the firing with changed entity.""" - # set initial state - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 11 is above 10 and 9 is below - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_fires_on_entity_change_above_to_above(self): - """Test the firing with changed entity.""" - # set initial state - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 12 is above 10 so this should fire - self.hass.states.set('test.entity', 12) - self.hass.block_till_done() - assert 1 == len(self.calls) - - # already above, should not fire again - self.hass.states.set('test.entity', 15) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_above_fires_on_entity_change_to_equal(self): - """Test the firing with changed entity.""" - # set initial state - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 10 is not above 10 so this should not fire again - self.hass.states.set('test.entity', 10) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_entity_change_below_range(self): - """Test the firing with changed entity.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - 'above': 5, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 9 is below 10 - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_below_above_range(self): - """Test the firing with changed entity.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - 'above': 5, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 4 is below 5 - self.hass.states.set('test.entity', 4) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_entity_change_over_to_below_range(self): - """Test the firing with changed entity.""" - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - 'above': 5, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 9 is below 10 - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_over_to_below_above_range(self): - """Test the firing with changed entity.""" - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - 'above': 5, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # 4 is below 5 so it should not fire - self.hass.states.set('test.entity', 4) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_if_entity_not_match(self): - """Test if not fired with non matching entity.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.another_entity', - 'below': 100, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 11) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_entity_change_below_with_attribute(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 9 is below 10 - self.hass.states.set('test.entity', 9, {'test_attribute': 11}) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_fires_on_entity_change_not_below_with_attribute(self): - """Test attributes.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is not below 10 - self.hass.states.set('test.entity', 11, {'test_attribute': 9}) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_attribute_change_with_attribute_below(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 9 is below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': 9}) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_fires_on_attribute_change_with_attribute_not_below(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is not below 10 - self.hass.states.set('test.entity', 'entity', {'test_attribute': 11}) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_on_entity_change_with_attribute_below(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is not below 10, entity state value should not be tested - self.hass.states.set('test.entity', '9', {'test_attribute': 11}) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_on_entity_change_with_not_attribute_below(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is not below 10, entity state value should not be tested - self.hass.states.set('test.entity', 'entity') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(self): - """Test attributes change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 9 is not below 10 - self.hass.states.set('test.entity', 'entity', - {'test_attribute': 9, 'not_test_attribute': 11}) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_template_list(self): - """Test template list.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': - '{{ state.attributes.test_attribute[2] }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 3 is below 10 - self.hass.states.set('test.entity', 'entity', - {'test_attribute': [11, 15, 3]}) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_template_string(self): - """Test template string.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': - '{{ state.attributes.test_attribute | multiply(10) }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', 'below', 'above', - 'from_state.state', 'to_state.state')) - }, - } - } - }) - self.hass.states.set('test.entity', 'test state 1', - {'test_attribute': '1.2'}) - self.hass.block_till_done() - self.hass.states.set('test.entity', 'test state 2', - {'test_attribute': '0.9'}) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'numeric_state - test.entity - 10.0 - None - test state 1 - ' \ - 'test state 2' == \ - self.calls[0].data['some'] - - def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self): - """Test if not fired changed attributes.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'value_template': '{{ state.attributes.test_attribute }}', - 'below': 10, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - # 11 is not below 10 - self.hass.states.set('test.entity', 'entity', - {'test_attribute': 11, 'not_test_attribute': 9}) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_action(self): - """Test if action.""" - entity_id = 'domain.test_entity' - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'numeric_state', - 'entity_id': entity_id, - 'above': 8, - 'below': 12, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set(entity_id, 10) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - self.hass.states.set(entity_id, 8) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - self.hass.states.set(entity_id, 9) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 2 == len(self.calls) - - def test_if_fails_setup_bad_for(self): - """Test for setup failure for bad for.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 8, - 'below': 12, - 'for': { - 'invalid': 5 - }, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_fails_setup_for_without_above_below(self): - """Test for setup failures for missing above or below.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_not_fires_on_entity_change_with_for(self): - """Test for not firing on entity change with for.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 8, - 'below': 12, - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - self.hass.states.set('test.entity', 15) - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_on_entities_change_with_for_after_stop(self): - """Test for not firing on entities change with for after stop.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': [ - 'test.entity_1', - 'test.entity_2', - ], - 'above': 8, - 'below': 12, - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity_1', 9) - self.hass.states.set('test.entity_2', 9) - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 2 == len(self.calls) - - self.hass.states.set('test.entity_1', 15) - self.hass.states.set('test.entity_2', 15) - self.hass.block_till_done() - self.hass.states.set('test.entity_1', 9) - self.hass.states.set('test.entity_2', 9) - self.hass.block_till_done() - common.turn_off(self.hass) - self.hass.block_till_done() - - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 2 == len(self.calls) - - def test_if_fires_on_entity_change_with_for_attribute_change(self): - """Test for firing on entity change with for and attribute change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 8, - 'below': 12, - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - utcnow = dt_util.utcnow() - with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: - mock_utcnow.return_value = utcnow - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - mock_utcnow.return_value += timedelta(seconds=4) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.states.set('test.entity', 9, - attributes={"mock_attr": "attr_change"}) - self.hass.block_till_done() - assert 0 == len(self.calls) - mock_utcnow.return_value += timedelta(seconds=4) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_with_for(self): - """Test for firing on entity change with for.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 8, - 'below': 12, - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 9) - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_wait_template_with_trigger(self): - """Test using wait template with 'trigger.entity_id'.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': 'test.entity', - 'above': 10, - }, - 'action': [ - {'wait_template': - "{{ states(trigger.entity_id) | int < 10 }}"}, - {'service': 'test.automation', - 'data_template': { - 'some': - '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', 'to_state.state')) - }} + } + }) + + # 9 is below 10 + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entities_change_over_to_below(hass, calls): + """Test the firing with changed entities.""" + hass.states.async_set('test.entity_1', 11) + hass.states.async_set('test.entity_2', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': [ + 'test.entity_1', + 'test.entity_2', ], + 'below': 10, + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.block_till_done() - self.calls = [] + # 9 is below 10 + hass.states.async_set('test.entity_1', 9) + await hass.async_block_till_done() + assert 1 == len(calls) + hass.states.async_set('test.entity_2', 9) + await hass.async_block_till_done() + assert 2 == len(calls) - self.hass.states.set('test.entity', '12') - self.hass.block_till_done() - self.hass.states.set('test.entity', '8') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'numeric_state - test.entity - 12' == \ - self.calls[0].data['some'] + +async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): + """Test the firing with changed entity.""" + context = Context() + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 9 is below 10 so this should fire + hass.states.async_set('test.entity', 9, context=context) + await hass.async_block_till_done() + assert 1 == len(calls) + assert calls[0].context is context + + # already below so should not fire again + hass.states.async_set('test.entity', 5) + await hass.async_block_till_done() + assert 1 == len(calls) + + # still below so should not fire again + hass.states.async_set('test.entity', 3) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): + """Test the firing with changed entity.""" + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 10 is not below 10 so this should not fire again + hass.states.async_set('test.entity', 10) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_initial_entity_below(hass, calls): + """Test the firing when starting with a match.""" + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # Fire on first update even if initial state was already below + hass.states.async_set('test.entity', 8) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_initial_entity_above(hass, calls): + """Test the firing when starting with a match.""" + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # Fire on first update even if initial state was already above + hass.states.async_set('test.entity', 12) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_above(hass, calls): + """Test the firing with changed entity.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is above 10 + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_below_to_above(hass, calls): + """Test the firing with changed entity.""" + # set initial state + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 11 is above 10 and 9 is below + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): + """Test the firing with changed entity.""" + # set initial state + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 12 is above 10 so this should fire + hass.states.async_set('test.entity', 12) + await hass.async_block_till_done() + assert 1 == len(calls) + + # already above, should not fire again + hass.states.async_set('test.entity', 15) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): + """Test the firing with changed entity.""" + # set initial state + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 10 is not above 10 so this should not fire again + hass.states.async_set('test.entity', 10) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_entity_change_below_range(hass, calls): + """Test the firing with changed entity.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 9 is below 10 + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_below_above_range(hass, calls): + """Test the firing with changed entity.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 4 is below 5 + hass.states.async_set('test.entity', 4) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): + """Test the firing with changed entity.""" + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 9 is below 10 + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_over_to_below_above_range( + hass, calls): + """Test the firing with changed entity.""" + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # 4 is below 5 so it should not fire + hass.states.async_set('test.entity', 4) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_if_entity_not_match(hass, calls): + """Test if not fired with non matching entity.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.another_entity', + 'below': 100, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 11) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 9 is below 10 + hass.states.async_set('test.entity', 9, {'test_attribute': 11}) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_on_entity_change_not_below_with_attribute( + hass, calls): + """Test attributes.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is not below 10 + hass.states.async_set('test.entity', 11, {'test_attribute': 9}) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 9 is below 10 + hass.states.async_set('test.entity', 'entity', {'test_attribute': 9}) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_on_attribute_change_with_attribute_not_below( + hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is not below 10 + hass.states.async_set('test.entity', 'entity', {'test_attribute': 11}) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is not below 10, entity state value should not be tested + hass.states.async_set('test.entity', '9', {'test_attribute': 11}) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_on_entity_change_with_not_attribute_below( + hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is not below 10, entity state value should not be tested + hass.states.async_set('test.entity', 'entity') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr( + hass, calls): + """Test attributes change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 9 is not below 10 + hass.states.async_set('test.entity', 'entity', + {'test_attribute': 9, 'not_test_attribute': 11}) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_template_list(hass, calls): + """Test template list.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': + '{{ state.attributes.test_attribute[2] }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 3 is below 10 + hass.states.async_set('test.entity', 'entity', + {'test_attribute': [11, 15, 3]}) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_template_string(hass, calls): + """Test template string.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': + '{{ state.attributes.test_attribute | multiply(10) }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', 'below', 'above', + 'from_state.state', 'to_state.state')) + }, + } + } + }) + hass.states.async_set('test.entity', 'test state 1', + {'test_attribute': '1.2'}) + await hass.async_block_till_done() + hass.states.async_set('test.entity', 'test state 2', + {'test_attribute': '0.9'}) + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'numeric_state - test.entity - 10.0 - None - test state 1 - ' \ + 'test state 2' == \ + calls[0].data['some'] + + +async def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr( + hass, calls): + """Test if not fired changed attributes.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + # 11 is not below 10 + hass.states.async_set('test.entity', 'entity', + {'test_attribute': 11, 'not_test_attribute': 9}) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_action(hass, calls): + """Test if action.""" + entity_id = 'domain.test_entity' + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'numeric_state', + 'entity_id': entity_id, + 'above': 8, + 'below': 12, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set(entity_id, 10) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + hass.states.async_set(entity_id, 8) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + hass.states.async_set(entity_id, 9) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 2 == len(calls) + + +async def test_if_fails_setup_bad_for(hass, calls): + """Test for setup failure for bad for.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 8, + 'below': 12, + 'for': { + 'invalid': 5 + }, + }, + 'action': { + 'service': 'homeassistant.turn_on', + } + }}) + + +async def test_if_fails_setup_for_without_above_below(hass, calls): + """Test for setup failures for missing above or below.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'homeassistant.turn_on', + } + }}) + + +async def test_if_not_fires_on_entity_change_with_for(hass, calls): + """Test for not firing on entity change with for.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 8, + 'below': 12, + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + hass.states.async_set('test.entity', 15) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, + calls): + """Test for not firing on entities change with for after stop.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': [ + 'test.entity_1', + 'test.entity_2', + ], + 'above': 8, + 'below': 12, + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity_1', 9) + hass.states.async_set('test.entity_2', 9) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 2 == len(calls) + + hass.states.async_set('test.entity_1', 15) + hass.states.async_set('test.entity_2', 15) + await hass.async_block_till_done() + hass.states.async_set('test.entity_1', 9) + hass.states.async_set('test.entity_2', 9) + await hass.async_block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 2 == len(calls) + + +async def test_if_fires_on_entity_change_with_for_attribute_change(hass, + calls): + """Test for firing on entity change with for and attribute change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 8, + 'below': 12, + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + utcnow = dt_util.utcnow() + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = utcnow + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=4) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set('test.entity', 9, + attributes={"mock_attr": "attr_change"}) + await hass.async_block_till_done() + assert 0 == len(calls) + mock_utcnow.return_value += timedelta(seconds=4) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_with_for(hass, calls): + """Test for firing on entity change with for.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 8, + 'below': 12, + 'for': { + 'seconds': 5 + }, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 9) + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_wait_template_with_trigger(hass, calls): + """Test using wait template with 'trigger.entity_id'.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': [ + {'wait_template': + "{{ states(trigger.entity_id) | int < 10 }}"}, + {'service': 'test.automation', + 'data_template': { + 'some': + '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', 'to_state.state')) + }} + ], + } + }) + + await hass.async_block_till_done() + + hass.states.async_set('test.entity', '12') + await hass.async_block_till_done() + hass.states.async_set('test.entity', '8') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'numeric_state - test.entity - 12' == \ + calls[0].data['some'] diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 599816ac7dc..abe02638f26 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,619 +1,634 @@ """The test for state automation.""" from datetime import timedelta -import unittest +import pytest from unittest.mock import patch -from homeassistant.core import Context, callback -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation from tests.common import ( - fire_time_changed, get_test_home_assistant, assert_setup_component, - mock_component) + async_fire_time_changed, assert_setup_component, mock_component) from tests.components.automation import common +from tests.common import async_mock_service -# pylint: disable=invalid-name -class TestAutomationState(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - self.hass.states.set('test.entity', 'hello') - self.calls = [] - @callback - def record_call(service): - """Call recorder.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.states.async_set('test.entity', 'hello') - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() +async def test_if_fires_on_entity_change(hass, calls): + """Test for firing on entity change.""" + context = Context() + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() - def test_if_fires_on_entity_change(self): - """Test for firing on entity change.""" - context = Context() - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'for')) + }, + } + } + }) - assert setup_component(self.hass, automation.DOMAIN, { + hass.states.async_set('test.entity', 'world', context=context) + await hass.async_block_till_done() + assert 1 == len(calls) + assert calls[0].context is context + assert 'state - test.entity - hello - world - None' == \ + calls[0].data['some'] + + await common.async_turn_off(hass) + await hass.async_block_till_done() + hass.states.async_set('test.entity', 'planet') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_with_from_filter(hass, calls): + """Test for firing on entity change with filter.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_with_to_filter(hass, calls): + """Test for firing on entity change with no filter.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_attribute_change_with_to_filter(hass, calls): + """Test for not firing on attribute change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world', {'test_attribute': 11}) + hass.states.async_set('test.entity', 'world', {'test_attribute': 12}) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_entity_change_with_both_filters(hass, calls): + """Test for firing if both filters are a non match.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_if_to_filter_not_match(hass, calls): + """Test for not firing if to filter is not a match.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'moon') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_if_from_filter_not_match(hass, calls): + """Test for not firing if from filter is not a match.""" + hass.states.async_set('test.entity', 'bye') + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_if_entity_not_match(hass, calls): + """Test for not firing if entity is not matching.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.another_entity', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_action(hass, calls): + """Test for to action.""" + entity_id = 'domain.test_entity' + test_state = 'new_state' + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': [{ + 'condition': 'state', + 'entity_id': entity_id, + 'state': test_state + }], + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set(entity_id, test_state) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + hass.states.async_set(entity_id, test_state + 'something') + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + +async def test_if_fails_setup_if_to_boolean_value(hass, calls): + """Test for setup failure for boolean to.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', 'entity_id': 'test.entity', + 'to': True, }, 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', - 'from_state.state', 'to_state.state', - 'for')) + 'service': 'homeassistant.turn_on', + } + }}) + + +async def test_if_fails_setup_if_from_boolean_value(hass, calls): + """Test for setup failure for boolean from.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': True, + }, + 'action': { + 'service': 'homeassistant.turn_on', + } + }}) + + +async def test_if_fails_setup_bad_for(hass, calls): + """Test for setup failure for bad for.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'invalid': 5 }, + }, + 'action': { + 'service': 'homeassistant.turn_on', } - } - }) + }}) - self.hass.states.set('test.entity', 'world', context=context) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context - assert 'state - test.entity - hello - world - None' == \ - self.calls[0].data['some'] - common.turn_off(self.hass) - self.hass.block_till_done() - self.hass.states.set('test.entity', 'planet') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_with_from_filter(self): - """Test for firing on entity change with filter.""" - assert setup_component(self.hass, automation.DOMAIN, { +async def test_if_fails_setup_for_without_to(hass, calls): + """Test for setup failures for missing to.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', 'entity_id': 'test.entity', - 'from': 'hello' + 'for': { + 'seconds': 5 + }, }, 'action': { - 'service': 'test.automation' + 'service': 'homeassistant.turn_on', } - } - }) + }}) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) - def test_if_fires_on_entity_change_with_to_filter(self): - """Test for firing on entity change with no filter.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world' +async def test_if_not_fires_on_entity_change_with_for(hass, calls): + """Test for not firing on entity change with for.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'seconds': 5 }, - 'action': { - 'service': 'test.automation' - } + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + hass.states.async_set('test.entity', 'not_world') + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 0 == len(calls) - def test_if_fires_on_attribute_change_with_to_filter(self): - """Test for not firing on attribute change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world' + +async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, + calls): + """Test for not firing on entity change with for after stop trigger.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': [ + 'test.entity_1', + 'test.entity_2', + ], + 'to': 'world', + 'for': { + 'seconds': 5 }, - 'action': { - 'service': 'test.automation' - } + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'world', {'test_attribute': 11}) - self.hass.states.set('test.entity', 'world', {'test_attribute': 12}) - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.states.async_set('test.entity_1', 'world') + hass.states.async_set('test.entity_2', 'world') + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 2 == len(calls) - def test_if_fires_on_entity_change_with_both_filters(self): - """Test for firing if both filters are a non match.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'from': 'hello', - 'to': 'world' + hass.states.async_set('test.entity_1', 'world_no') + hass.states.async_set('test.entity_2', 'world_no') + await hass.async_block_till_done() + hass.states.async_set('test.entity_1', 'world') + hass.states.async_set('test.entity_2', 'world') + await hass.async_block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 2 == len(calls) + + +async def test_if_fires_on_entity_change_with_for_attribute_change(hass, + calls): + """Test for firing on entity change with for and attribute change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'seconds': 5 }, - 'action': { - 'service': 'test.automation' - } + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) + utcnow = dt_util.utcnow() + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = utcnow + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + mock_utcnow.return_value += timedelta(seconds=4) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set('test.entity', 'world', + attributes={"mock_attr": "attr_change"}) + await hass.async_block_till_done() + assert 0 == len(calls) + mock_utcnow.return_value += timedelta(seconds=4) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_not_fires_if_to_filter_not_match(self): - """Test for not firing if to filter is not a match.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'from': 'hello', - 'to': 'world' + +async def test_if_fires_on_entity_change_with_for_multiple_force_update(hass, + calls): + """Test for firing on entity change with for and force update.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.force_entity', + 'to': 'world', + 'for': { + 'seconds': 5 }, - 'action': { - 'service': 'test.automation' - } + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'moon') - self.hass.block_till_done() - assert 0 == len(self.calls) + utcnow = dt_util.utcnow() + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = utcnow + hass.states.async_set('test.force_entity', 'world', None, True) + await hass.async_block_till_done() + for _ in range(0, 4): + mock_utcnow.return_value += timedelta(seconds=1) + async_fire_time_changed(hass, mock_utcnow.return_value) + hass.states.async_set('test.force_entity', 'world', None, True) + await hass.async_block_till_done() + assert 0 == len(calls) + mock_utcnow.return_value += timedelta(seconds=4) + async_fire_time_changed(hass, mock_utcnow.return_value) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_not_fires_if_from_filter_not_match(self): - """Test for not firing if from filter is not a match.""" - self.hass.states.set('test.entity', 'bye') - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'from': 'hello', - 'to': 'world' +async def test_if_fires_on_entity_change_with_for(hass, calls): + """Test for firing on entity change with for.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'seconds': 5 }, - 'action': { - 'service': 'test.automation' - } + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_not_fires_if_entity_not_match(self): - """Test for not firing if entity is not matching.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.another_entity', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_action(self): - """Test for to action.""" - entity_id = 'domain.test_entity' - test_state = 'new_state' - assert setup_component(self.hass, automation.DOMAIN, { +async def test_if_fires_on_for_condition(hass, calls): + """Test for firing if condition is on.""" + point1 = dt_util.utcnow() + point2 = point1 + timedelta(seconds=10) + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = point1 + hass.states.async_set('test.entity', 'on') + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', 'event_type': 'test_event', }, - 'condition': [{ + 'condition': { 'condition': 'state', - 'entity_id': entity_id, - 'state': test_state - }], - 'action': { - 'service': 'test.automation' - } + 'entity_id': 'test.entity', + 'state': 'on', + 'for': { + 'seconds': 5 + }, + }, + 'action': {'service': 'test.automation'}, } }) - self.hass.states.set(entity_id, test_state) - self.hass.bus.fire('test_event') - self.hass.block_till_done() + # not enough time has passed + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) - assert 1 == len(self.calls) + # Time travel 10 secs into the future + mock_utcnow.return_value = point2 + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - self.hass.states.set(entity_id, test_state + 'something') - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fails_setup_if_to_boolean_value(self): - """Test for setup failure for boolean to.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': True, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_fails_setup_if_from_boolean_value(self): - """Test for setup failure for boolean from.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'from': True, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_fails_setup_bad_for(self): - """Test for setup failure for bad for.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world', - 'for': { - 'invalid': 5 - }, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_fails_setup_for_without_to(self): - """Test for setup failures for missing to.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'homeassistant.turn_on', - } - }}) - - def test_if_not_fires_on_entity_change_with_for(self): - """Test for not firing on entity change with for.""" - assert setup_component(self.hass, automation.DOMAIN, { +async def test_if_fires_on_for_condition_attribute_change(hass, calls): + """Test for firing if condition is on with attribute change.""" + point1 = dt_util.utcnow() + point2 = point1 + timedelta(seconds=4) + point3 = point1 + timedelta(seconds=8) + with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: + mock_utcnow.return_value = point1 + hass.states.async_set('test.entity', 'on') + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'state', + 'entity_id': 'test.entity', + 'state': 'on', + 'for': { + 'seconds': 5 + }, + }, + 'action': {'service': 'test.automation'}, + } + }) + + # not enough time has passed + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + # Still not enough time has passed, but an attribute is changed + mock_utcnow.return_value = point2 + hass.states.async_set('test.entity', 'on', + attributes={"mock_attr": "attr_change"}) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + # Enough time has now passed + mock_utcnow.return_value = point3 + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fails_setup_for_without_time(hass, calls): + """Test for setup failure if no time is provided.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'bla' + }, + 'condition': { 'platform': 'state', 'entity_id': 'test.entity', - 'to': 'world', + 'state': 'on', + 'for': {}, + }, + 'action': {'service': 'test.automation'}, + }}) + + +async def test_if_fails_setup_for_without_entity(hass, calls): + """Test for setup failure if no entity is provided.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': {'event_type': 'bla'}, + 'condition': { + 'platform': 'state', + 'state': 'on', 'for': { 'seconds': 5 }, }, - 'action': { - 'service': 'test.automation' - } - } - }) + 'action': {'service': 'test.automation'}, + }}) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - self.hass.states.set('test.entity', 'not_world') - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 0 == len(self.calls) - def test_if_not_fires_on_entities_change_with_for_after_stop(self): - """Test for not firing on entity change with for after stop trigger.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': [ - 'test.entity_1', - 'test.entity_2', - ], - 'to': 'world', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) +async def test_wait_template_with_trigger(hass, calls): + """Test using wait template with 'trigger.entity_id'.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + }, + 'action': [ + {'wait_template': + "{{ is_state(trigger.entity_id, 'hello') }}"}, + {'service': 'test.automation', + 'data_template': { + 'some': + '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', 'from_state.state', + 'to_state.state')) + }} + ], + } + }) - self.hass.states.set('test.entity_1', 'world') - self.hass.states.set('test.entity_2', 'world') - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 2 == len(self.calls) + await hass.async_block_till_done() - self.hass.states.set('test.entity_1', 'world_no') - self.hass.states.set('test.entity_2', 'world_no') - self.hass.block_till_done() - self.hass.states.set('test.entity_1', 'world') - self.hass.states.set('test.entity_2', 'world') - self.hass.block_till_done() - common.turn_off(self.hass) - self.hass.block_till_done() - - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 2 == len(self.calls) - - def test_if_fires_on_entity_change_with_for_attribute_change(self): - """Test for firing on entity change with for and attribute change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - utcnow = dt_util.utcnow() - with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: - mock_utcnow.return_value = utcnow - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - mock_utcnow.return_value += timedelta(seconds=4) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.states.set('test.entity', 'world', - attributes={"mock_attr": "attr_change"}) - self.hass.block_till_done() - assert 0 == len(self.calls) - mock_utcnow.return_value += timedelta(seconds=4) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_with_for_multiple_force_update(self): - """Test for firing on entity change with for and force update.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.force_entity', - 'to': 'world', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - utcnow = dt_util.utcnow() - with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: - mock_utcnow.return_value = utcnow - self.hass.states.set('test.force_entity', 'world', None, True) - self.hass.block_till_done() - for _ in range(0, 4): - mock_utcnow.return_value += timedelta(seconds=1) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.states.set('test.force_entity', 'world', None, True) - self.hass.block_till_done() - assert 0 == len(self.calls) - mock_utcnow.return_value += timedelta(seconds=4) - fire_time_changed(self.hass, mock_utcnow.return_value) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_entity_change_with_for(self): - """Test for firing on entity change with for.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world', - 'for': { - 'seconds': 5 - }, - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_for_condition(self): - """Test for firing if condition is on.""" - point1 = dt_util.utcnow() - point2 = point1 + timedelta(seconds=10) - with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: - mock_utcnow.return_value = point1 - self.hass.states.set('test.entity', 'on') - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'state', - 'entity_id': 'test.entity', - 'state': 'on', - 'for': { - 'seconds': 5 - }, - }, - 'action': {'service': 'test.automation'}, - } - }) - - # not enough time has passed - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Time travel 10 secs into the future - mock_utcnow.return_value = point2 - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_for_condition_attribute_change(self): - """Test for firing if condition is on with attribute change.""" - point1 = dt_util.utcnow() - point2 = point1 + timedelta(seconds=4) - point3 = point1 + timedelta(seconds=8) - with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: - mock_utcnow.return_value = point1 - self.hass.states.set('test.entity', 'on') - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'state', - 'entity_id': 'test.entity', - 'state': 'on', - 'for': { - 'seconds': 5 - }, - }, - 'action': {'service': 'test.automation'}, - } - }) - - # not enough time has passed - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Still not enough time has passed, but an attribute is changed - mock_utcnow.return_value = point2 - self.hass.states.set('test.entity', 'on', - attributes={"mock_attr": "attr_change"}) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Enough time has now passed - mock_utcnow.return_value = point3 - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fails_setup_for_without_time(self): - """Test for setup failure if no time is provided.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'bla' - }, - 'condition': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'state': 'on', - 'for': {}, - }, - 'action': {'service': 'test.automation'}, - }}) - - def test_if_fails_setup_for_without_entity(self): - """Test for setup failure if no entity is provided.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': {'event_type': 'bla'}, - 'condition': { - 'platform': 'state', - 'state': 'on', - 'for': { - 'seconds': 5 - }, - }, - 'action': {'service': 'test.automation'}, - }}) - - def test_wait_template_with_trigger(self): - """Test using wait template with 'trigger.entity_id'.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world', - }, - 'action': [ - {'wait_template': - "{{ is_state(trigger.entity_id, 'hello') }}"}, - {'service': 'test.automation', - 'data_template': { - 'some': - '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', 'from_state.state', - 'to_state.state')) - }} - ], - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'state - test.entity - hello - world' == \ - self.calls[0].data['some'] + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'state - test.entity - hello - world' == \ + calls[0].data['some'] diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 3eb30b5594e..dce7933a759 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -1,322 +1,319 @@ """The tests for the sun automation.""" from datetime import datetime -import unittest +import pytest from unittest.mock import patch -from homeassistant.core import callback -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.components import sun import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util from tests.common import ( - fire_time_changed, get_test_home_assistant, mock_component) + async_fire_time_changed, mock_component, async_mock_service) from tests.components.automation import common -# pylint: disable=invalid-name -class TestAutomationSun(unittest.TestCase): - """Test the sun automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) - self.calls = [] +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.loop.run_until_complete(async_setup_component(hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}})) - @callback - def record_call(service): - """Call recorder.""" - self.calls.append(service) - self.hass.services.register('test', 'automation', record_call) +async def test_sunset_trigger(hass, calls): + """Test the sunset trigger.""" + now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) + trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() + with patch('homeassistant.util.dt.utcnow', + return_value=now): + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + }, + 'action': { + 'service': 'test.automation', + } + } + }) - def test_sunset_trigger(self): - """Test the sunset trigger.""" - now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) - trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC) + await common.async_turn_off(hass) + await hass.async_block_till_done() - with patch('homeassistant.util.dt.utcnow', - return_value=now): - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'sun', - 'event': 'sunset', + async_fire_time_changed(hass, trigger_time) + await hass.async_block_till_done() + assert 0 == len(calls) + + with patch('homeassistant.util.dt.utcnow', + return_value=now): + await common.async_turn_on(hass) + await hass.async_block_till_done() + + async_fire_time_changed(hass, trigger_time) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_sunrise_trigger(hass, calls): + """Test the sunrise trigger.""" + now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) + trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC) + + with patch('homeassistant.util.dt.utcnow', + return_value=now): + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunrise', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + async_fire_time_changed(hass, trigger_time) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_sunset_trigger_with_offset(hass, calls): + """Test the sunset trigger with offset.""" + now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) + trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC) + + with patch('homeassistant.util.dt.utcnow', + return_value=now): + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + 'offset': '0:30:00' + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': + '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'event', 'offset')) }, - 'action': { - 'service': 'test.automation', - } - } - }) - - common.turn_off(self.hass) - self.hass.block_till_done() - - fire_time_changed(self.hass, trigger_time) - self.hass.block_till_done() - assert 0 == len(self.calls) - - with patch('homeassistant.util.dt.utcnow', - return_value=now): - common.turn_on(self.hass) - self.hass.block_till_done() - - fire_time_changed(self.hass, trigger_time) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_sunrise_trigger(self): - """Test the sunrise trigger.""" - now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) - trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC) - - with patch('homeassistant.util.dt.utcnow', - return_value=now): - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'sun', - 'event': 'sunrise', - }, - 'action': { - 'service': 'test.automation', - } - } - }) - - fire_time_changed(self.hass, trigger_time) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_sunset_trigger_with_offset(self): - """Test the sunset trigger with offset.""" - now = datetime(2015, 9, 15, 23, tzinfo=dt_util.UTC) - trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC) - - with patch('homeassistant.util.dt.utcnow', - return_value=now): - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'sun', - 'event': 'sunset', - 'offset': '0:30:00' - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': - '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'event', 'offset')) - }, - } - } - }) - - fire_time_changed(self.hass, trigger_time) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'sun - sunset - 0:30:00' == self.calls[0].data['some'] - - def test_sunrise_trigger_with_offset(self): - """Test the sunrise trigger with offset.""" - now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) - trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC) - - with patch('homeassistant.util.dt.utcnow', - return_value=now): - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'sun', - 'event': 'sunrise', - 'offset': '-0:30:00' - }, - 'action': { - 'service': 'test.automation', - } - } - }) - - fire_time_changed(self.hass, trigger_time) - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_action_before(self): - """Test if action was before.""" - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'before': 'sunrise', - }, - 'action': { - 'service': 'test.automation' } } }) - now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + async_fire_time_changed(hass, trigger_time) + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'sun - sunset - 0:30:00' == calls[0].data['some'] - now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - def test_if_action_after(self): - """Test if action was after.""" - setup_component(self.hass, automation.DOMAIN, { +async def test_sunrise_trigger_with_offset(hass, calls): + """Test the sunrise trigger with offset.""" + now = datetime(2015, 9, 13, 23, tzinfo=dt_util.UTC) + trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC) + + with patch('homeassistant.util.dt.utcnow', + return_value=now): + await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'after': 'sunrise', + 'platform': 'sun', + 'event': 'sunrise', + 'offset': '-0:30:00' }, 'action': { - 'service': 'test.automation' + 'service': 'test.automation', } } }) - now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + async_fire_time_changed(hass, trigger_time) + await hass.async_block_till_done() + assert 1 == len(calls) - now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - def test_if_action_before_with_offset(self): - """Test if action was before offset.""" - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'before': 'sunrise', - 'before_offset': '+1:00:00' - }, - 'action': { - 'service': 'test.automation' - } +async def test_if_action_before(hass, calls): + """Test if action was before.""" + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'sun', + 'before': 'sunrise', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - now = datetime(2015, 9, 16, 14, 32, 44, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) - now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_action_after_with_offset(self): - """Test if action was after offset.""" - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'after': 'sunrise', - 'after_offset': '+1:00:00' - }, - 'action': { - 'service': 'test.automation' - } + +async def test_if_action_after(hass, calls): + """Test if action was after.""" + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'sun', + 'after': 'sunrise', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - now = datetime(2015, 9, 16, 14, 32, 42, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + now = datetime(2015, 9, 16, 13, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) - now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - def test_if_action_before_and_after_during(self): - """Test if action was before and after during.""" - setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': { - 'condition': 'sun', - 'after': 'sunrise', - 'before': 'sunset' - }, - 'action': { - 'service': 'test.automation' - } + +async def test_if_action_before_with_offset(hass, calls): + """Test if action was before offset.""" + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'sun', + 'before': 'sunrise', + 'before_offset': '+1:00:00' + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - now = datetime(2015, 9, 16, 13, 8, 51, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + now = datetime(2015, 9, 16, 14, 32, 44, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) - now = datetime(2015, 9, 17, 2, 25, 18, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) + now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) - now = datetime(2015, 9, 16, 16, tzinfo=dt_util.UTC) - with patch('homeassistant.util.dt.utcnow', - return_value=now): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + +async def test_if_action_after_with_offset(hass, calls): + """Test if action was after offset.""" + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'sun', + 'after': 'sunrise', + 'after_offset': '+1:00:00' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + now = datetime(2015, 9, 16, 14, 32, 42, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_action_before_and_after_during(hass, calls): + """Test if action was before and after during.""" + await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'condition': 'sun', + 'after': 'sunrise', + 'before': 'sunset' + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + now = datetime(2015, 9, 16, 13, 8, 51, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + now = datetime(2015, 9, 17, 2, 25, 18, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + now = datetime(2015, 9, 16, 16, tzinfo=dt_util.UTC) + with patch('homeassistant.util.dt.utcnow', + return_value=now): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 9945677c123..d9ad765db3f 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -1,44 +1,380 @@ """The tests for the Template automation.""" -import unittest +import pytest -from homeassistant.core import Context, callback -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component import homeassistant.components.automation as automation -from tests.common import ( - get_test_home_assistant, assert_setup_component, mock_component) +from tests.common import (assert_setup_component, mock_component) from tests.components.automation import common +from tests.common import async_mock_service -# pylint: disable=invalid-name -class TestAutomationTemplate(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - self.hass.states.set('test.entity', 'hello') - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.states.async_set('test.entity', 'hello') - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() +async def test_if_fires_on_change_bool(hass, calls): + """Test for firing on boolean change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ true }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) - def test_if_fires_on_change_bool(self): - """Test for firing on boolean change.""" - assert setup_component(self.hass, automation.DOMAIN, { + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + await common.async_turn_off(hass) + await hass.async_block_till_done() + + hass.states.async_set('test.entity', 'planet') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_change_str(hass, calls): + """Test for firing on change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': 'true', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_change_str_crazy(hass, calls): + """Test for firing on change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': 'TrUE', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_on_change_bool(hass, calls): + """Test for not firing on boolean change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ false }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_on_change_str(hass, calls): + """Test for not firing on string change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': 'False', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_not_fires_on_change_str_crazy(hass, calls): + """Test for not firing on string change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': 'Anything other than "true" is false.', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_no_change(hass, calls): + """Test for firing on no change.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ true }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + await hass.async_block_till_done() + cur_len = len(calls) + + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() + assert cur_len == len(calls) + + +async def test_if_fires_on_two_change(hass, calls): + """Test for firing on two changes.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ true }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # Trigger once + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + # Trigger again + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_change_with_template(hass, calls): + """Test for firing on change with template.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ is_state("test.entity", "world") }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_not_fires_on_change_with_template(hass, calls): + """Test for not firing on change with template.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ is_state("test.entity", "hello") }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + await hass.async_block_till_done() + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_if_fires_on_change_with_template_advanced(hass, calls): + """Test for firing on change with template advanced.""" + context = Context() + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ is_state("test.entity", "world") }}' + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': + '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', 'from_state.state', + 'to_state.state')) + }, + } + } + }) + + await hass.async_block_till_done() + + hass.states.async_set('test.entity', 'world', context=context) + await hass.async_block_till_done() + assert 1 == len(calls) + assert calls[0].context is context + assert 'template - test.entity - hello - world' == \ + calls[0].data['some'] + + +async def test_if_fires_on_no_change_with_template_advanced(hass, calls): + """Test for firing on no change with template advanced.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '''{%- if is_state("test.entity", "world") -%} + true + {%- else -%} + false + {%- endif -%}''', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + # Different state + hass.states.async_set('test.entity', 'worldz') + await hass.async_block_till_done() + assert 0 == len(calls) + + # Different state + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_if_fires_on_change_with_template_2(hass, calls): + """Test for firing on change with template.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': + '{{ not is_state("test.entity", "world") }}', + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + await hass.async_block_till_done() + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert len(calls) == 0 + + hass.states.async_set('test.entity', 'home') + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set('test.entity', 'work') + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set('test.entity', 'not_home') + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.states.async_set('test.entity', 'home') + await hass.async_block_till_done() + assert len(calls) == 2 + + +async def test_if_action(hass, calls): + """Test for firing if action.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': [{ + 'condition': 'template', + 'value_template': '{{ is_state("test.entity", "world") }}' + }], + 'action': { + 'service': 'test.automation' + } + } + }) + + # Condition is not true yet + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 0 == len(calls) + + # Change condition to true, but it shouldn't be triggered yet + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) + + # Condition is true and event is triggered + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_on_change_with_bad_template(hass, calls): + """Test for firing on change with bad template.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': '{{ true }}', + 'value_template': '{{ ', }, 'action': { 'service': 'test.automation' @@ -46,388 +382,55 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) - common.turn_off(self.hass) - self.hass.block_till_done() - - self.hass.states.set('test.entity', 'planet') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_change_str(self): - """Test for firing on change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': 'true', - }, - 'action': { - 'service': 'test.automation' - } +async def test_if_fires_on_change_with_bad_template_2(hass, calls): + """Test for firing on change with bad template.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': '{{ xyz | round(0) }}', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + assert 0 == len(calls) - def test_if_fires_on_change_str_crazy(self): - """Test for firing on change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': 'TrUE', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) +async def test_wait_template_with_trigger(hass, calls): + """Test using wait template with 'trigger.entity_id'.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'template', + 'value_template': + "{{ states.test.entity.state == 'world' }}", + }, + 'action': [ + {'wait_template': + "{{ is_state(trigger.entity_id, 'hello') }}"}, + {'service': 'test.automation', + 'data_template': { + 'some': + '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', 'from_state.state', + 'to_state.state')) + }} + ], + } + }) - def test_if_not_fires_on_change_bool(self): - """Test for not firing on boolean change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ false }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) + await hass.async_block_till_done() - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_on_change_str(self): - """Test for not firing on string change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': 'False', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_on_change_str_crazy(self): - """Test for not firing on string change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': 'Anything other than "true" is false.', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_no_change(self): - """Test for firing on no change.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ true }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_two_change(self): - """Test for firing on two changes.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ true }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # Trigger once - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) - - # Trigger again - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_change_with_template(self): - """Test for firing on change with template.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ is_state("test.entity", "world") }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_not_fires_on_change_with_template(self): - """Test for not firing on change with template.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ is_state("test.entity", "hello") }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert len(self.calls) == 0 - - def test_if_fires_on_change_with_template_advanced(self): - """Test for firing on change with template advanced.""" - context = Context() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ is_state("test.entity", "world") }}' - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': - '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', 'from_state.state', - 'to_state.state')) - }, - } - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'world', context=context) - self.hass.block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context - assert 'template - test.entity - hello - world' == \ - self.calls[0].data['some'] - - def test_if_fires_on_no_change_with_template_advanced(self): - """Test for firing on no change with template advanced.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '''{%- if is_state("test.entity", "world") -%} - true - {%- else -%} - false - {%- endif -%}''', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - # Different state - self.hass.states.set('test.entity', 'worldz') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Different state - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_fires_on_change_with_template_2(self): - """Test for firing on change with template.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': - '{{ not is_state("test.entity", "world") }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert len(self.calls) == 0 - - self.hass.states.set('test.entity', 'home') - self.hass.block_till_done() - assert len(self.calls) == 1 - - self.hass.states.set('test.entity', 'work') - self.hass.block_till_done() - assert len(self.calls) == 1 - - self.hass.states.set('test.entity', 'not_home') - self.hass.block_till_done() - assert len(self.calls) == 1 - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert len(self.calls) == 1 - - self.hass.states.set('test.entity', 'home') - self.hass.block_till_done() - assert len(self.calls) == 2 - - def test_if_action(self): - """Test for firing if action.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'condition': [{ - 'condition': 'template', - 'value_template': '{{ is_state("test.entity", "world") }}' - }], - 'action': { - 'service': 'test.automation' - } - } - }) - - # Condition is not true yet - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Change condition to true, but it shouldn't be triggered yet - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - # Condition is true and event is triggered - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) - - def test_if_fires_on_change_with_bad_template(self): - """Test for firing on change with bad template.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ ', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - def test_if_fires_on_change_with_bad_template_2(self): - """Test for firing on change with bad template.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': '{{ xyz | round(0) }}', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_wait_template_with_trigger(self): - """Test using wait template with 'trigger.entity_id'.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'template', - 'value_template': - "{{ states.test.entity.state == 'world' }}", - }, - 'action': [ - {'wait_template': - "{{ is_state(trigger.entity_id, 'hello') }}"}, - {'service': 'test.automation', - 'data_template': { - 'some': - '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', 'from_state.state', - 'to_state.state')) - }} - ], - } - }) - - self.hass.block_till_done() - self.calls = [] - - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'template - test.entity - hello - world' == \ - self.calls[0].data['some'] + hass.states.async_set('test.entity', 'world') + await hass.async_block_till_done() + hass.states.async_set('test.entity', 'hello') + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'template - test.entity - hello - world' == \ + calls[0].data['some'] diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index d1d9bcaecdf..11387f25889 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -1,47 +1,216 @@ """The tests for the time automation.""" from datetime import timedelta -import unittest from unittest.mock import patch -from homeassistant.core import callback -from homeassistant.setup import setup_component +import pytest + +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation from tests.common import ( - fire_time_changed, get_test_home_assistant, assert_setup_component, - mock_component) + async_fire_time_changed, assert_setup_component, mock_component) from tests.components.automation import common +from tests.common import async_mock_service -# pylint: disable=invalid-name -class TestAutomationTime(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') - self.hass.services.register('test', 'automation', record_call) - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() +async def test_if_fires_when_hour_matches(hass, calls): + """Test for firing if hour is matching.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'hours': 0, + }, + 'action': { + 'service': 'test.automation' + } + } + }) - def test_if_fires_when_hour_matches(self): - """Test for firing if hour is matching.""" - assert setup_component(self.hass, automation.DOMAIN, { + async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0)) + await hass.async_block_till_done() + assert 1 == len(calls) + + await common.async_turn_off(hass) + await hass.async_block_till_done() + + async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0)) + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_when_minute_matches(hass, calls): + """Test for firing if minutes are matching.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'minutes': 0, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace(minute=0)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_when_second_matches(hass, calls): + """Test for firing if seconds are matching.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'seconds': 0, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace(second=0)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_when_all_matches(hass, calls): + """Test for firing if everything matches.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'hours': 1, + 'minutes': 2, + 'seconds': 3, + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=1, minute=2, second=3)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_periodic_seconds(hass, calls): + """Test for firing periodically every second.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'seconds': "/2", + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=0, minute=0, second=2)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_periodic_minutes(hass, calls): + """Test for firing periodically every minute.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'minutes': "/2", + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=0, minute=2, second=0)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_periodic_hours(hass, calls): + """Test for firing periodically every hour.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'hours': "/2", + }, + 'action': { + 'service': 'test.automation' + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=2, minute=0, second=0)) + + await hass.async_block_till_done() + assert 1 == len(calls) + + +async def test_if_fires_using_at(hass, calls): + """Test for firing at.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'at': '5:00:00', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.platform }} - ' + '{{ trigger.now.hour }}' + }, + } + } + }) + + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=5, minute=0, second=0)) + + await hass.async_block_till_done() + assert 1 == len(calls) + assert 'time - 5' == calls[0].data['some'] + + +async def test_if_not_working_if_no_values_in_conf_provided(hass, calls): + """Test for failure if no configuration.""" + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', - 'hours': 0, }, 'action': { 'service': 'test.automation' @@ -49,24 +218,25 @@ class TestAutomationTime(unittest.TestCase): } }) - fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) - self.hass.block_till_done() - assert 1 == len(self.calls) + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=5, minute=0, second=0)) - common.turn_off(self.hass) - self.hass.block_till_done() + await hass.async_block_till_done() + assert 0 == len(calls) - fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) - self.hass.block_till_done() - assert 1 == len(self.calls) - def test_if_fires_when_minute_matches(self): - """Test for firing if minutes are matching.""" - assert setup_component(self.hass, automation.DOMAIN, { +async def test_if_not_fires_using_wrong_at(hass, calls): + """YAML translates time values to total seconds. + + This should break the before rule. + """ + with assert_setup_component(0): + assert await async_setup_component(hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', - 'minutes': 0, + 'at': 3605, + # Total seconds. Hour = 3600 second }, 'action': { 'service': 'test.automation' @@ -74,328 +244,162 @@ class TestAutomationTime(unittest.TestCase): } }) - fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) + async_fire_time_changed(hass, dt_util.utcnow().replace( + hour=1, minute=0, second=5)) - self.hass.block_till_done() - assert 1 == len(self.calls) + await hass.async_block_till_done() + assert 0 == len(calls) - def test_if_fires_when_second_matches(self): - """Test for firing if seconds are matching.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'seconds': 0, - }, - 'action': { - 'service': 'test.automation' - } + +async def test_if_action_before(hass, calls): + """Test for if action before.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'condition': 'time', + 'before': '10:00', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) + before_10 = dt_util.now().replace(hour=8) + after_10 = dt_util.now().replace(hour=14) - self.hass.block_till_done() - assert 1 == len(self.calls) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=before_10): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - def test_if_fires_when_all_matches(self): - """Test for firing if everything matches.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'hours': 1, - 'minutes': 2, - 'seconds': 3, - }, - 'action': { - 'service': 'test.automation' - } + assert 1 == len(calls) + + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=after_10): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + +async def test_if_action_after(hass, calls): + """Test for if action after.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'condition': 'time', + 'after': '10:00', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=1, minute=2, second=3)) + before_10 = dt_util.now().replace(hour=8) + after_10 = dt_util.now().replace(hour=14) - self.hass.block_till_done() - assert 1 == len(self.calls) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=before_10): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - def test_if_fires_periodic_seconds(self): - """Test for firing periodically every second.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'seconds': "/2", - }, - 'action': { - 'service': 'test.automation' - } + assert 0 == len(calls) + + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=after_10): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + +async def test_if_action_one_weekday(hass, calls): + """Test for if action with one weekday.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'condition': 'time', + 'weekday': 'mon', + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=0, minute=0, second=2)) + days_past_monday = dt_util.now().weekday() + monday = dt_util.now() - timedelta(days=days_past_monday) + tuesday = monday + timedelta(days=1) - self.hass.block_till_done() - assert 1 == len(self.calls) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=monday): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - def test_if_fires_periodic_minutes(self): - """Test for firing periodically every minute.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'minutes': "/2", - }, - 'action': { - 'service': 'test.automation' - } + assert 1 == len(calls) + + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=tuesday): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + + assert 1 == len(calls) + + +async def test_if_action_list_weekday(hass, calls): + """Test for action with a list of weekdays.""" + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'condition': 'time', + 'weekday': ['mon', 'tue'], + }, + 'action': { + 'service': 'test.automation' } - }) + } + }) - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=0, minute=2, second=0)) + days_past_monday = dt_util.now().weekday() + monday = dt_util.now() - timedelta(days=days_past_monday) + tuesday = monday + timedelta(days=1) + wednesday = tuesday + timedelta(days=1) - self.hass.block_till_done() - assert 1 == len(self.calls) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=monday): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - def test_if_fires_periodic_hours(self): - """Test for firing periodically every hour.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'hours': "/2", - }, - 'action': { - 'service': 'test.automation' - } - } - }) + assert 1 == len(calls) - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=2, minute=0, second=0)) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=tuesday): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - self.hass.block_till_done() - assert 1 == len(self.calls) + assert 2 == len(calls) - def test_if_fires_using_at(self): - """Test for firing at.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'at': '5:00:00', - }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.platform }} - ' - '{{ trigger.now.hour }}' - }, - } - } - }) + with patch('homeassistant.helpers.condition.dt_util.now', + return_value=wednesday): + hass.bus.async_fire('test_event') + await hass.async_block_till_done() - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=5, minute=0, second=0)) - - self.hass.block_till_done() - assert 1 == len(self.calls) - assert 'time - 5' == self.calls[0].data['some'] - - def test_if_not_working_if_no_values_in_conf_provided(self): - """Test for failure if no configuration.""" - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=5, minute=0, second=0)) - - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_not_fires_using_wrong_at(self): - """YAML translates time values to total seconds. - - This should break the before rule. - """ - with assert_setup_component(0): - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'time', - 'at': 3605, - # Total seconds. Hour = 3600 second - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - fire_time_changed(self.hass, dt_util.utcnow().replace( - hour=1, minute=0, second=5)) - - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_if_action_before(self): - """Test for if action before.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event' - }, - 'condition': { - 'condition': 'time', - 'before': '10:00', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - before_10 = dt_util.now().replace(hour=8) - after_10 = dt_util.now().replace(hour=14) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=before_10): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=after_10): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - def test_if_action_after(self): - """Test for if action after.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event' - }, - 'condition': { - 'condition': 'time', - 'after': '10:00', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - before_10 = dt_util.now().replace(hour=8) - after_10 = dt_util.now().replace(hour=14) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=before_10): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 0 == len(self.calls) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=after_10): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - def test_if_action_one_weekday(self): - """Test for if action with one weekday.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event' - }, - 'condition': { - 'condition': 'time', - 'weekday': 'mon', - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - days_past_monday = dt_util.now().weekday() - monday = dt_util.now() - timedelta(days=days_past_monday) - tuesday = monday + timedelta(days=1) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=monday): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=tuesday): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - def test_if_action_list_weekday(self): - """Test for action with a list of weekdays.""" - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event' - }, - 'condition': { - 'condition': 'time', - 'weekday': ['mon', 'tue'], - }, - 'action': { - 'service': 'test.automation' - } - } - }) - - days_past_monday = dt_util.now().weekday() - monday = dt_util.now() - timedelta(days=days_past_monday) - tuesday = monday + timedelta(days=1) - wednesday = tuesday + timedelta(days=1) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=monday): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 1 == len(self.calls) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=tuesday): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 2 == len(self.calls) - - with patch('homeassistant.helpers.condition.dt_util.now', - return_value=wednesday): - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert 2 == len(self.calls) + assert 2 == len(calls) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index d0ab4d726ab..04ffeaf13aa 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,218 +1,212 @@ """The tests for the location automation.""" -import unittest +import pytest -from homeassistant.core import Context, callback -from homeassistant.setup import setup_component +from homeassistant.core import Context +from homeassistant.setup import async_setup_component from homeassistant.components import automation, zone -from tests.common import get_test_home_assistant, mock_component from tests.components.automation import common +from tests.common import async_mock_service, mock_component -# pylint: disable=invalid-name -class TestAutomationZone(unittest.TestCase): - """Test the event automation.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') - assert setup_component(self.hass, zone.DOMAIN, { + +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + mock_component(hass, 'group') + hass.loop.run_until_complete(async_setup_component(hass, zone.DOMAIN, { 'zone': { 'name': 'test', 'latitude': 32.880837, 'longitude': -117.237561, 'radius': 250, } - }) + })) - self.calls = [] - @callback - def record_call(service): - """Record calls.""" - self.calls.append(service) +async def test_if_fires_on_zone_enter(hass, calls): + """Test for firing on zone enter.""" + context = Context() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() - self.hass.services.register('test', 'automation', record_call) - - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_if_fires_on_zone_enter(self): - """Test for firing on zone enter.""" - context = Context() - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'zone', - 'entity_id': 'test.entity', - 'zone': 'zone.test', - 'event': 'enter', + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'zone', + 'entity_id': 'test.entity', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', + 'data_template': { + 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( + 'platform', 'entity_id', + 'from_state.state', 'to_state.state', + 'zone.name')) }, - 'action': { - 'service': 'test.automation', - 'data_template': { - 'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join(( - 'platform', 'entity_id', - 'from_state.state', 'to_state.state', - 'zone.name')) - }, - } } - }) + } + }) - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }, context=context) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }, context=context) + await hass.async_block_till_done() - assert 1 == len(self.calls) - assert self.calls[0].context is context - assert 'zone - test.entity - hello - hello - test' == \ - self.calls[0].data['some'] + assert 1 == len(calls) + assert calls[0].context is context + assert 'zone - test.entity - hello - hello - test' == \ + calls[0].data['some'] - # Set out of zone again so we can trigger call - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() + # Set out of zone again so we can trigger call + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() - common.turn_off(self.hass) - self.hass.block_till_done() + await common.async_turn_off(hass) + await hass.async_block_till_done() - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() - assert 1 == len(self.calls) + assert 1 == len(calls) - def test_if_not_fires_for_enter_on_zone_leave(self): - """Test for not firing on zone leave.""" - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'zone', - 'entity_id': 'test.entity', - 'zone': 'zone.test', - 'event': 'enter', - }, - 'action': { - 'service': 'test.automation', - } +async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): + """Test for not firing on zone leave.""" + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'zone', + 'entity_id': 'test.entity', + 'zone': 'zone.test', + 'event': 'enter', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() - assert 0 == len(self.calls) + assert 0 == len(calls) - def test_if_fires_on_zone_leave(self): - """Test for firing on zone leave.""" - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'zone', - 'entity_id': 'test.entity', - 'zone': 'zone.test', - 'event': 'leave', - }, - 'action': { - 'service': 'test.automation', - } +async def test_if_fires_on_zone_leave(hass, calls): + """Test for firing on zone leave.""" + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'zone', + 'entity_id': 'test.entity', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() - assert 1 == len(self.calls) + assert 1 == len(calls) - def test_if_not_fires_for_leave_on_zone_enter(self): - """Test for not firing on zone enter.""" - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.881011, - 'longitude': -117.234758 - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'zone', - 'entity_id': 'test.entity', - 'zone': 'zone.test', - 'event': 'leave', - }, - 'action': { - 'service': 'test.automation', - } +async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): + """Test for not firing on zone enter.""" + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'zone', + 'entity_id': 'test.entity', + 'zone': 'zone.test', + 'event': 'leave', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() - assert 0 == len(self.calls) + assert 0 == len(calls) - def test_zone_condition(self): - """Test for zone condition.""" - self.hass.states.set('test.entity', 'hello', { - 'latitude': 32.880586, - 'longitude': -117.237564 - }) - self.hass.block_till_done() - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event' - }, - 'condition': { - 'condition': 'zone', - 'entity_id': 'test.entity', - 'zone': 'zone.test', - }, - 'action': { - 'service': 'test.automation', - } +async def test_zone_condition(hass, calls): + """Test for zone condition.""" + hass.states.async_set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + await hass.async_block_till_done() + + assert await async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'condition': 'zone', + 'entity_id': 'test.entity', + 'zone': 'zone.test', + }, + 'action': { + 'service': 'test.automation', } - }) + } + }) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - assert 1 == len(self.calls) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(calls) From cce8b1183f1e72d3fb233a15ed854d53b3b08408 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Oct 2018 12:55:44 +0200 Subject: [PATCH 068/230] frontend bump --- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index 39de689dfcb..6a9592118c9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -472,7 +472,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181024.0 +home-assistant-frontend==20181026.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 718d2475672..b49781f8bad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181024.0 +home-assistant-frontend==20181026.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From b7896491e305f276a600f23a06919183711b13bb Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 26 Oct 2018 12:56:14 +0200 Subject: [PATCH 069/230] Lovelace ws: add move command (#17806) * Check for unique ids + ids are strings * Add move command * Add test for move * lint * more lint * Address comments * Update test --- homeassistant/components/lovelace/__init__.py | 131 ++++++++++++++++-- tests/components/lovelace/test_init.py | 84 ++++++++++- 2 files changed, 205 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 141f3c98334..9102830e82f 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -27,6 +27,7 @@ WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' WS_TYPE_ADD_CARD = 'lovelace/config/card/add' +WS_TYPE_MOVE_CARD = 'lovelace/config/card/move' SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, @@ -57,6 +58,13 @@ SCHEMA_ADD_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ FORMAT_YAML), }) +SCHEMA_MOVE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_MOVE_CARD, + vol.Required('card_id'): str, + vol.Optional('new_position'): int, + vol.Optional('new_view_id'): str, +}) + class WriteError(HomeAssistantError): """Error writing the data.""" @@ -74,6 +82,10 @@ class UnsupportedYamlError(HomeAssistantError): """Unsupported YAML.""" +class DuplicateIdError(HomeAssistantError): + """Duplicate ID's.""" + + def save_yaml(fname: str, data: JSON_TYPE): """Save a YAML file.""" from ruamel.yaml import YAML @@ -134,19 +146,34 @@ def load_yaml(fname: str) -> JSON_TYPE: def load_config(fname: str) -> JSON_TYPE: """Load a YAML file and adds id to views and cards if not present.""" config = load_yaml(fname) - # Check if all views and cards have an id or else add one + # Check if all views and cards have a unique id or else add one updated = False + seen_card_ids = set() + seen_view_ids = set() index = 0 for view in config.get('views', []): - if 'id' not in view: + view_id = view.get('id') + if view_id is None: updated = True view.insert(0, 'id', index, comment="Automatically created id") + else: + if view_id in seen_view_ids: + raise DuplicateIdError( + 'ID `{}` has multiple occurances in views'.format(view_id)) + seen_view_ids.add(view_id) for card in view.get('cards', []): - if 'id' not in card: + card_id = card.get('id') + if card_id is None: updated = True card.insert(0, 'id', uuid.uuid4().hex, comment="Automatically created id") + else: + if card_id in seen_card_ids: + raise DuplicateIdError( + 'ID `{}` has multiple occurances in cards' + .format(card_id)) + seen_card_ids.add(card_id) index += 1 if updated: save_yaml(fname, config) @@ -187,7 +214,7 @@ def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\ config = load_yaml(fname) for view in config.get('views', []): for card in view.get('cards', []): - if card.get('id') != card_id: + if str(card.get('id')) != card_id: continue if data_format == FORMAT_YAML: return object_to_yaml(card) @@ -203,7 +230,7 @@ def update_card(fname: str, card_id: str, card_config: str, config = load_yaml(fname) for view in config.get('views', []): for card in view.get('cards', []): - if card.get('id') != card_id: + if str(card.get('id')) != card_id: continue if data_format == FORMAT_YAML: card_config = yaml_to_object(card_config) @@ -220,7 +247,7 @@ def add_card(fname: str, view_id: str, card_config: str, """Add a card to a view.""" config = load_yaml(fname) for view in config.get('views', []): - if view.get('id') != view_id: + if str(view.get('id')) != view_id: continue cards = view.get('cards', []) if data_format == FORMAT_YAML: @@ -236,6 +263,55 @@ def add_card(fname: str, view_id: str, card_config: str, "View with ID: {} was not found in {}.".format(view_id, fname)) +def move_card(fname: str, card_id: str, position: int = None): + """Move a card to a different position.""" + if position is None: + raise HomeAssistantError('Position is required if view is not\ + specified.') + config = load_yaml(fname) + for view in config.get('views', []): + for card in view.get('cards', []): + if str(card.get('id')) != card_id: + continue + cards = view.get('cards') + cards.insert(position, cards.pop(cards.index(card))) + save_yaml(fname, config) + return + + raise CardNotFoundError( + "Card with ID: {} was not found in {}.".format(card_id, fname)) + + +def move_card_view(fname: str, card_id: str, view_id: str, + position: int = None): + """Move a card to a different view.""" + config = load_yaml(fname) + for view in config.get('views', []): + if str(view.get('id')) == view_id: + destination = view.get('cards') + for card in view.get('cards'): + if str(card.get('id')) != card_id: + continue + origin = view.get('cards') + card_to_move = card + + if 'destination' not in locals(): + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + if 'card_to_move' not in locals(): + raise CardNotFoundError( + "Card with ID: {} was not found in {}.".format(card_id, fname)) + + origin.pop(origin.index(card_to_move)) + + if position is None: + destination.append(card_to_move) + else: + destination.insert(position, card_to_move) + + save_yaml(fname, config) + + async def async_setup(hass, config): """Set up the Lovelace commands.""" # Backwards compat. Added in 0.80. Remove after 0.85 @@ -259,6 +335,10 @@ async def async_setup(hass, config): WS_TYPE_ADD_CARD, websocket_lovelace_add_card, SCHEMA_ADD_CARD) + hass.components.websocket_api.async_register_command( + WS_TYPE_MOVE_CARD, websocket_lovelace_move_card, + SCHEMA_MOVE_CARD) + return True @@ -322,7 +402,7 @@ async def websocket_lovelace_update_card(hass, connection, msg): update_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML)) message = websocket_api.result_message( - msg['id'], True + msg['id'] ) except FileNotFoundError: error = ('file_not_found', @@ -350,7 +430,7 @@ async def websocket_lovelace_add_card(hass, connection, msg): msg['view_id'], msg['card_config'], msg.get('position'), msg.get('format', FORMAT_YAML)) message = websocket_api.result_message( - msg['id'], True + msg['id'] ) except FileNotFoundError: error = ('file_not_found', @@ -366,3 +446,38 @@ async def websocket_lovelace_add_card(hass, connection, msg): message = websocket_api.error_message(msg['id'], *error) connection.send_message(message) + + +@websocket_api.async_response +async def websocket_lovelace_move_card(hass, connection, msg): + """Move card to different position over websocket and save.""" + error = None + try: + if 'new_view_id' in msg: + await hass.async_add_executor_job( + move_card_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg['new_view_id'], msg.get('new_position')) + else: + await hass.async_add_executor_job( + move_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg.get('new_position')) + + message = websocket_api.result_message( + msg['id'] + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except ViewNotFoundError as err: + error = 'view_not_found', str(err) + except CardNotFoundError as err: + error = 'card_not_found', str(err) + except HomeAssistantError as err: + error = 'save_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index d9465d7a752..5e486e295fe 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -3,6 +3,7 @@ import os import unittest from unittest.mock import patch from tempfile import mkdtemp +import pytest from ruamel.yaml import YAML from homeassistant.exceptions import HomeAssistantError @@ -11,7 +12,6 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.components.lovelace import (load_yaml, save_yaml, load_config, UnsupportedYamlError) -import pytest TEST_YAML_A = """\ title: My Awesome Home @@ -59,6 +59,7 @@ views: cards: - id: test type: entities + title: Test card # Entities card will take a list of entities and show their state. - type: entities # Title of the entities card @@ -327,7 +328,7 @@ async def test_lovelace_get_card(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] - assert msg['result'] == 'id: test\ntype: entities\n' + assert msg['result'] == 'id: test\ntype: entities\ntitle: Test card\n' async def test_lovelace_get_card_not_found(hass, hass_ws_client): @@ -494,3 +495,82 @@ async def test_lovelace_add_card_position(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] + + +async def test_lovelace_move_card_position(hass, hass_ws_client): + """Test add_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/move', + 'card_id': 'test', + 'new_position': 2, + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 1, 'cards', 2, 'title'], + list_ok=True) == 'Test card' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_move_card_view(hass, hass_ws_client): + """Test add_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/move', + 'card_id': 'test', + 'new_view_id': 'example', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 0, 'cards', 2, 'title'], + list_ok=True) == 'Test card' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_move_card_view_position(hass, hass_ws_client): + """Test add_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/move', + 'card_id': 'test', + 'new_view_id': 'example', + 'new_position': 1, + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 0, 'cards', 1, 'title'], + list_ok=True) == 'Test card' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] From 644c33cc1e6924e0b7e9927b960bd4e059ac0e88 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 26 Oct 2018 15:10:05 +0200 Subject: [PATCH 070/230] Convert MQTT Light tests to async (#17754) --- tests/components/light/test_mqtt.py | 1664 ++++++++++++++------------- 1 file changed, 840 insertions(+), 824 deletions(-) diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index 84c863d8621..f09f3726252 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -153,11 +153,10 @@ light: payload_off: "off" """ -import unittest from unittest import mock from unittest.mock import patch -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.const import ( STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE) from homeassistant.components import light, mqtt @@ -165,860 +164,877 @@ from homeassistant.components.mqtt.discovery import async_start import homeassistant.core as ha from tests.common import ( - assert_setup_component, get_test_home_assistant, mock_mqtt_component, - async_fire_mqtt_message, fire_mqtt_message, mock_coro, MockConfigEntry) + assert_setup_component, async_fire_mqtt_message, + mock_coro, MockConfigEntry) from tests.components.light import common -class TestLightMQTT(unittest.TestCase): - """Test the MQTT light.""" +async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): + """Test if command fails with command topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + } + }) + assert hass.states.get('light.test') is None - # pylint: disable=invalid-name - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.mock_publish = mock_mqtt_component(self.hass) - - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() - - def test_fail_setup_if_no_command_topic(self): - """Test if command fails with command topic.""" - with assert_setup_component(0, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - } - }) - assert self.hass.states.get('light.test') is None - - def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(self): - """Test if there is no color and brightness if no topic.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_light_rgb/status', - 'command_topic': 'test_light_rgb/set', - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('hs_color') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('hs_color') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - - def test_controlling_state_via_topic(self): - """Test the controlling of the state via topic.""" - config = {light.DOMAIN: { +async def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics( + hass, mqtt_mock): + """Test if there is no color and brightness if no topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'state_topic': 'test_light_rgb/status', 'command_topic': 'test_light_rgb/set', - 'brightness_state_topic': 'test_light_rgb/brightness/status', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'rgb_state_topic': 'test_light_rgb/rgb/status', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'color_temp_state_topic': 'test_light_rgb/color_temp/status', - 'color_temp_command_topic': 'test_light_rgb/color_temp/set', - 'effect_state_topic': 'test_light_rgb/effect/status', - 'effect_command_topic': 'test_light_rgb/effect/set', - 'hs_state_topic': 'test_light_rgb/hs/status', - 'hs_command_topic': 'test_light_rgb/hs/set', - 'white_value_state_topic': 'test_light_rgb/white_value/status', - 'white_value_command_topic': 'test_light_rgb/white_value/set', - 'xy_state_topic': 'test_light_rgb/xy/status', - 'xy_command_topic': 'test_light_rgb/xy/set', - 'qos': '0', - 'payload_on': 1, - 'payload_off': 0 - }} + } + }) + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + + +async def test_controlling_state_via_topic(hass, mqtt_mock): + """Test the controlling of the state via topic.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_state_topic': 'test_light_rgb/color_temp/status', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'effect_state_topic': 'test_light_rgb/effect/status', + 'effect_command_topic': 'test_light_rgb/effect/set', + 'hs_state_topic': 'test_light_rgb/hs/status', + 'hs_command_topic': 'test_light_rgb/hs/set', + 'white_value_state_topic': 'test_light_rgb/white_value/status', + 'white_value_command_topic': 'test_light_rgb/white_value/set', + 'xy_state_topic': 'test_light_rgb/xy/status', + 'xy_command_topic': 'test_light_rgb/xy/set', + 'qos': '0', + 'payload_on': 1, + 'payload_off': 0 + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('rgb_color') is None + assert state.attributes.get('brightness') is None + assert state.attributes.get('color_temp') is None + assert state.attributes.get('effect') is None + assert state.attributes.get('hs_color') is None + assert state.attributes.get('white_value') is None + assert state.attributes.get('xy_color') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, 'test_light_rgb/status', '1') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 255, 255) == state.attributes.get('rgb_color') + assert 255 == state.attributes.get('brightness') + assert 150 == state.attributes.get('color_temp') + assert 'none' == state.attributes.get('effect') + assert (0, 0) == state.attributes.get('hs_color') + assert 255 == state.attributes.get('white_value') + assert (0.323, 0.329) == state.attributes.get('xy_color') + + async_fire_mqtt_message(hass, 'test_light_rgb/status', '0') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + async_fire_mqtt_message(hass, 'test_light_rgb/status', '1') + await hass.async_block_till_done() + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, 'test_light_rgb/brightness/status', '100') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 100 == \ + light_state.attributes['brightness'] + + async_fire_mqtt_message(hass, 'test_light_rgb/color_temp/status', '300') + await hass.async_block_till_done() + await hass.async_block_till_done() + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 300 == light_state.attributes['color_temp'] + + async_fire_mqtt_message(hass, 'test_light_rgb/effect/status', 'rainbow') + await hass.async_block_till_done() + await hass.async_block_till_done() + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 'rainbow' == light_state.attributes['effect'] + + async_fire_mqtt_message(hass, 'test_light_rgb/white_value/status', + '100') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 100 == \ + light_state.attributes['white_value'] + + async_fire_mqtt_message(hass, 'test_light_rgb/status', '1') + await hass.async_block_till_done() + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, 'test_light_rgb/rgb/status', + '125,125,125') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (255, 255, 255) == \ + light_state.attributes.get('rgb_color') + + async_fire_mqtt_message(hass, 'test_light_rgb/hs/status', + '200,50') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (200, 50) == \ + light_state.attributes.get('hs_color') + + async_fire_mqtt_message(hass, 'test_light_rgb/xy/status', + '0.675,0.322') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + assert (0.672, 0.324) == \ + light_state.attributes.get('xy_color') + + +async def test_brightness_controlling_scale(hass, mqtt_mock): + """Test the brightness controlling scale.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_scale/status', + 'command_topic': 'test_scale/set', + 'brightness_state_topic': 'test_scale/brightness/status', + 'brightness_command_topic': 'test_scale/brightness/set', + 'brightness_scale': '99', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, 'test_scale/status', 'on') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + + async_fire_mqtt_message(hass, 'test_scale/status', 'off') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + async_fire_mqtt_message(hass, 'test_scale/status', 'on') + await hass.async_block_till_done() + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, 'test_scale/brightness/status', '99') + await hass.async_block_till_done() + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + await hass.async_block_till_done() + assert 255 == \ + light_state.attributes['brightness'] + + +async def test_brightness_from_rgb_controlling_scale(hass, mqtt_mock): + """Test the brightness controlling scale.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_scale_rgb/status', + 'command_topic': 'test_scale_rgb/set', + 'rgb_state_topic': 'test_scale_rgb/rgb/status', + 'rgb_command_topic': 'test_scale_rgb/rgb/set', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, 'test_scale_rgb/status', 'on') + async_fire_mqtt_message(hass, 'test_scale_rgb/rgb/status', '255,0,0') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert 255 == state.attributes.get('brightness') + + async_fire_mqtt_message(hass, 'test_scale_rgb/rgb/status', '127,0,0') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert 127 == state.attributes.get('brightness') + + +async def test_white_value_controlling_scale(hass, mqtt_mock): + """Test the white_value controlling scale.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_scale/status', + 'command_topic': 'test_scale/set', + 'white_value_state_topic': 'test_scale/white_value/status', + 'white_value_command_topic': 'test_scale/white_value/set', + 'white_value_scale': '99', + 'qos': 0, + 'payload_on': 'on', + 'payload_off': 'off' + } + }) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('white_value') is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, 'test_scale/status', 'on') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') + + async_fire_mqtt_message(hass, 'test_scale/status', 'off') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + async_fire_mqtt_message(hass, 'test_scale/status', 'on') + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, 'test_scale/white_value/status', '99') + await hass.async_block_till_done() + + light_state = hass.states.get('light.test') + await hass.async_block_till_done() + assert 255 == \ + light_state.attributes['white_value'] + + +async def test_controlling_state_via_topic_with_templates(hass, mqtt_mock): + """Test the setting of the state with a template.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'test_light_rgb/status', + 'command_topic': 'test_light_rgb/set', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'effect_command_topic': 'test_light_rgb/effect/set', + 'hs_command_topic': 'test_light_rgb/hs/set', + 'white_value_command_topic': 'test_light_rgb/white_value/set', + 'xy_command_topic': 'test_light_rgb/xy/set', + 'brightness_state_topic': 'test_light_rgb/brightness/status', + 'color_temp_state_topic': 'test_light_rgb/color_temp/status', + 'effect_state_topic': 'test_light_rgb/effect/status', + 'hs_state_topic': 'test_light_rgb/hs/status', + 'rgb_state_topic': 'test_light_rgb/rgb/status', + 'white_value_state_topic': 'test_light_rgb/white_value/status', + 'xy_state_topic': 'test_light_rgb/xy/status', + 'state_value_template': '{{ value_json.hello }}', + 'brightness_value_template': '{{ value_json.hello }}', + 'color_temp_value_template': '{{ value_json.hello }}', + 'effect_value_template': '{{ value_json.hello }}', + 'hs_value_template': '{{ value_json.hello | join(",") }}', + 'rgb_value_template': '{{ value_json.hello | join(",") }}', + 'white_value_template': '{{ value_json.hello }}', + 'xy_value_template': '{{ value_json.hello | join(",") }}', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + assert state.attributes.get('rgb_color') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/rgb/status', + '{"hello": [1, 2, 3]}') + async_fire_mqtt_message(hass, 'test_light_rgb/status', + '{"hello": "ON"}') + async_fire_mqtt_message(hass, 'test_light_rgb/brightness/status', + '{"hello": "50"}') + async_fire_mqtt_message(hass, 'test_light_rgb/color_temp/status', + '{"hello": "300"}') + async_fire_mqtt_message(hass, 'test_light_rgb/effect/status', + '{"hello": "rainbow"}') + async_fire_mqtt_message(hass, 'test_light_rgb/white_value/status', + '{"hello": "75"}') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 50 == state.attributes.get('brightness') + assert (84, 169, 255) == state.attributes.get('rgb_color') + assert 300 == state.attributes.get('color_temp') + assert 'rainbow' == state.attributes.get('effect') + assert 75 == state.attributes.get('white_value') + + async_fire_mqtt_message(hass, 'test_light_rgb/hs/status', + '{"hello": [100,50]}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert (100, 50) == state.attributes.get('hs_color') + + async_fire_mqtt_message(hass, 'test_light_rgb/xy/status', + '{"hello": [0.123,0.123]}') + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert (0.14, 0.131) == state.attributes.get('xy_color') + + +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test the sending of command in optimistic mode.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'color_temp_command_topic': 'test_light_rgb/color_temp/set', + 'effect_command_topic': 'test_light_rgb/effect/set', + 'hs_command_topic': 'test_light_rgb/hs/set', + 'white_value_command_topic': 'test_light_rgb/white_value/set', + 'xy_command_topic': 'test_light_rgb/xy/set', + 'effect_list': ['colorloop', 'random'], + 'qos': 2, + 'payload_on': 'on', + 'payload_off': 'off' + }} + fake_state = ha.State('light.test', 'on', {'brightness': 95, + 'hs_color': [100, 100], + 'effect': 'random', + 'color_temp': 100, + 'white_value': 50}) + with patch('homeassistant.components.light.mqtt.async_get_last_state', + return_value=mock_coro(fake_state)): with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('rgb_color') is None - assert state.attributes.get('brightness') is None - assert state.attributes.get('color_temp') is None - assert state.attributes.get('effect') is None - assert state.attributes.get('hs_color') is None - assert state.attributes.get('white_value') is None - assert state.attributes.get('xy_color') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 255, 255) == state.attributes.get('rgb_color') - assert 255 == state.attributes.get('brightness') - assert 150 == state.attributes.get('color_temp') - assert 'none' == state.attributes.get('effect') - assert (0, 0) == state.attributes.get('hs_color') - assert 255 == state.attributes.get('white_value') - assert (0.323, 0.329) == state.attributes.get('xy_color') - - fire_mqtt_message(self.hass, 'test_light_rgb/status', '0') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.block_till_done() - - fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '100') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 100 == \ - light_state.attributes['brightness'] - - fire_mqtt_message(self.hass, 'test_light_rgb/color_temp/status', '300') - self.hass.block_till_done() - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 300 == light_state.attributes['color_temp'] - - fire_mqtt_message(self.hass, 'test_light_rgb/effect/status', 'rainbow') - self.hass.block_till_done() - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 'rainbow' == light_state.attributes['effect'] - - fire_mqtt_message(self.hass, 'test_light_rgb/white_value/status', - '100') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 100 == \ - light_state.attributes['white_value'] - - fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.block_till_done() - - fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', - '125,125,125') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (255, 255, 255) == \ - light_state.attributes.get('rgb_color') - - fire_mqtt_message(self.hass, 'test_light_rgb/hs/status', - '200,50') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (200, 50) == \ - light_state.attributes.get('hs_color') - - fire_mqtt_message(self.hass, 'test_light_rgb/xy/status', - '0.675,0.322') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - assert (0.672, 0.324) == \ - light_state.attributes.get('xy_color') - - def test_brightness_controlling_scale(self): - """Test the brightness controlling scale.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_scale/status', - 'command_topic': 'test_scale/set', - 'brightness_state_topic': 'test_scale/brightness/status', - 'brightness_command_topic': 'test_scale/brightness/set', - 'brightness_scale': '99', - 'qos': 0, - 'payload_on': 'on', - 'payload_off': 'off' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('brightness') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - - fire_mqtt_message(self.hass, 'test_scale/status', 'off') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.block_till_done() - - fire_mqtt_message(self.hass, 'test_scale/brightness/status', '99') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 255 == \ - light_state.attributes['brightness'] - - def test_brightness_from_rgb_controlling_scale(self): - """Test the brightness controlling scale.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_scale_rgb/status', - 'command_topic': 'test_scale_rgb/set', - 'rgb_state_topic': 'test_scale_rgb/rgb/status', - 'rgb_command_topic': 'test_scale_rgb/rgb/set', - 'qos': 0, - 'payload_on': 'on', - 'payload_off': 'off' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('brightness') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - fire_mqtt_message(self.hass, 'test_scale_rgb/status', 'on') - fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '255,0,0') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert 255 == state.attributes.get('brightness') - - fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '127,0,0') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert 127 == state.attributes.get('brightness') - - def test_white_value_controlling_scale(self): - """Test the white_value controlling scale.""" - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_scale/status', - 'command_topic': 'test_scale/set', - 'white_value_state_topic': 'test_scale/white_value/status', - 'white_value_command_topic': 'test_scale/white_value/set', - 'white_value_scale': '99', - 'qos': 0, - 'payload_on': 'on', - 'payload_off': 'off' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('white_value') is None - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('white_value') - - fire_mqtt_message(self.hass, 'test_scale/status', 'off') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.block_till_done() - - fire_mqtt_message(self.hass, 'test_scale/white_value/status', '99') - self.hass.block_till_done() - - light_state = self.hass.states.get('light.test') - self.hass.block_till_done() - assert 255 == \ - light_state.attributes['white_value'] - - def test_controlling_state_via_topic_with_templates(self): - """Test the setting of the state with a template.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'test_light_rgb/status', - 'command_topic': 'test_light_rgb/set', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'color_temp_command_topic': 'test_light_rgb/color_temp/set', - 'effect_command_topic': 'test_light_rgb/effect/set', - 'hs_command_topic': 'test_light_rgb/hs/set', - 'white_value_command_topic': 'test_light_rgb/white_value/set', - 'xy_command_topic': 'test_light_rgb/xy/set', - 'brightness_state_topic': 'test_light_rgb/brightness/status', - 'color_temp_state_topic': 'test_light_rgb/color_temp/status', - 'effect_state_topic': 'test_light_rgb/effect/status', - 'hs_state_topic': 'test_light_rgb/hs/status', - 'rgb_state_topic': 'test_light_rgb/rgb/status', - 'white_value_state_topic': 'test_light_rgb/white_value/status', - 'xy_state_topic': 'test_light_rgb/xy/status', - 'state_value_template': '{{ value_json.hello }}', - 'brightness_value_template': '{{ value_json.hello }}', - 'color_temp_value_template': '{{ value_json.hello }}', - 'effect_value_template': '{{ value_json.hello }}', - 'hs_value_template': '{{ value_json.hello | join(",") }}', - 'rgb_value_template': '{{ value_json.hello | join(",") }}', - 'white_value_template': '{{ value_json.hello }}', - 'xy_value_template': '{{ value_json.hello | join(",") }}', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('brightness') is None - assert state.attributes.get('rgb_color') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', - '{"hello": [1, 2, 3]}') - fire_mqtt_message(self.hass, 'test_light_rgb/status', - '{"hello": "ON"}') - fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', - '{"hello": "50"}') - fire_mqtt_message(self.hass, 'test_light_rgb/color_temp/status', - '{"hello": "300"}') - fire_mqtt_message(self.hass, 'test_light_rgb/effect/status', - '{"hello": "rainbow"}') - fire_mqtt_message(self.hass, 'test_light_rgb/white_value/status', - '{"hello": "75"}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 50 == state.attributes.get('brightness') - assert (84, 169, 255) == state.attributes.get('rgb_color') - assert 300 == state.attributes.get('color_temp') - assert 'rainbow' == state.attributes.get('effect') - assert 75 == state.attributes.get('white_value') - - fire_mqtt_message(self.hass, 'test_light_rgb/hs/status', - '{"hello": [100,50]}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert (100, 50) == state.attributes.get('hs_color') - - fire_mqtt_message(self.hass, 'test_light_rgb/xy/status', - '{"hello": [0.123,0.123]}') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert (0.14, 0.131) == state.attributes.get('xy_color') - - def test_sending_mqtt_commands_and_optimistic(self): - """Test the sending of command in optimistic mode.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'color_temp_command_topic': 'test_light_rgb/color_temp/set', - 'effect_command_topic': 'test_light_rgb/effect/set', - 'hs_command_topic': 'test_light_rgb/hs/set', - 'white_value_command_topic': 'test_light_rgb/white_value/set', - 'xy_command_topic': 'test_light_rgb/xy/set', - 'effect_list': ['colorloop', 'random'], - 'qos': 2, - 'payload_on': 'on', - 'payload_off': 'off' - }} - fake_state = ha.State('light.test', 'on', {'brightness': 95, - 'hs_color': [100, 100], - 'effect': 'random', - 'color_temp': 100, - 'white_value': 50}) - with patch('homeassistant.components.light.mqtt.async_get_last_state', - return_value=mock_coro(fake_state)): - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 95 == state.attributes.get('brightness') - assert (100, 100) == state.attributes.get('hs_color') - assert 'random' == state.attributes.get('effect') - assert 100 == state.attributes.get('color_temp') - assert 50 == state.attributes.get('white_value') - assert state.attributes.get(ATTR_ASSUMED_STATE) - - common.turn_on(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'on', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light_rgb/set', 'off', 2, False) - self.mock_publish.async_publish.reset_mock() - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - self.mock_publish.reset_mock() - common.turn_on(self.hass, 'light.test', - brightness=50, xy_color=[0.123, 0.123]) - common.turn_on(self.hass, 'light.test', - brightness=50, hs_color=[359, 78]) - common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0], - white_value=80) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light_rgb/set', 'on', 2, False), - mock.call('test_light_rgb/rgb/set', '255,128,0', 2, False), - mock.call('test_light_rgb/brightness/set', 50, 2, False), - mock.call('test_light_rgb/hs/set', '359.0,78.0', 2, False), - mock.call('test_light_rgb/white_value/set', 80, 2, False), - mock.call('test_light_rgb/xy/set', '0.14,0.131', 2, False), - ], any_order=True) - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 128, 0) == state.attributes['rgb_color'] - assert 50 == state.attributes['brightness'] - assert (30.118, 100) == state.attributes['hs_color'] - assert 80 == state.attributes['white_value'] - assert (0.611, 0.375) == state.attributes['xy_color'] - - def test_sending_mqtt_rgb_command_with_template(self): - """Test the sending of RGB command with template.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light_rgb/set', - 'rgb_command_topic': 'test_light_rgb/rgb/set', - 'rgb_command_template': '{{ "#%02x%02x%02x" | ' - 'format(red, green, blue)}}', - 'payload_on': 'on', - 'payload_off': 'off', - 'qos': 0 - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 64]) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light_rgb/set', 'on', 0, False), - mock.call('test_light_rgb/rgb/set', '#ff803f', 0, False), - ], any_order=True) - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (255, 128, 63) == state.attributes['rgb_color'] - - def test_show_brightness_if_only_command_topic(self): - """Test the brightness if only a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'brightness_command_topic': 'test_light_rgb/brightness/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('brightness') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('brightness') - - def test_show_color_temp_only_if_command_topic(self): - """Test the color temp only if a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'color_temp_command_topic': 'test_light_rgb/brightness/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status' - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('color_temp') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 150 == state.attributes.get('color_temp') - - def test_show_effect_only_if_command_topic(self): - """Test the color temp only if a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'effect_command_topic': 'test_light_rgb/effect/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status' - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('effect') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 'none' == state.attributes.get('effect') - - def test_show_hs_if_only_command_topic(self): - """Test the hs if only a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'hs_command_topic': 'test_light_rgb/hs/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('hs_color') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (0, 0) == state.attributes.get('hs_color') - - def test_show_white_value_if_only_command_topic(self): - """Test the white_value if only a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'white_value_command_topic': 'test_light_rgb/white_value/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('white_value') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert 255 == state.attributes.get('white_value') - - def test_show_xy_if_only_command_topic(self): - """Test the xy if only a command topic is present.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'xy_command_topic': 'test_light_rgb/xy/set', - 'command_topic': 'test_light_rgb/set', - 'state_topic': 'test_light_rgb/status', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - assert state.attributes.get('xy_color') is None - - fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_ON == state.state - assert (0.323, 0.329) == state.attributes.get('xy_color') - - def test_on_command_first(self): - """Test on command being sent before brightness.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light/set', - 'brightness_command_topic': 'test_light/bright', - 'on_command_type': 'first', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - common.turn_on(self.hass, 'light.test', brightness=50) - self.hass.block_till_done() - - # Should get the following MQTT messages. - # test_light/set: 'ON' - # test_light/bright: 50 - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light/set', 'ON', 0, False), - mock.call('test_light/bright', 50, 0, False), - ], any_order=True) - self.mock_publish.async_publish.reset_mock() - - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/set', 'OFF', 0, False) - - def test_on_command_last(self): - """Test on command being sent after brightness.""" - config = {light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light/set', - 'brightness_command_topic': 'test_light/bright', - }} - - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) - - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state - - common.turn_on(self.hass, 'light.test', brightness=50) - self.hass.block_till_done() - - # Should get the following MQTT messages. - # test_light/bright: 50 - # test_light/set: 'ON' - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light/bright', 50, 0, False), - mock.call('test_light/set', 'ON', 0, False), - ], any_order=True) - self.mock_publish.async_publish.reset_mock() - - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/set', 'OFF', 0, False) - - def test_on_command_brightness(self): - """Test on command being sent as only brightness.""" - config = {light.DOMAIN: { + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 95 == state.attributes.get('brightness') + assert (100, 100) == state.attributes.get('hs_color') + assert 'random' == state.attributes.get('effect') + assert 100 == state.attributes.get('color_temp') + assert 50 == state.attributes.get('white_value') + assert state.attributes.get(ATTR_ASSUMED_STATE) + + common.async_turn_on(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light_rgb/set', 'on', 2, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('light.test') + assert STATE_ON == state.state + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light_rgb/set', 'off', 2, False) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + mqtt_mock.reset_mock() + common.async_turn_on(hass, 'light.test', + brightness=50, xy_color=[0.123, 0.123]) + common.async_turn_on(hass, 'light.test', + brightness=50, hs_color=[359, 78]) + common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0], + white_value=80) + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light_rgb/set', 'on', 2, False), + mock.call('test_light_rgb/rgb/set', '255,128,0', 2, False), + mock.call('test_light_rgb/brightness/set', 50, 2, False), + mock.call('test_light_rgb/hs/set', '359.0,78.0', 2, False), + mock.call('test_light_rgb/white_value/set', 80, 2, False), + mock.call('test_light_rgb/xy/set', '0.14,0.131', 2, False), + ], any_order=True) + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 128, 0) == state.attributes['rgb_color'] + assert 50 == state.attributes['brightness'] + assert (30.118, 100) == state.attributes['hs_color'] + assert 80 == state.attributes['white_value'] + assert (0.611, 0.375) == state.attributes['xy_color'] + + +async def test_sending_mqtt_rgb_command_with_template(hass, mqtt_mock): + """Test the sending of RGB command with template.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light_rgb/set', + 'rgb_command_topic': 'test_light_rgb/rgb/set', + 'rgb_command_template': '{{ "#%02x%02x%02x" | ' + 'format(red, green, blue)}}', + 'payload_on': 'on', + 'payload_off': 'off', + 'qos': 0 + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 64]) + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light_rgb/set', 'on', 0, False), + mock.call('test_light_rgb/rgb/set', '#ff803f', 0, False), + ], any_order=True) + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (255, 128, 63) == state.attributes['rgb_color'] + + +async def test_show_brightness_if_only_command_topic(hass, mqtt_mock): + """Test the brightness if only a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'brightness_command_topic': 'test_light_rgb/brightness/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('brightness') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('brightness') + + +async def test_show_color_temp_only_if_command_topic(hass, mqtt_mock): + """Test the color temp only if a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'color_temp_command_topic': 'test_light_rgb/brightness/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status' + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('color_temp') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 150 == state.attributes.get('color_temp') + + +async def test_show_effect_only_if_command_topic(hass, mqtt_mock): + """Test the color temp only if a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'effect_command_topic': 'test_light_rgb/effect/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status' + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('effect') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 'none' == state.attributes.get('effect') + + +async def test_show_hs_if_only_command_topic(hass, mqtt_mock): + """Test the hs if only a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'hs_command_topic': 'test_light_rgb/hs/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('hs_color') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (0, 0) == state.attributes.get('hs_color') + + +async def test_show_white_value_if_only_command_topic(hass, mqtt_mock): + """Test the white_value if only a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'white_value_command_topic': 'test_light_rgb/white_value/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('white_value') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert 255 == state.attributes.get('white_value') + + +async def test_show_xy_if_only_command_topic(hass, mqtt_mock): + """Test the xy if only a command topic is present.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'xy_command_topic': 'test_light_rgb/xy/set', + 'command_topic': 'test_light_rgb/set', + 'state_topic': 'test_light_rgb/status', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + assert state.attributes.get('xy_color') is None + + async_fire_mqtt_message(hass, 'test_light_rgb/status', 'ON') + await hass.async_block_till_done() + + state = hass.states.get('light.test') + assert STATE_ON == state.state + assert (0.323, 0.329) == state.attributes.get('xy_color') + + +async def test_on_command_first(hass, mqtt_mock): + """Test on command being sent before brightness.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light/set', + 'brightness_command_topic': 'test_light/bright', + 'on_command_type': 'first', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + common.async_turn_on(hass, 'light.test', brightness=50) + await hass.async_block_till_done() + + # Should get the following MQTT messages. + # test_light/set: 'ON' + # test_light/bright: 50 + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light/set', 'ON', 0, False), + mock.call('test_light/bright', 50, 0, False), + ], any_order=True) + mqtt_mock.async_publish.reset_mock() + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/set', 'OFF', 0, False) + + +async def test_on_command_last(hass, mqtt_mock): + """Test on command being sent after brightness.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light/set', + 'brightness_command_topic': 'test_light/bright', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + common.async_turn_on(hass, 'light.test', brightness=50) + await hass.async_block_till_done() + + # Should get the following MQTT messages. + # test_light/bright: 50 + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light/bright', 50, 0, False), + mock.call('test_light/set', 'ON', 0, False), + ], any_order=True) + mqtt_mock.async_publish.reset_mock() + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/set', 'OFF', 0, False) + + +async def test_on_command_brightness(hass, mqtt_mock): + """Test on command being sent as only brightness.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light/set', + 'brightness_command_topic': 'test_light/bright', + 'rgb_command_topic': "test_light/rgb", + 'on_command_type': 'brightness', + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + # Turn on w/ no brightness - should set to max + common.async_turn_on(hass, 'light.test') + await hass.async_block_till_done() + + # Should get the following MQTT messages. + # test_light/bright: 255 + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/bright', 255, 0, False) + mqtt_mock.async_publish.reset_mock() + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/set', 'OFF', 0, False) + mqtt_mock.async_publish.reset_mock() + + # Turn on w/ brightness + common.async_turn_on(hass, 'light.test', brightness=50) + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/bright', 50, 0, False) + mqtt_mock.async_publish.reset_mock() + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + # Turn on w/ just a color to insure brightness gets + # added and sent. + common.async_turn_on(hass, 'light.test', rgb_color=[255, 128, 0]) + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light/rgb', '255,128,0', 0, False), + mock.call('test_light/bright', 50, 0, False) + ], any_order=True) + + +async def test_on_command_rgb(hass, mqtt_mock): + """Test on command in RGB brightness mode.""" + config = {light.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'command_topic': 'test_light/set', + 'rgb_command_topic': "test_light/rgb", + }} + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get('light.test') + assert STATE_OFF == state.state + + common.async_turn_on(hass, 'light.test', brightness=127) + await hass.async_block_till_done() + + # Should get the following MQTT messages. + # test_light/rgb: '127,127,127' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls([ + mock.call('test_light/rgb', '127,127,127', 0, False), + mock.call('test_light/set', 'ON', 0, False), + ], any_order=True) + mqtt_mock.async_publish.reset_mock() + + common.async_turn_off(hass, 'light.test') + await hass.async_block_till_done() + + mqtt_mock.async_publish.assert_called_once_with( + 'test_light/set', 'OFF', 0, False) + + +async def test_default_availability_payload(hass, mqtt_mock): + """Test availability by default payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'command_topic': 'test_light/set', 'brightness_command_topic': 'test_light/bright', 'rgb_command_topic': "test_light/rgb", - 'on_command_type': 'brightness', - }} + 'availability_topic': 'availability-topic' + } + }) - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state + async_fire_mqtt_message(hass, 'availability-topic', 'online') + await hass.async_block_till_done() - # Turn on w/ no brightness - should set to max - common.turn_on(self.hass, 'light.test') - self.hass.block_till_done() + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state - # Should get the following MQTT messages. - # test_light/bright: 255 - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/bright', 255, 0, False) - self.mock_publish.async_publish.reset_mock() + async_fire_mqtt_message(hass, 'availability-topic', 'offline') + await hass.async_block_till_done() + await hass.async_block_till_done() - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/set', 'OFF', 0, False) - self.mock_publish.async_publish.reset_mock() - # Turn on w/ brightness - common.turn_on(self.hass, 'light.test', brightness=50) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/bright', 50, 0, False) - self.mock_publish.async_publish.reset_mock() - - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - # Turn on w/ just a color to insure brightness gets - # added and sent. - common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0]) - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light/rgb', '255,128,0', 0, False), - mock.call('test_light/bright', 50, 0, False) - ], any_order=True) - - def test_on_command_rgb(self): - """Test on command in RGB brightness mode.""" - config = {light.DOMAIN: { +async def test_custom_availability_payload(hass, mqtt_mock): + """Test availability by custom payload with defined topic.""" + assert await async_setup_component(hass, light.DOMAIN, { + light.DOMAIN: { 'platform': 'mqtt', 'name': 'test', 'command_topic': 'test_light/set', + 'brightness_command_topic': 'test_light/bright', 'rgb_command_topic': "test_light/rgb", - }} + 'availability_topic': 'availability-topic', + 'payload_available': 'good', + 'payload_not_available': 'nogood' + } + }) - with assert_setup_component(1, light.DOMAIN): - assert setup_component(self.hass, light.DOMAIN, config) + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state - state = self.hass.states.get('light.test') - assert STATE_OFF == state.state + async_fire_mqtt_message(hass, 'availability-topic', 'good') + await hass.async_block_till_done() - common.turn_on(self.hass, 'light.test', brightness=127) - self.hass.block_till_done() + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE != state.state - # Should get the following MQTT messages. - # test_light/rgb: '127,127,127' - # test_light/set: 'ON' - self.mock_publish.async_publish.assert_has_calls([ - mock.call('test_light/rgb', '127,127,127', 0, False), - mock.call('test_light/set', 'ON', 0, False), - ], any_order=True) - self.mock_publish.async_publish.reset_mock() + async_fire_mqtt_message(hass, 'availability-topic', 'nogood') + await hass.async_block_till_done() + await hass.async_block_till_done() - common.turn_off(self.hass, 'light.test') - self.hass.block_till_done() - - self.mock_publish.async_publish.assert_called_once_with( - 'test_light/set', 'OFF', 0, False) - - def test_default_availability_payload(self): - """Test availability by default payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light/set', - 'brightness_command_topic': 'test_light/bright', - 'rgb_command_topic': "test_light/rgb", - 'availability_topic': 'availability-topic' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'online') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'offline') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - assert setup_component(self.hass, light.DOMAIN, { - light.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'command_topic': 'test_light/set', - 'brightness_command_topic': 'test_light/bright', - 'rgb_command_topic': "test_light/rgb", - 'availability_topic': 'availability-topic', - 'payload_available': 'good', - 'payload_not_available': 'nogood' - } - }) - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'good') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE != state.state - - fire_mqtt_message(self.hass, 'availability-topic', 'nogood') - self.hass.block_till_done() - - state = self.hass.states.get('light.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('light.test') + assert STATE_UNAVAILABLE == state.state async def test_discovery_removal_light(hass, mqtt_mock, caplog): From 92bad453f25fb481c06e089babe4f9a845f80fb1 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Fri, 26 Oct 2018 15:45:38 +0200 Subject: [PATCH 071/230] Bumping aioasuswrt version (#17814) --- homeassistant/components/device_tracker/asuswrt.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 2ac3aaee933..7f79bea0d2e 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, CONF_PROTOCOL) -REQUIREMENTS = ['aioasuswrt==1.0.0'] +REQUIREMENTS = ['aioasuswrt==1.1.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 6a9592118c9..8dd576e9583 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -88,7 +88,7 @@ abodepy==0.14.0 afsapi==0.0.4 # homeassistant.components.device_tracker.asuswrt -aioasuswrt==1.0.0 +aioasuswrt==1.1.0 # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 From 434c84810437f4db9679ccb81894fab87a04fc14 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 26 Oct 2018 15:45:57 +0200 Subject: [PATCH 072/230] Minor changes (#17812) --- homeassistant/components/switch/dlink.py | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/switch/dlink.py b/homeassistant/components/switch/dlink.py index 91ef546ea22..de584510008 100644 --- a/homeassistant/components/switch/dlink.py +++ b/homeassistant/components/switch/dlink.py @@ -1,43 +1,44 @@ """ -Support for D-link W215 smart switch. +Support for D-Link W215 smart switch. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.dlink/ """ +from datetime import timedelta import logging import urllib -from datetime import timedelta import voluptuous as vol +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, + TEMP_CELSIUS) import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util -from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) -from homeassistant.const import (ATTR_TEMPERATURE, - CONF_HOST, CONF_NAME, CONF_PASSWORD, - CONF_USERNAME, TEMP_CELSIUS) REQUIREMENTS = ['pyW215==0.6.0'] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'D-link Smart Plug W215' -DEFAULT_PASSWORD = '' -DEFAULT_USERNAME = 'admin' +ATTR_TOTAL_CONSUMPTION = 'total_consumption' + CONF_USE_LEGACY_PROTOCOL = 'use_legacy_protocol' -ATTR_TOTAL_CONSUMPTION = 'total_consumption' +DEFAULT_NAME = "D-Link Smart Plug W215" +DEFAULT_PASSWORD = '' +DEFAULT_USERNAME = 'admin' + +SCAN_INTERVAL = timedelta(minutes=2) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Required(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_USE_LEGACY_PROTOCOL, default=False): cv.boolean, + vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_USE_LEGACY_PROTOCOL, default=False): cv.boolean, }) -SCAN_INTERVAL = timedelta(minutes=2) - def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a D-Link Smart Plug.""" @@ -49,17 +50,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): use_legacy_protocol = config.get(CONF_USE_LEGACY_PROTOCOL) name = config.get(CONF_NAME) - smartplug = SmartPlug(host, - password, - username, - use_legacy_protocol) + smartplug = SmartPlug(host, password, username, use_legacy_protocol) data = SmartPlugData(smartplug) add_entities([SmartPlugSwitch(hass, data, name)], True) class SmartPlugSwitch(SwitchDevice): - """Representation of a D-link Smart Plug switch.""" + """Representation of a D-Link Smart Plug switch.""" def __init__(self, hass, data, name): """Initialize the switch.""" @@ -69,15 +67,15 @@ class SmartPlugSwitch(SwitchDevice): @property def name(self): - """Return the name of the Smart Plug, if any.""" + """Return the name of the Smart Plug.""" return self._name @property def device_state_attributes(self): """Return the state attributes of the device.""" try: - ui_temp = self.units.temperature(int(self.data.temperature), - TEMP_CELSIUS) + ui_temp = self.units.temperature( + int(self.data.temperature), TEMP_CELSIUS) temperature = ui_temp except (ValueError, TypeError): temperature = None @@ -149,16 +147,18 @@ class SmartPlugData: return _state = 'unknown' + try: self._last_tried = dt_util.now() _state = self.smartplug.state except urllib.error.HTTPError: - _LOGGER.error("Dlink connection problem") + _LOGGER.error("D-Link connection problem") if _state == 'unknown': self._n_tried += 1 self.available = False - _LOGGER.warning("Failed to connect to dlink switch.") + _LOGGER.warning("Failed to connect to D-Link switch") return + self.state = _state self.available = True From 86d7bc4962c25268d03787d91d04d84518aa8f42 Mon Sep 17 00:00:00 2001 From: Yevgeniy <33804747+sgttrs@users.noreply.github.com> Date: Fri, 26 Oct 2018 19:50:30 +0600 Subject: [PATCH 073/230] Add snow to Openweathermap precipitation forecast (#17551) * Add snow to Openweathermap precipitation forecast * Fix lint * Fix pylint * Add missing docstring --- homeassistant/components/weather/openweathermap.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py index b70413b9565..0e3a6f90b70 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/weather/openweathermap.py @@ -149,6 +149,15 @@ class OpenWeatherMapWeather(WeatherEntity): def forecast(self): """Return the forecast array.""" data = [] + + def calc_precipitation(rain, snow): + """Calculate the precipitation.""" + rain_value = 0 if rain is None else rain + snow_value = 0 if snow is None else snow + if round(rain_value + snow_value, 1) == 0: + return None + return round(rain_value + snow_value, 1) + for entry in self.forecast_data.get_weathers(): if self._mode == 'daily': data.append({ @@ -159,7 +168,9 @@ class OpenWeatherMapWeather(WeatherEntity): ATTR_FORECAST_TEMP_LOW: entry.get_temperature('celsius').get('night'), ATTR_FORECAST_PRECIPITATION: - entry.get_rain().get('all'), + calc_precipitation( + entry.get_rain().get('all'), + entry.get_snow().get('all')), ATTR_FORECAST_WIND_SPEED: entry.get_wind().get('speed'), ATTR_FORECAST_WIND_BEARING: From 317562736377a8459429a101a1ab2e032aabf344 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 26 Oct 2018 17:29:33 +0200 Subject: [PATCH 074/230] Add delete command (#17816) --- homeassistant/components/lovelace/__init__.py | 53 +++++++++++++++++++ tests/components/lovelace/test_init.py | 32 +++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 9102830e82f..e81af9e0d27 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -28,6 +28,7 @@ WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' WS_TYPE_ADD_CARD = 'lovelace/config/card/add' WS_TYPE_MOVE_CARD = 'lovelace/config/card/move' +WS_TYPE_DELETE_CARD = 'lovelace/config/card/delete' SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, @@ -65,6 +66,11 @@ SCHEMA_MOVE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Optional('new_view_id'): str, }) +SCHEMA_DELETE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_DELETE_CARD, + vol.Required('card_id'): str, +}) + class WriteError(HomeAssistantError): """Error writing the data.""" @@ -312,6 +318,22 @@ def move_card_view(fname: str, card_id: str, view_id: str, save_yaml(fname, config) +def delete_card(fname: str, card_id: str, position: int = None): + """Delete a card from view.""" + config = load_yaml(fname) + for view in config.get('views', []): + for card in view.get('cards', []): + if str(card.get('id')) != card_id: + continue + cards = view.get('cards') + cards.pop(cards.index(card)) + save_yaml(fname, config) + return + + raise CardNotFoundError( + "Card with ID: {} was not found in {}.".format(card_id, fname)) + + async def async_setup(hass, config): """Set up the Lovelace commands.""" # Backwards compat. Added in 0.80. Remove after 0.85 @@ -339,6 +361,10 @@ async def async_setup(hass, config): WS_TYPE_MOVE_CARD, websocket_lovelace_move_card, SCHEMA_MOVE_CARD) + hass.components.websocket_api.async_register_command( + WS_TYPE_DELETE_CARD, websocket_lovelace_delete_card, + SCHEMA_DELETE_CARD) + return True @@ -481,3 +507,30 @@ async def websocket_lovelace_move_card(hass, connection, msg): message = websocket_api.error_message(msg['id'], *error) connection.send_message(message) + + +@websocket_api.async_response +async def websocket_lovelace_delete_card(hass, connection, msg): + """Delete card from lovelace over websocket and save.""" + error = None + try: + await hass.async_add_executor_job( + delete_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id']) + message = websocket_api.result_message( + msg['id'] + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except CardNotFoundError as err: + error = 'card_not_found', str(err) + except HomeAssistantError as err: + error = 'save_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 5e486e295fe..21362a32193 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -498,7 +498,7 @@ async def test_lovelace_add_card_position(hass, hass_ws_client): async def test_lovelace_move_card_position(hass, hass_ws_client): - """Test add_card command.""" + """Test move_card command.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -524,7 +524,7 @@ async def test_lovelace_move_card_position(hass, hass_ws_client): async def test_lovelace_move_card_view(hass, hass_ws_client): - """Test add_card command.""" + """Test move_card to view command.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -550,7 +550,7 @@ async def test_lovelace_move_card_view(hass, hass_ws_client): async def test_lovelace_move_card_view_position(hass, hass_ws_client): - """Test add_card command.""" + """Test move_card to view with position command.""" await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) yaml = YAML(typ='rt') @@ -574,3 +574,29 @@ async def test_lovelace_move_card_view_position(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] + + +async def test_lovelace_delete_card(hass, hass_ws_client): + """Test delete_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.components.lovelace.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.components.lovelace.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/card/delete', + 'card_id': 'test', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + cards = result.mlget(['views', 1, 'cards'], list_ok=True) + assert len(cards) == 2 + assert cards[0]['title'] == 'Example' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] From 9f146a3954453c0a1d87f74f75a9bfb7d68b17e5 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Fri, 26 Oct 2018 20:18:14 +0200 Subject: [PATCH 075/230] Raise PlatformNotReady for RMVtransport if API not available (#17635) * Raise PlatformNotReady if API not available * Delete whitespaces * Revert unwanted breaking changes * Revert deleted line * Update homeassistant/components/sensor/rmvtransport.py * Use await asyncio.wait --- .../components/sensor/rmvtransport.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/rmvtransport.py b/homeassistant/components/sensor/rmvtransport.py index f9bd65c1a74..7835b74ac98 100644 --- a/homeassistant/components/sensor/rmvtransport.py +++ b/homeassistant/components/sensor/rmvtransport.py @@ -4,16 +4,18 @@ Support for real-time departure information for Rhein-Main public transport. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.rmvtransport/ """ +import asyncio import logging from datetime import timedelta import voluptuous as vol -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.entity import Entity from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION) +from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle REQUIREMENTS = ['PyRMVtransport==0.1.3'] @@ -90,7 +92,14 @@ async def async_setup_platform(hass, config, async_add_entities, next_departure.get(CONF_MAX_JOURNEYS), next_departure.get(CONF_NAME), timeout)) - async_add_entities(sensors, True) + + tasks = [sensor.async_update() for sensor in sensors] + if tasks: + await asyncio.wait(tasks) + if not all(sensor.data.departures for sensor in sensors): + raise PlatformNotReady + + async_add_entities(sensors) class RMVDepartureSensor(Entity): @@ -185,14 +194,16 @@ class RMVDepartureData: @Throttle(SCAN_INTERVAL) async def async_update(self): """Update the connection data.""" + from RMVtransport.rmvtransport import RMVtransportApiConnectionError + try: _data = await self.rmv.get_departures(self._station_id, products=self._products, directionId=self._direction, maxJourneys=50) - except ValueError: + except RMVtransportApiConnectionError: self.departures = [] - _LOGGER.warning("Returned data not understood") + _LOGGER.warning("Could not retrive data from rmv.de") return self.station = _data.get('station') _deps = [] From cfbd84f45020b063d05cd2dd8df262156f5715ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=81uci=C3=B3w?= Date: Fri, 26 Oct 2018 23:02:07 +0200 Subject: [PATCH 076/230] Added vacuum dock and pause/unpause traits (#17657) --- .../components/google_assistant/const.py | 3 +- .../components/google_assistant/smart_home.py | 5 +- .../components/google_assistant/trait.py | 97 +++++++++++++++++++ .../components/google_assistant/test_trait.py | 72 +++++++++++++- 4 files changed, 174 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index d8ab231c96b..7d9fa44442c 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -15,7 +15,7 @@ CONF_ROOM_HINT = 'room' DEFAULT_EXPOSE_BY_DEFAULT = True DEFAULT_EXPOSED_DOMAINS = [ 'climate', 'cover', 'fan', 'group', 'input_boolean', 'light', - 'media_player', 'scene', 'script', 'switch' + 'media_player', 'scene', 'script', 'switch', 'vacuum', ] CLIMATE_MODE_HEATCOOL = 'heatcool' CLIMATE_SUPPORTED_MODES = {'heat', 'cool', 'off', 'on', CLIMATE_MODE_HEATCOOL} @@ -23,6 +23,7 @@ CLIMATE_SUPPORTED_MODES = {'heat', 'cool', 'off', 'on', CLIMATE_MODE_HEATCOOL} PREFIX_TYPES = 'action.devices.types.' TYPE_LIGHT = PREFIX_TYPES + 'LIGHT' TYPE_SWITCH = PREFIX_TYPES + 'SWITCH' +TYPE_VACUUM = PREFIX_TYPES + 'VACUUM' TYPE_SCENE = PREFIX_TYPES + 'SCENE' TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT' diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 9c8f949b175..da6211b1911 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -19,11 +19,13 @@ from homeassistant.components import ( scene, script, switch, + vacuum, ) from . import trait from .const import ( - TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH, TYPE_THERMOSTAT, + TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH, TYPE_VACUUM, + TYPE_THERMOSTAT, CONF_ALIASES, CONF_ROOM_HINT, ERR_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR @@ -44,6 +46,7 @@ DOMAIN_TO_GOOGLE_TYPES = { scene.DOMAIN: TYPE_SCENE, script.DOMAIN: TYPE_SCENE, switch.DOMAIN: TYPE_SWITCH, + vacuum.DOMAIN: TYPE_VACUUM, } diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 1ee9d4e2364..00a01f262a9 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -13,6 +13,7 @@ from homeassistant.components import ( scene, script, switch, + vacuum, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -21,6 +22,7 @@ from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, + ATTR_SUPPORTED_FEATURES, ) from homeassistant.util import color as color_util, temperature as temp_util @@ -31,6 +33,8 @@ _LOGGER = logging.getLogger(__name__) PREFIX_TRAITS = 'action.devices.traits.' TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff' +TRAIT_DOCK = PREFIX_TRAITS + 'Dock' +TRAIT_STARTSTOP = PREFIX_TRAITS + 'StartStop' TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness' TRAIT_COLOR_SPECTRUM = PREFIX_TRAITS + 'ColorSpectrum' TRAIT_COLOR_TEMP = PREFIX_TRAITS + 'ColorTemperature' @@ -39,6 +43,9 @@ TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting' PREFIX_COMMANDS = 'action.devices.commands.' COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' +COMMAND_DOCK = PREFIX_COMMANDS + 'Dock' +COMMAND_STARTSTOP = PREFIX_COMMANDS + 'StartStop' +COMMAND_PAUSEUNPAUSE = PREFIX_COMMANDS + 'PauseUnpause' COMMAND_BRIGHTNESS_ABSOLUTE = PREFIX_COMMANDS + 'BrightnessAbsolute' COMMAND_COLOR_ABSOLUTE = PREFIX_COMMANDS + 'ColorAbsolute' COMMAND_ACTIVATE_SCENE = PREFIX_COMMANDS + 'ActivateScene' @@ -392,6 +399,96 @@ class SceneTrait(_Trait): }, blocking=self.state.domain != script.DOMAIN) +@register_trait +class DockTrait(_Trait): + """Trait to offer dock functionality. + + https://developers.google.com/actions/smarthome/traits/dock + """ + + name = TRAIT_DOCK + commands = [ + COMMAND_DOCK + ] + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + return domain == vacuum.DOMAIN + + def sync_attributes(self): + """Return dock attributes for a sync request.""" + return {} + + def query_attributes(self): + """Return dock query attributes.""" + return {'isDocked': self.state.state == vacuum.STATE_DOCKED} + + async def execute(self, command, params): + """Execute a dock command.""" + await self.hass.services.async_call( + self.state.domain, vacuum.SERVICE_RETURN_TO_BASE, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True) + + +@register_trait +class StartStopTrait(_Trait): + """Trait to offer StartStop functionality. + + https://developers.google.com/actions/smarthome/traits/startstop + """ + + name = TRAIT_STARTSTOP + commands = [ + COMMAND_STARTSTOP, + COMMAND_PAUSEUNPAUSE + ] + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + return domain == vacuum.DOMAIN + + def sync_attributes(self): + """Return StartStop attributes for a sync request.""" + return {'pausable': + self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + & vacuum.SUPPORT_PAUSE != 0} + + def query_attributes(self): + """Return StartStop query attributes.""" + return { + 'isRunning': self.state.state == vacuum.STATE_CLEANING, + 'isPaused': self.state.state == vacuum.STATE_PAUSED, + } + + async def execute(self, command, params): + """Execute a StartStop command.""" + if command == COMMAND_STARTSTOP: + if params['start']: + await self.hass.services.async_call( + self.state.domain, vacuum.SERVICE_START, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True) + else: + await self.hass.services.async_call( + self.state.domain, vacuum.SERVICE_STOP, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True) + elif command == COMMAND_PAUSEUNPAUSE: + if params['pause']: + await self.hass.services.async_call( + self.state.domain, vacuum.SERVICE_PAUSE, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True) + else: + await self.hass.services.async_call( + self.state.domain, vacuum.SERVICE_START, { + ATTR_ENTITY_ID: self.state.entity_id + }, blocking=True) + + @register_trait class TemperatureSettingTrait(_Trait): """Trait to offer handling both temperature point and modes functionality. diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 52dac7ddb61..3b3a158fbf8 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -3,7 +3,7 @@ import pytest from homeassistant.const import ( STATE_ON, STATE_OFF, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, - TEMP_CELSIUS, TEMP_FAHRENHEIT) + TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_SUPPORTED_FEATURES) from homeassistant.core import State, DOMAIN as HA_DOMAIN from homeassistant.components import ( climate, @@ -15,6 +15,7 @@ from homeassistant.components import ( scene, script, switch, + vacuum, ) from homeassistant.components.google_assistant import trait, helpers, const from homeassistant.util import color @@ -357,6 +358,75 @@ async def test_onoff_media_player(hass): } +async def test_dock_vacuum(hass): + """Test dock trait support for vacuum domain.""" + assert trait.DockTrait.supported(vacuum.DOMAIN, 0) + + trt = trait.DockTrait(hass, State('vacuum.bla', vacuum.STATE_IDLE)) + + assert trt.sync_attributes() == {} + + assert trt.query_attributes() == { + 'isDocked': False + } + + calls = async_mock_service(hass, vacuum.DOMAIN, + vacuum.SERVICE_RETURN_TO_BASE) + await trt.execute(trait.COMMAND_DOCK, {}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: 'vacuum.bla', + } + + +async def test_startstop_vacuum(hass): + """Test startStop trait support for vacuum domain.""" + assert trait.StartStopTrait.supported(vacuum.DOMAIN, 0) + + trt = trait.StartStopTrait(hass, State('vacuum.bla', vacuum.STATE_PAUSED, { + ATTR_SUPPORTED_FEATURES: vacuum.SUPPORT_PAUSE, + })) + + assert trt.sync_attributes() == {'pausable': True} + + assert trt.query_attributes() == { + 'isRunning': False, + 'isPaused': True + } + + start_calls = async_mock_service(hass, vacuum.DOMAIN, + vacuum.SERVICE_START) + await trt.execute(trait.COMMAND_STARTSTOP, {'start': True}) + assert len(start_calls) == 1 + assert start_calls[0].data == { + ATTR_ENTITY_ID: 'vacuum.bla', + } + + stop_calls = async_mock_service(hass, vacuum.DOMAIN, + vacuum.SERVICE_STOP) + await trt.execute(trait.COMMAND_STARTSTOP, {'start': False}) + assert len(stop_calls) == 1 + assert stop_calls[0].data == { + ATTR_ENTITY_ID: 'vacuum.bla', + } + + pause_calls = async_mock_service(hass, vacuum.DOMAIN, + vacuum.SERVICE_PAUSE) + await trt.execute(trait.COMMAND_PAUSEUNPAUSE, {'pause': True}) + assert len(pause_calls) == 1 + assert pause_calls[0].data == { + ATTR_ENTITY_ID: 'vacuum.bla', + } + + unpause_calls = async_mock_service(hass, vacuum.DOMAIN, + vacuum.SERVICE_START) + await trt.execute(trait.COMMAND_PAUSEUNPAUSE, {'pause': False}) + assert len(unpause_calls) == 1 + assert unpause_calls[0].data == { + ATTR_ENTITY_ID: 'vacuum.bla', + } + + async def test_color_spectrum_light(hass): """Test ColorSpectrum trait support for light domain.""" assert not trait.ColorSpectrumTrait.supported(light.DOMAIN, 0) From 8a4c78b69f40504c8427996a898358621404c402 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 26 Oct 2018 23:39:11 +0200 Subject: [PATCH 077/230] Minor changes to the config validation (#17808) --- homeassistant/components/sensor/sma.py | 49 ++++++++++++++++---------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index dd5209a4e0a..5290b2018bf 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -5,33 +5,41 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sma/ """ import asyncio -import logging from datetime import timedelta +import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, - CONF_SSL) -from homeassistant.helpers.event import async_track_time_interval + CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL, + EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_track_time_interval REQUIREMENTS = ['pysma==0.2'] _LOGGER = logging.getLogger(__name__) -CONF_GROUP = 'group' -CONF_SENSORS = 'sensors' CONF_CUSTOM = 'custom' +CONF_FACTOR = 'factor' +CONF_GROUP = 'group' +CONF_KEY = 'key' +CONF_SENSORS = 'sensors' +CONF_UNIT = 'unit' GROUP_INSTALLER = 'installer' GROUP_USER = 'user' GROUPS = [GROUP_USER, GROUP_INSTALLER] -SENSOR_OPTIONS = ['current_consumption', 'current_power', 'total_consumption', - 'total_yield'] + +SENSOR_OPTIONS = [ + 'current_consumption', + 'current_power', + 'total_consumption', + 'total_yield', +] def _check_sensor_schema(conf): @@ -48,23 +56,26 @@ def _check_sensor_schema(conf): return conf +CUSTOM_SCHEMA = vol.Any({ + vol.Required(CONF_KEY): + vol.All(cv.string, vol.Length(min=13, max=13)), + vol.Required(CONF_UNIT): cv.string, + vol.Optional(CONF_FACTOR, default=1): vol.Coerce(float), +}) + PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): str, + vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_SSL, default=False): cv.boolean, - vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_GROUP, default=GROUPS[0]): vol.In(GROUPS), vol.Required(CONF_SENSORS): vol.Schema({cv.slug: cv.ensure_list}), - vol.Optional(CONF_CUSTOM, default={}): vol.Schema({ - cv.slug: { - vol.Required('key'): vol.All(str, vol.Length(min=13, max=13)), - vol.Required('unit'): str, - vol.Optional('factor', default=1): vol.Coerce(float), - }}) + vol.Optional(CONF_CUSTOM, default={}): + vol.Schema({cv.slug: CUSTOM_SCHEMA}), }, extra=vol.PREVENT_EXTRA), _check_sensor_schema) -async def async_setup_platform(hass, config, async_add_entities, - discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up SMA WebConnect sensor.""" import pysma @@ -146,7 +157,7 @@ async def async_setup_platform(hass, config, async_add_entities, class SMAsensor(Entity): - """Representation of a Bitcoin sensor.""" + """Representation of a SMA sensor.""" def __init__(self, sensor_name, attr, sensor_defs): """Initialize the sensor.""" From 6b7cbca04cbaaff2ead27acae1ac3a4a63649a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 26 Oct 2018 22:43:31 +0100 Subject: [PATCH 078/230] Alexa motion sensor (#17798) * Alexa: add motion sensors * Alexa: add motion sensor tests * Fix comparison and lint --- homeassistant/components/alexa/smart_home.py | 33 ++++++++++++++++++++ tests/components/alexa/test_smart_home.py | 31 ++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index df477a3b653..e83a409d3bf 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -81,6 +81,9 @@ class _DisplayCategory: # Indicates light sources or fixtures. LIGHT = "LIGHT" + # Indicates an endpoint that detects and reports motion. + MOTION_SENSOR = "MOTION_SENSOR" + # An endpoint that cannot be described in on of the other categories. OTHER = "OTHER" @@ -441,6 +444,29 @@ class _AlexaContactSensor(_AlexaInterface): return 'NOT_DETECTED' +class _AlexaMotionSensor(_AlexaInterface): + def __init__(self, hass, entity): + _AlexaInterface.__init__(self, entity) + self.hass = hass + + def name(self): + return 'Alexa.MotionSensor' + + def properties_supported(self): + return [{'name': 'detectionState'}] + + def properties_retrievable(self): + return True + + def get_property(self, name): + if name != 'detectionState': + raise _UnsupportedProperty(name) + + if self.entity.state == STATE_ON: + return 'DETECTED' + return 'NOT_DETECTED' + + class _AlexaThermostatController(_AlexaInterface): def __init__(self, hass, entity): _AlexaInterface.__init__(self, entity) @@ -655,16 +681,21 @@ class _SensorCapabilities(_AlexaEntity): @ENTITY_ADAPTERS.register(binary_sensor.DOMAIN) class _BinarySensorCapabilities(_AlexaEntity): TYPE_CONTACT = 'contact' + TYPE_MOTION = 'motion' def default_display_categories(self): sensor_type = self.get_type() if sensor_type is self.TYPE_CONTACT: return [_DisplayCategory.CONTACT_SENSOR] + if sensor_type is self.TYPE_MOTION: + return [_DisplayCategory.MOTION_SENSOR] def interfaces(self): sensor_type = self.get_type() if sensor_type is self.TYPE_CONTACT: yield _AlexaContactSensor(self.hass, self.entity) + elif sensor_type is self.TYPE_MOTION: + yield _AlexaMotionSensor(self.hass, self.entity) def get_type(self): """Return the type of binary sensor.""" @@ -676,6 +707,8 @@ class _BinarySensorCapabilities(_AlexaEntity): 'window', ): return self.TYPE_CONTACT + if attrs.get(ATTR_DEVICE_CLASS) == 'motion': + return self.TYPE_MOTION class _Cause: diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 9596f341447..9f77a1b8c09 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -725,6 +725,37 @@ def test_contact_sensor(hass): 'DETECTED') +@asyncio.coroutine +def test_motion_sensor(hass): + """Test motion sensor discovery.""" + device = ( + 'binary_sensor.test_motion', + 'on', + { + 'friendly_name': "Test Motion Sensor", + 'device_class': 'motion', + } + ) + appliance = yield from discovery_test(device, hass) + + assert appliance['endpointId'] == 'binary_sensor#test_motion' + assert appliance['displayCategories'][0] == 'MOTION_SENSOR' + assert appliance['friendlyName'] == 'Test Motion Sensor' + + (capability,) = assert_endpoint_capabilities( + appliance, + 'Alexa.MotionSensor') + assert capability['interface'] == 'Alexa.MotionSensor' + properties = capability['properties'] + assert properties['retrievable'] is True + assert {'name': 'detectionState'} in properties['supported'] + + properties = yield from reported_properties(hass, + 'binary_sensor#test_motion') + properties.assert_equal('Alexa.MotionSensor', 'detectionState', + 'DETECTED') + + @asyncio.coroutine def test_unknown_sensor(hass): """Test sensors of unknown quantities are not discovered.""" From 21870e2167f7766a5e3aff73021d36b6e33f98f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sat, 27 Oct 2018 09:17:48 +0200 Subject: [PATCH 079/230] Mill device state attributes (#17834) * Mill device state attributes * lower case --- homeassistant/components/climate/mill.py | 22 +++++++++++++++++----- requirements_all.txt | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/climate/mill.py index 11ad83bdbcc..8f058a3f05e 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -8,16 +8,18 @@ https://home-assistant.io/components/climate.mill/ import logging import voluptuous as vol + from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_FAN_MODE, SUPPORT_ON_OFF) + ClimateDevice, PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, + SUPPORT_ON_OFF) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, STATE_ON, STATE_OFF, TEMP_CELSIUS) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -REQUIREMENTS = ['millheater==0.2.0'] +REQUIREMENTS = ['millheater==0.2.1'] _LOGGER = logging.getLogger(__name__) @@ -43,8 +45,7 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Failed to connect to Mill") return - await mill_data_connection.update_rooms() - await mill_data_connection.update_heaters() + await mill_data_connection.find_all_heaters() dev = [] for heater in mill_data_connection.heaters.values(): @@ -80,6 +81,17 @@ class MillHeater(ClimateDevice): """Return the name of the entity.""" return self._heater.name + @property + def device_state_attributes(self): + """Return the state attributes.""" + if self._heater.room: + room = self._heater.room.name + else: + room = "Independent device" + return {"room": room, + "open_window": self._heater.open_window, + "heating": self._heater.is_heating} + @property def temperature_unit(self): """Return the unit of measurement which this thermostat uses.""" diff --git a/requirements_all.txt b/requirements_all.txt index 8dd576e9583..8d86af1429a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -612,7 +612,7 @@ mficlient==0.3.0 miflora==0.4.0 # homeassistant.components.climate.mill -millheater==0.2.0 +millheater==0.2.1 # homeassistant.components.sensor.mitemp_bt mitemp_bt==0.0.1 From 2adf5918f574dbe75eb94822d929092d9176babb Mon Sep 17 00:00:00 2001 From: Ryan Wagoner Date: Sat, 27 Oct 2018 05:57:04 -0400 Subject: [PATCH 080/230] Fix Alexa unsupported operation_mode off (#17844) --- homeassistant/components/alexa/smart_home.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index e83a409d3bf..d3c18131cfc 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -42,6 +42,7 @@ API_THERMOSTAT_MODES = { climate.STATE_COOL: 'COOL', climate.STATE_AUTO: 'AUTO', climate.STATE_ECO: 'ECO', + climate.STATE_OFF: 'OFF', climate.STATE_IDLE: 'OFF', climate.STATE_FAN_ONLY: 'OFF', climate.STATE_DRY: 'OFF', From 92e9c2aa724bee8e7a29cc72b0ab3253b451fea7 Mon Sep 17 00:00:00 2001 From: Florian Klien Date: Sat, 27 Oct 2018 14:23:04 +0200 Subject: [PATCH 081/230] adding myself as yessssms codeowner (#17862) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 2bf31378ac3..55400d15920 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -87,6 +87,7 @@ homeassistant/components/notify/mastodon.py @fabaff homeassistant/components/notify/smtp.py @fabaff homeassistant/components/notify/syslog.py @fabaff homeassistant/components/notify/xmpp.py @fabaff +homeassistant/components/notify/yessssms.py @flowolf homeassistant/components/plant.py @ChristianKuehnel homeassistant/components/scene/lifx_cloud.py @amelchio homeassistant/components/sensor/airvisual.py @bachya From a22aad50e1b4e191615cb3e119149f6a27667326 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 27 Oct 2018 11:07:08 -0600 Subject: [PATCH 082/230] Fixes an issue with OpenUV config import failing (#17831) * Fixes an issue with OpenUV config import failing * Update * Update __init__.py * Update config_flow.py --- homeassistant/components/openuv/__init__.py | 44 +++++++++++-------- .../components/openuv/config_flow.py | 27 ++++-------- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 52cf0ba75d5..bc29910a196 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -109,26 +109,30 @@ async def async_setup(hass, config): return True conf = config[DOMAIN] - latitude = conf.get(CONF_LATITUDE) - longitude = conf.get(CONF_LONGITUDE) - identifier = '{0}, {1}'.format(latitude, longitude) + identifier = '{0}, {1}'.format( + conf.get(CONF_LATITUDE, hass.config.latitude), + conf.get(CONF_LONGITUDE, hass.config.longitude)) if identifier in configured_instances(hass): return True + data = { + CONF_API_KEY: conf[CONF_API_KEY], + CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS], + CONF_SENSORS: conf[CONF_SENSORS], + CONF_SCAN_INTERVAL: conf[CONF_SCAN_INTERVAL], + } + + if CONF_LATITUDE in conf: + data[CONF_LATITUDE] = conf[CONF_LATITUDE] + if CONF_LONGITUDE in conf: + data[CONF_LONGITUDE] = conf[CONF_LONGITUDE] + if CONF_ELEVATION in conf: + data[CONF_ELEVATION] = conf[CONF_ELEVATION] + hass.async_create_task( hass.config_entries.flow.async_init( - DOMAIN, - context={'source': SOURCE_IMPORT}, - data={ - CONF_API_KEY: conf[CONF_API_KEY], - CONF_LATITUDE: latitude, - CONF_LONGITUDE: longitude, - CONF_ELEVATION: conf.get(CONF_ELEVATION), - CONF_BINARY_SENSORS: conf[CONF_BINARY_SENSORS], - CONF_SENSORS: conf[CONF_SENSORS], - CONF_SCAN_INTERVAL: conf[CONF_SCAN_INTERVAL], - })) + DOMAIN, context={'source': SOURCE_IMPORT}, data=data)) return True @@ -143,10 +147,11 @@ async def async_setup_entry(hass, config_entry): openuv = OpenUV( Client( config_entry.data[CONF_API_KEY], - config_entry.data[CONF_LATITUDE], - config_entry.data[CONF_LONGITUDE], + config_entry.data.get(CONF_LATITUDE, hass.config.latitude), + config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), websession, - altitude=config_entry.data[CONF_ELEVATION]), + altitude=config_entry.data.get( + CONF_ELEVATION, hass.config.elevation)), config_entry.data.get(CONF_BINARY_SENSORS, {}).get( CONF_MONITORED_CONDITIONS, list(BINARY_SENSORS)), config_entry.data.get(CONF_SENSORS, {}).get( @@ -158,8 +163,9 @@ async def async_setup_entry(hass, config_entry): raise ConfigEntryNotReady for component in ('binary_sensor', 'sensor'): - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, component)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + config_entry, component)) async def refresh(event_time): """Refresh OpenUV data.""" diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 27ffe5c3985..11301baf5c5 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -17,7 +17,8 @@ def configured_instances(hass): """Return a set of configured OpenUV instances.""" return set( '{0}, {1}'.format( - entry.data[CONF_LATITUDE], entry.data[CONF_LONGITUDE]) + entry.data.get(CONF_LATITUDE, hass.config.latitude), + entry.data.get(CONF_LONGITUDE, hass.config.longitude)) for entry in hass.config_entries.async_entries(DOMAIN)) @@ -36,12 +37,9 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): """Show the form to the user.""" data_schema = vol.Schema({ vol.Required(CONF_API_KEY): str, - vol.Optional(CONF_LATITUDE, default=self.hass.config.latitude): - cv.latitude, - vol.Optional(CONF_LONGITUDE, default=self.hass.config.longitude): - cv.longitude, - vol.Optional(CONF_ELEVATION, default=self.hass.config.elevation): - vol.Coerce(float), + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_ELEVATION): vol.Coerce(float), }) return self.async_show_form( @@ -61,11 +59,9 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): if not user_input: return await self._show_form() - latitude = user_input[CONF_LATITUDE] - longitude = user_input[CONF_LONGITUDE] - elevation = user_input[CONF_ELEVATION] - - identifier = '{0}, {1}'.format(latitude, longitude) + identifier = '{0}, {1}'.format( + user_input.get(CONF_LATITUDE, self.hass.config.latitude), + user_input.get(CONF_LONGITUDE, self.hass.config.longitude)) if identifier in configured_instances(self.hass): return await self._show_form({CONF_LATITUDE: 'identifier_exists'}) @@ -78,11 +74,6 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): scan_interval = user_input.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) - user_input.update({ - CONF_LATITUDE: latitude, - CONF_LONGITUDE: longitude, - CONF_ELEVATION: elevation, - CONF_SCAN_INTERVAL: scan_interval.seconds, - }) + user_input[CONF_SCAN_INTERVAL] = scan_interval.seconds return self.async_create_entry(title=identifier, data=user_input) From 649bc55a3b61dffba49921ed23b34072ae72e6d5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 27 Oct 2018 21:34:33 +0200 Subject: [PATCH 083/230] Allow a list ofr update entity (#17860) * Allow a list ofr update entity * Update services.yaml * Update services.yaml --- homeassistant/components/__init__.py | 9 ++++++--- homeassistant/components/services.yaml | 6 ++++++ tests/components/test_init.py | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index bdb89dd60fa..8715f0baa96 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -31,7 +31,7 @@ SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' SERVICE_CHECK_CONFIG = 'check_config' SERVICE_UPDATE_ENTITY = 'update_entity' SCHEMA_UPDATE_ENTITY = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_id + ATTR_ENTITY_ID: cv.entity_ids }) @@ -142,8 +142,11 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: async def async_handle_update_service(call): """Service handler for updating an entity.""" - await hass.helpers.entity_component.async_update_entity( - call.data[ATTR_ENTITY_ID]) + tasks = [hass.helpers.entity_component.async_update_entity(entity) + for entity in call.data[ATTR_ENTITY_ID]] + + if tasks: + await asyncio.wait(tasks) hass.services.async_register( ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 62da489aab4..e8512d67fc4 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -527,6 +527,12 @@ homeassistant: entity_id: description: The entity_id of the device to turn off. example: light.living_room + update_entity: + description: Force one or more entities to update its data + fields: + entity_id: + description: One or multiple entity_ids to update. Can be a list. + example: light.living_room xiaomi_aqara: play_ringtone: diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 768951d46b3..da06927db3a 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -364,7 +364,7 @@ async def test_entity_update(hass): with patch('homeassistant.helpers.entity_component.async_update_entity', return_value=mock_coro()) as mock_update: await hass.services.async_call('homeassistant', 'update_entity', { - 'entity_id': 'light.kitchen' + 'entity_id': ['light.kitchen'] }, blocking=True) assert len(mock_update.mock_calls) == 1 From d6e4208665a598842ecacbf4fd86411b060243a8 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Sat, 27 Oct 2018 12:37:07 -0700 Subject: [PATCH 084/230] Switch to using Client from twilio.rest rather than the deleted TwilioRestClient (#17883) --- homeassistant/components/twilio/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py index d7196a034ce..c28f56a4b6c 100644 --- a/homeassistant/components/twilio/__init__.py +++ b/homeassistant/components/twilio/__init__.py @@ -32,12 +32,12 @@ CONFIG_SCHEMA = vol.Schema({ async def async_setup(hass, config): """Set up the Twilio component.""" - from twilio.rest import TwilioRestClient + from twilio.rest import Client if DOMAIN not in config: return True conf = config[DOMAIN] - hass.data[DATA_TWILIO] = TwilioRestClient( + hass.data[DATA_TWILIO] = Client( conf.get(CONF_ACCOUNT_SID), conf.get(CONF_AUTH_TOKEN)) return True From 9cd8a86eb4f9fec8f32105aa0bac26d70a02c059 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 27 Oct 2018 23:51:40 +0200 Subject: [PATCH 085/230] Move migrate to separate WS command (#17890) --- homeassistant/components/lovelace/__init__.py | 38 +++++++++++++++++++ tests/components/lovelace/test_init.py | 8 ++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index e81af9e0d27..ec131c8a4d9 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -24,6 +24,7 @@ FORMAT_JSON = 'json' OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' +WS_TYPE_MIGRATE_CONFIG = 'lovelace/config/migrate' WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' WS_TYPE_ADD_CARD = 'lovelace/config/card/add' @@ -35,6 +36,10 @@ SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ OLD_WS_TYPE_GET_LOVELACE_UI), }) +SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_MIGRATE_CONFIG, +}) + SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_CARD, vol.Required('card_id'): str, @@ -150,6 +155,11 @@ def load_yaml(fname: str) -> JSON_TYPE: def load_config(fname: str) -> JSON_TYPE: + """Load a YAML file.""" + return load_yaml(fname) + + +def migrate_config(fname: str) -> JSON_TYPE: """Load a YAML file and adds id to views and cards if not present.""" config = load_yaml(fname) # Check if all views and cards have a unique id or else add one @@ -341,6 +351,10 @@ async def async_setup(hass, config): OLD_WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) + hass.components.websocket_api.async_register_command( + WS_TYPE_MIGRATE_CONFIG, websocket_lovelace_migrate_config, + SCHEMA_MIGRATE_CONFIG) + hass.components.websocket_api.async_register_command( WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) @@ -392,6 +406,30 @@ async def websocket_lovelace_config(hass, connection, msg): connection.send_message(message) +@websocket_api.async_response +async def websocket_lovelace_migrate_config(hass, connection, msg): + """Migrate lovelace UI config.""" + error = None + try: + config = await hass.async_add_executor_job( + migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) + message = websocket_api.result_message( + msg['id'], config + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except HomeAssistantError as err: + error = 'load_error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) + + @websocket_api.async_response async def websocket_lovelace_get_card(hass, connection, msg): """Send lovelace card config over websocket config.""" diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 21362a32193..212bd9e2722 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -9,8 +9,8 @@ from ruamel.yaml import YAML from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.components.lovelace import (load_yaml, - save_yaml, load_config, +from homeassistant.components.lovelace import (load_yaml, migrate_config, + save_yaml, UnsupportedYamlError) TEST_YAML_A = """\ @@ -164,7 +164,7 @@ class TestYAML(unittest.TestCase): with patch('homeassistant.components.lovelace.load_yaml', return_value=self.yaml.load(TEST_YAML_A)), \ patch('homeassistant.components.lovelace.save_yaml'): - data = load_config(fname) + data = migrate_config(fname) assert 'id' in data['views'][0]['cards'][0] assert 'id' in data['views'][1] @@ -173,7 +173,7 @@ class TestYAML(unittest.TestCase): fname = self._path_for("test7") with patch('homeassistant.components.lovelace.load_yaml', return_value=self.yaml.load(TEST_YAML_B)): - data = load_config(fname) + data = migrate_config(fname) assert data == self.yaml.load(TEST_YAML_B) From cbadd64b28f8327e9cb48a2573450054123db47e Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Sun, 28 Oct 2018 03:35:57 -0400 Subject: [PATCH 086/230] Fix Vera climate component to use correct states (#17892) Changed the Vera climate component so it uses the STATE_* states from the base climate component. This will allow it to work with Google Assistant. --- homeassistant/components/climate/vera.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index e97bd6cd8ad..5e016b8666b 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -8,9 +8,12 @@ import logging from homeassistant.util import convert from homeassistant.components.climate import ( - ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, STATE_AUTO, STATE_COOL, + STATE_HEAT, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE) from homeassistant.const import ( + STATE_ON, + STATE_OFF, TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE) @@ -22,8 +25,8 @@ DEPENDENCIES = ['vera'] _LOGGER = logging.getLogger(__name__) -OPERATION_LIST = ['Heat', 'Cool', 'Auto Changeover', 'Off'] -FAN_OPERATION_LIST = ['On', 'Auto', 'Cycle'] +OPERATION_LIST = [STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_OFF] +FAN_OPERATION_LIST = [STATE_ON, STATE_AUTO] SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE) @@ -54,13 +57,13 @@ class VeraThermostat(VeraDevice, ClimateDevice): """Return current operation ie. heat, cool, idle.""" mode = self.vera_device.get_hvac_mode() if mode == 'HeatOn': - return OPERATION_LIST[0] # heat + return OPERATION_LIST[0] # Heat if mode == 'CoolOn': - return OPERATION_LIST[1] # cool + return OPERATION_LIST[1] # Cool if mode == 'AutoChangeOver': - return OPERATION_LIST[2] # auto + return OPERATION_LIST[2] # Auto if mode == 'Off': - return OPERATION_LIST[3] # off + return OPERATION_LIST[3] # Off return 'Off' @property @@ -76,8 +79,6 @@ class VeraThermostat(VeraDevice, ClimateDevice): return FAN_OPERATION_LIST[0] # on if mode == "Auto": return FAN_OPERATION_LIST[1] # auto - if mode == "PeriodicOn": - return FAN_OPERATION_LIST[2] # cycle return "Auto" @property @@ -89,10 +90,8 @@ class VeraThermostat(VeraDevice, ClimateDevice): """Set new target temperature.""" if fan_mode == FAN_OPERATION_LIST[0]: self.vera_device.fan_on() - elif fan_mode == FAN_OPERATION_LIST[1]: + else: self.vera_device.fan_auto() - elif fan_mode == FAN_OPERATION_LIST[2]: - return self.vera_device.fan_cycle() @property def current_power_w(self): From 0acd4b28f9d433fa47db0991a4559c8d81332d49 Mon Sep 17 00:00:00 2001 From: Lindsay Ward Date: Sun, 28 Oct 2018 21:18:44 +1000 Subject: [PATCH 087/230] Add myself to CODEOWNERS for Yeelight Sunflower light platform (#17896) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 55400d15920..b4bd5883393 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -68,6 +68,7 @@ homeassistant/components/influx.py @fabaff homeassistant/components/light/lifx_legacy.py @amelchio homeassistant/components/light/tplink.py @rytilahti homeassistant/components/light/yeelight.py @rytilahti +homeassistant/components/light/yeelightsunflower.py @lindsaymarkward homeassistant/components/lock/nello.py @pschmitt homeassistant/components/lock/nuki.py @pschmitt homeassistant/components/media_player/emby.py @mezz64 From 6cb735271fec26cee606dc09fbb73ef740ddfdb4 Mon Sep 17 00:00:00 2001 From: Evan Bruhn Date: Sun, 28 Oct 2018 23:46:28 +1100 Subject: [PATCH 088/230] Fix logi_circle sensor update method naming (#17909) Resolves regression in 0.81 --- homeassistant/components/sensor/logi_circle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/logi_circle.py b/homeassistant/components/sensor/logi_circle.py index a0a2ca96444..104de68ce03 100644 --- a/homeassistant/components/sensor/logi_circle.py +++ b/homeassistant/components/sensor/logi_circle.py @@ -137,7 +137,7 @@ class LogiSensor(Entity): """Return the units of measurement.""" return SENSOR_TYPES.get(self._sensor_type)[1] - async def update(self): + async def async_update(self): """Get the latest data and updates the state.""" _LOGGER.debug("Pulling data from %s sensor", self._name) await self._camera.update() From 3f3955c1cdc4f514f28f797938d7f18e5d4a6c49 Mon Sep 17 00:00:00 2001 From: emontnemery Date: Sun, 28 Oct 2018 13:57:44 +0100 Subject: [PATCH 089/230] Fix RFLink issue #17875 (#17889) --- homeassistant/components/rflink.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py index 3bb3bb7044b..b3c58da1076 100644 --- a/homeassistant/components/rflink.py +++ b/homeassistant/components/rflink.py @@ -492,8 +492,7 @@ class RflinkCommand(RflinkDevice): # Rflink protocol/transport handles asynchronous writing of buffer # to serial/tcp device. Does not wait for command send # confirmation. - self.hass.async_create_task(self._protocol.send_command( - self._device_id, cmd)) + self._protocol.send_command(self._device_id, cmd) if repetitions > 1: self._repetition_task = self.hass.async_create_task( From 38576e5b740acb02407676189f4d7d85e986c113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=81uci=C3=B3w?= Date: Sun, 28 Oct 2018 14:53:47 +0100 Subject: [PATCH 090/230] Corrected fan device type in google assistant to fan (#17792) --- homeassistant/components/google_assistant/const.py | 1 + homeassistant/components/google_assistant/smart_home.py | 4 ++-- tests/components/google_assistant/__init__.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 7d9fa44442c..2f54ee33f77 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -25,6 +25,7 @@ TYPE_LIGHT = PREFIX_TYPES + 'LIGHT' TYPE_SWITCH = PREFIX_TYPES + 'SWITCH' TYPE_VACUUM = PREFIX_TYPES + 'VACUUM' TYPE_SCENE = PREFIX_TYPES + 'SCENE' +TYPE_FAN = PREFIX_TYPES + 'FAN' TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT' SERVICE_REQUEST_SYNC = 'request_sync' diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index da6211b1911..633e6258c03 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -25,7 +25,7 @@ from homeassistant.components import ( from . import trait from .const import ( TYPE_LIGHT, TYPE_SCENE, TYPE_SWITCH, TYPE_VACUUM, - TYPE_THERMOSTAT, + TYPE_THERMOSTAT, TYPE_FAN, CONF_ALIASES, CONF_ROOM_HINT, ERR_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR @@ -38,7 +38,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN_TO_GOOGLE_TYPES = { climate.DOMAIN: TYPE_THERMOSTAT, cover.DOMAIN: TYPE_SWITCH, - fan.DOMAIN: TYPE_SWITCH, + fan.DOMAIN: TYPE_FAN, group.DOMAIN: TYPE_SWITCH, input_boolean.DOMAIN: TYPE_SWITCH, light.DOMAIN: TYPE_LIGHT, diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 6c4dd713b32..273a7e86505 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -184,7 +184,7 @@ DEMO_DEVICES = [{ 'name': 'Living Room Fan' }, 'traits': ['action.devices.traits.OnOff'], - 'type': 'action.devices.types.SWITCH', + 'type': 'action.devices.types.FAN', 'willReportState': False }, { 'id': 'fan.ceiling_fan', @@ -192,7 +192,7 @@ DEMO_DEVICES = [{ 'name': 'Ceiling Fan' }, 'traits': ['action.devices.traits.OnOff'], - 'type': 'action.devices.types.SWITCH', + 'type': 'action.devices.types.FAN', 'willReportState': False }, { 'id': 'group.all_fans', From 60080a529df59a0b0c9ff108433ec126a4bef3a0 Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Sun, 28 Oct 2018 11:25:43 -0700 Subject: [PATCH 091/230] Migrate dialogflow over to the new webhook component (#17804) * Migrate dialogflow over to the new webhook component * Updating dialogflow unit tests * Lint * Revert changes to HomeAssistantView * Use json_response from aiohttp --- .../dialogflow/.translations/en.json | 18 + .../{dialogflow.py => dialogflow/__init__.py} | 106 ++-- .../components/dialogflow/strings.json | 18 + homeassistant/config_entries.py | 1 + tests/components/dialogflow/__init__.py | 1 + tests/components/dialogflow/test_init.py | 529 ++++++++++++++++++ tests/components/test_dialogflow.py | 525 ----------------- 7 files changed, 626 insertions(+), 572 deletions(-) create mode 100644 homeassistant/components/dialogflow/.translations/en.json rename homeassistant/components/{dialogflow.py => dialogflow/__init__.py} (58%) create mode 100644 homeassistant/components/dialogflow/strings.json create mode 100644 tests/components/dialogflow/__init__.py create mode 100644 tests/components/dialogflow/test_init.py delete mode 100644 tests/components/test_dialogflow.py diff --git a/homeassistant/components/dialogflow/.translations/en.json b/homeassistant/components/dialogflow/.translations/en.json new file mode 100644 index 00000000000..4a3e91a3e50 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Dialogflow", + "step": { + "user": { + "title": "Set up the Dialogflow Webhook", + "description": "Are you sure you want to set up Dialogflow?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details." + } + } +} diff --git a/homeassistant/components/dialogflow.py b/homeassistant/components/dialogflow/__init__.py similarity index 58% rename from homeassistant/components/dialogflow.py rename to homeassistant/components/dialogflow/__init__.py index 0f275a7fe66..900dae5c7c1 100644 --- a/homeassistant/components/dialogflow.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -7,24 +7,16 @@ https://home-assistant.io/components/dialogflow/ import logging import voluptuous as vol +from aiohttp import web +from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import intent, template -from homeassistant.components.http import HomeAssistantView - +from homeassistant.helpers import intent, template, config_entry_flow _LOGGER = logging.getLogger(__name__) -CONF_INTENTS = 'intents' -CONF_SPEECH = 'speech' -CONF_ACTION = 'action' -CONF_ASYNC_ACTION = 'async_action' - -DEFAULT_CONF_ASYNC_ACTION = False -DEPENDENCIES = ['http'] +DEPENDENCIES = ['webhook'] DOMAIN = 'dialogflow' -INTENTS_API_ENDPOINT = '/api/dialogflow' - SOURCE = "Home Assistant Dialogflow" CONFIG_SCHEMA = vol.Schema({ @@ -38,52 +30,72 @@ class DialogFlowError(HomeAssistantError): async def async_setup(hass, config): """Set up Dialogflow component.""" - hass.http.register_view(DialogflowIntentsView) - return True -class DialogflowIntentsView(HomeAssistantView): - """Handle Dialogflow requests.""" +async def handle_webhook(hass, webhook_id, request): + """Handle incoming webhook with Dialogflow requests.""" + message = await request.json() - url = INTENTS_API_ENDPOINT - name = 'api:dialogflow' + _LOGGER.debug("Received Dialogflow request: %s", message) - async def post(self, request): - """Handle Dialogflow.""" - hass = request.app['hass'] - message = await request.json() + try: + response = await async_handle_message(hass, message) + return b'' if response is None else web.json_response(response) - _LOGGER.debug("Received Dialogflow request: %s", message) + except DialogFlowError as err: + _LOGGER.warning(str(err)) + return web.json_response( + dialogflow_error_response(message, str(err)) + ) - try: - response = await async_handle_message(hass, message) - return b'' if response is None else self.json(response) + except intent.UnknownIntent as err: + _LOGGER.warning(str(err)) + return web.json_response( + dialogflow_error_response( + message, + "This intent is not yet configured within Home Assistant." + ) + ) - except DialogFlowError as err: - _LOGGER.warning(str(err)) - return self.json(dialogflow_error_response( - hass, message, str(err))) + except intent.InvalidSlotInfo as err: + _LOGGER.warning(str(err)) + return web.json_response( + dialogflow_error_response( + message, + "Invalid slot information received for this intent." + ) + ) - except intent.UnknownIntent as err: - _LOGGER.warning(str(err)) - return self.json(dialogflow_error_response( - hass, message, - "This intent is not yet configured within Home Assistant.")) - - except intent.InvalidSlotInfo as err: - _LOGGER.warning(str(err)) - return self.json(dialogflow_error_response( - hass, message, - "Invalid slot information received for this intent.")) - - except intent.IntentError as err: - _LOGGER.warning(str(err)) - return self.json(dialogflow_error_response( - hass, message, "Error handling intent.")) + except intent.IntentError as err: + _LOGGER.warning(str(err)) + return web.json_response( + dialogflow_error_response(message, "Error handling intent.")) -def dialogflow_error_response(hass, message, error): +async def async_setup_entry(hass, entry): + """Configure based on config entry.""" + hass.components.webhook.async_register( + entry.data[CONF_WEBHOOK_ID], handle_webhook) + return True + + +async def async_unload_entry(hass, entry): + """Unload a config entry.""" + hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID]) + return True + +config_entry_flow.register_webhook_flow( + DOMAIN, + 'Dialogflow Webhook', + { + 'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook', + 'docs_url': 'https://www.home-assistant.io/components/dialogflow/' + } +) + + +def dialogflow_error_response(message, error): """Return a response saying the error message.""" dialogflow_response = DialogflowResponse(message['result']['parameters']) dialogflow_response.add_speech(error) diff --git a/homeassistant/components/dialogflow/strings.json b/homeassistant/components/dialogflow/strings.json new file mode 100644 index 00000000000..4a3e91a3e50 --- /dev/null +++ b/homeassistant/components/dialogflow/strings.json @@ -0,0 +1,18 @@ +{ + "config": { + "title": "Dialogflow", + "step": { + "user": { + "title": "Set up the Dialogflow Webhook", + "description": "Are you sure you want to set up Dialogflow?" + } + }, + "abort": { + "one_instance_allowed": "Only a single instance is necessary.", + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details." + } + } +} diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d37bd8cb558..eaef97011fc 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -137,6 +137,7 @@ HANDLERS = Registry() FLOWS = [ 'cast', 'deconz', + 'dialogflow', 'hangouts', 'homematicip_cloud', 'hue', diff --git a/tests/components/dialogflow/__init__.py b/tests/components/dialogflow/__init__.py new file mode 100644 index 00000000000..b9fdf70afb1 --- /dev/null +++ b/tests/components/dialogflow/__init__.py @@ -0,0 +1 @@ +"""Tests for the Dialogflow component.""" diff --git a/tests/components/dialogflow/test_init.py b/tests/components/dialogflow/test_init.py new file mode 100644 index 00000000000..a2ac5b85d07 --- /dev/null +++ b/tests/components/dialogflow/test_init.py @@ -0,0 +1,529 @@ +"""The tests for the Dialogflow component.""" +import json +from unittest.mock import Mock + +import pytest + +from homeassistant import data_entry_flow +from homeassistant.components import dialogflow, intent_script +from homeassistant.core import callback +from homeassistant.setup import async_setup_component + +SESSION_ID = "a9b84cec-46b6-484e-8f31-f65dba03ae6d" +INTENT_ID = "c6a74079-a8f0-46cd-b372-5a934d23591c" +INTENT_NAME = "tests" +REQUEST_ID = "19ef7e78-fe15-4e94-99dd-0c0b1e8753c3" +REQUEST_TIMESTAMP = "2017-01-21T17:54:18.952Z" +CONTEXT_NAME = "78a5db95-b7d6-4d50-9c9b-2fc73a5e34c3_id_dialog_context" + +calls = [] + + +@pytest.fixture +async def fixture(hass, aiohttp_client): + """Initialize a Home Assistant server for testing this module.""" + @callback + def mock_service(call): + """Mock action call.""" + calls.append(call) + + hass.services.async_register('test', 'dialogflow', mock_service) + + await async_setup_component(hass, dialogflow.DOMAIN, { + "dialogflow": {}, + }) + await async_setup_component(hass, intent_script.DOMAIN, { + "intent_script": { + "WhereAreWeIntent": { + "speech": { + "type": "plain", + "text": """ + {%- if is_state("device_tracker.paulus", "home") + and is_state("device_tracker.anne_therese", + "home") -%} + You are both home, you silly + {%- else -%} + Anne Therese is at {{ + states("device_tracker.anne_therese") + }} and Paulus is at {{ + states("device_tracker.paulus") + }} + {% endif %} + """, + } + }, + "GetZodiacHoroscopeIntent": { + "speech": { + "type": "plain", + "text": "You told us your sign is {{ ZodiacSign }}.", + } + }, + "CallServiceIntent": { + "speech": { + "type": "plain", + "text": "Service called", + }, + "action": { + "service": "test.dialogflow", + "data_template": { + "hello": "{{ ZodiacSign }}" + }, + "entity_id": "switch.test", + } + } + } + }) + + hass.config.api = Mock(base_url='http://example.com') + result = await hass.config_entries.flow.async_init( + 'dialogflow', + context={ + 'source': 'user' + } + ) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], {}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + webhook_id = result['result'].data['webhook_id'] + + return await aiohttp_client(hass.http.app), webhook_id + + +async def test_intent_action_incomplete(fixture): + """Test when action is not completed.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "GetZodiacHoroscopeIntent", + "actionIncomplete": True, + "parameters": { + "ZodiacSign": "virgo" + }, + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + assert "" == await response.text() + + +async def test_intent_slot_filling(fixture): + """Test when Dialogflow asks for slot-filling return none.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is", + "speech": "", + "action": "GetZodiacHoroscopeIntent", + "actionIncomplete": True, + "parameters": { + "ZodiacSign": "" + }, + "contexts": [ + { + "name": CONTEXT_NAME, + "parameters": { + "ZodiacSign.original": "", + "ZodiacSign": "" + }, + "lifespan": 2 + }, + { + "name": "tests_ha_dialog_context", + "parameters": { + "ZodiacSign.original": "", + "ZodiacSign": "" + }, + "lifespan": 2 + }, + { + "name": "tests_ha_dialog_params_zodiacsign", + "parameters": { + "ZodiacSign.original": "", + "ZodiacSign": "" + }, + "lifespan": 1 + } + ], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "true", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "What is the ZodiacSign?", + "messages": [ + { + "type": 0, + "speech": "What is the ZodiacSign?" + } + ] + }, + "score": 0.77 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + assert "" == await response.text() + + +async def test_intent_request_with_parameters(fixture): + """Test a request with parameters.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "GetZodiacHoroscopeIntent", + "actionIncomplete": False, + "parameters": { + "ZodiacSign": "virgo" + }, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + assert "You told us your sign is virgo." == text + + +async def test_intent_request_with_parameters_but_empty(fixture): + """Test a request with parameters but empty value.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "GetZodiacHoroscopeIntent", + "actionIncomplete": False, + "parameters": { + "ZodiacSign": "" + }, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + assert "You told us your sign is ." == text + + +async def test_intent_request_without_slots(hass, fixture): + """Test a request without slots.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "where are we", + "speech": "", + "action": "WhereAreWeIntent", + "actionIncomplete": False, + "parameters": {}, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + + assert "Anne Therese is at unknown and Paulus is at unknown" == \ + text + + hass.states.async_set("device_tracker.paulus", "home") + hass.states.async_set("device_tracker.anne_therese", "home") + + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + assert "You are both home, you silly" == text + + +async def test_intent_request_calling_service(fixture): + """Test a request for calling a service. + + If this request is done async the test could finish before the action + has been executed. Hard to test because it will be a race condition. + """ + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "CallServiceIntent", + "actionIncomplete": False, + "parameters": { + "ZodiacSign": "virgo" + }, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + call_count = len(calls) + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + assert call_count + 1 == len(calls) + call = calls[-1] + assert "test" == call.domain + assert "dialogflow" == call.service + assert ["switch.test"] == call.data.get("entity_id") + assert "virgo" == call.data.get("hello") + + +async def test_intent_with_no_action(fixture): + """Test an intent with no defined action.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "", + "actionIncomplete": False, + "parameters": { + "ZodiacSign": "" + }, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + assert \ + "You have not defined an action in your Dialogflow intent." == text + + +async def test_intent_with_unknown_action(fixture): + """Test an intent with an action not defined in the conf.""" + mock_client, webhook_id = fixture + data = { + "id": REQUEST_ID, + "timestamp": REQUEST_TIMESTAMP, + "result": { + "source": "agent", + "resolvedQuery": "my zodiac sign is virgo", + "speech": "", + "action": "unknown", + "actionIncomplete": False, + "parameters": { + "ZodiacSign": "" + }, + "contexts": [], + "metadata": { + "intentId": INTENT_ID, + "webhookUsed": "true", + "webhookForSlotFillingUsed": "false", + "intentName": INTENT_NAME + }, + "fulfillment": { + "speech": "", + "messages": [ + { + "type": 0, + "speech": "" + } + ] + }, + "score": 1 + }, + "status": { + "code": 200, + "errorType": "success" + }, + "sessionId": SESSION_ID, + "originalRequest": None + } + response = await mock_client.post( + '/api/webhook/{}'.format(webhook_id), + data=json.dumps(data) + ) + assert 200 == response.status + text = (await response.json()).get("speech") + assert \ + "This intent is not yet configured within Home Assistant." == text diff --git a/tests/components/test_dialogflow.py b/tests/components/test_dialogflow.py deleted file mode 100644 index e05e33b30d6..00000000000 --- a/tests/components/test_dialogflow.py +++ /dev/null @@ -1,525 +0,0 @@ -"""The tests for the Dialogflow component.""" -# pylint: disable=protected-access -import json -import unittest - -import requests -from aiohttp.hdrs import CONTENT_TYPE - -from homeassistant.core import callback -from homeassistant import setup, const -from homeassistant.components import dialogflow, http - -from tests.common import get_test_instance_port, get_test_home_assistant - -API_PASSWORD = 'test1234' -SERVER_PORT = get_test_instance_port() -BASE_API_URL = "http://127.0.0.1:{}".format(SERVER_PORT) -INTENTS_API_URL = "{}{}".format(BASE_API_URL, dialogflow.INTENTS_API_ENDPOINT) - -HA_HEADERS = { - const.HTTP_HEADER_HA_AUTH: API_PASSWORD, - CONTENT_TYPE: const.CONTENT_TYPE_JSON, -} - -SESSION_ID = "a9b84cec-46b6-484e-8f31-f65dba03ae6d" -INTENT_ID = "c6a74079-a8f0-46cd-b372-5a934d23591c" -INTENT_NAME = "tests" -REQUEST_ID = "19ef7e78-fe15-4e94-99dd-0c0b1e8753c3" -REQUEST_TIMESTAMP = "2017-01-21T17:54:18.952Z" -CONTEXT_NAME = "78a5db95-b7d6-4d50-9c9b-2fc73a5e34c3_id_dialog_context" -MAX_RESPONSE_TIME = 5 # https://dialogflow.com/docs/fulfillment - -# An unknown action takes 8 s to return. Request timeout should be bigger to -# allow the test to finish -REQUEST_TIMEOUT = 15 - -# pylint: disable=invalid-name -hass = None -calls = [] - - -# pylint: disable=invalid-name -def setUpModule(): - """Initialize a Home Assistant server for testing this module.""" - global hass - - hass = get_test_home_assistant() - - setup.setup_component( - hass, http.DOMAIN, { - http.DOMAIN: { - http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: SERVER_PORT, - } - } - ) - - @callback - def mock_service(call): - """Mock action call.""" - calls.append(call) - - hass.services.register('test', 'dialogflow', mock_service) - - assert setup.setup_component(hass, dialogflow.DOMAIN, { - "dialogflow": {}, - }) - assert setup.setup_component(hass, "intent_script", { - "intent_script": { - "WhereAreWeIntent": { - "speech": { - "type": "plain", - "text": """ - {%- if is_state("device_tracker.paulus", "home") - and is_state("device_tracker.anne_therese", - "home") -%} - You are both home, you silly - {%- else -%} - Anne Therese is at {{ - states("device_tracker.anne_therese") - }} and Paulus is at {{ - states("device_tracker.paulus") - }} - {% endif %} - """, - } - }, - "GetZodiacHoroscopeIntent": { - "speech": { - "type": "plain", - "text": "You told us your sign is {{ ZodiacSign }}.", - } - }, - "CallServiceIntent": { - "speech": { - "type": "plain", - "text": "Service called", - }, - "action": { - "service": "test.dialogflow", - "data_template": { - "hello": "{{ ZodiacSign }}" - }, - "entity_id": "switch.test", - } - } - } - }) - - hass.start() - - -# pylint: disable=invalid-name -def tearDownModule(): - """Stop the Home Assistant server.""" - hass.stop() - - -def _intent_req(data): - return requests.post( - INTENTS_API_URL, data=json.dumps(data), timeout=REQUEST_TIMEOUT, - headers=HA_HEADERS) - - -class TestDialogflow(unittest.TestCase): - """Test Dialogflow.""" - - def tearDown(self): - """Stop everything that was started.""" - hass.block_till_done() - - def test_intent_action_incomplete(self): - """Test when action is not completed.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "GetZodiacHoroscopeIntent", - "actionIncomplete": True, - "parameters": { - "ZodiacSign": "virgo" - }, - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - - req = _intent_req(data) - assert 200 == req.status_code - assert "" == req.text - - def test_intent_slot_filling(self): - """Test when Dialogflow asks for slot-filling return none.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is", - "speech": "", - "action": "GetZodiacHoroscopeIntent", - "actionIncomplete": True, - "parameters": { - "ZodiacSign": "" - }, - "contexts": [ - { - "name": CONTEXT_NAME, - "parameters": { - "ZodiacSign.original": "", - "ZodiacSign": "" - }, - "lifespan": 2 - }, - { - "name": "tests_ha_dialog_context", - "parameters": { - "ZodiacSign.original": "", - "ZodiacSign": "" - }, - "lifespan": 2 - }, - { - "name": "tests_ha_dialog_params_zodiacsign", - "parameters": { - "ZodiacSign.original": "", - "ZodiacSign": "" - }, - "lifespan": 1 - } - ], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "true", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "What is the ZodiacSign?", - "messages": [ - { - "type": 0, - "speech": "What is the ZodiacSign?" - } - ] - }, - "score": 0.77 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - - req = _intent_req(data) - assert 200 == req.status_code - assert "" == req.text - - def test_intent_request_with_parameters(self): - """Test a request with parameters.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "GetZodiacHoroscopeIntent", - "actionIncomplete": False, - "parameters": { - "ZodiacSign": "virgo" - }, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - assert "You told us your sign is virgo." == text - - def test_intent_request_with_parameters_but_empty(self): - """Test a request with parameters but empty value.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "GetZodiacHoroscopeIntent", - "actionIncomplete": False, - "parameters": { - "ZodiacSign": "" - }, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - assert "You told us your sign is ." == text - - def test_intent_request_without_slots(self): - """Test a request without slots.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "where are we", - "speech": "", - "action": "WhereAreWeIntent", - "actionIncomplete": False, - "parameters": {}, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - - assert "Anne Therese is at unknown and Paulus is at unknown" == \ - text - - hass.states.set("device_tracker.paulus", "home") - hass.states.set("device_tracker.anne_therese", "home") - - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - assert "You are both home, you silly" == text - - def test_intent_request_calling_service(self): - """Test a request for calling a service. - - If this request is done async the test could finish before the action - has been executed. Hard to test because it will be a race condition. - """ - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "CallServiceIntent", - "actionIncomplete": False, - "parameters": { - "ZodiacSign": "virgo" - }, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - call_count = len(calls) - req = _intent_req(data) - assert 200 == req.status_code - assert call_count + 1 == len(calls) - call = calls[-1] - assert "test" == call.domain - assert "dialogflow" == call.service - assert ["switch.test"] == call.data.get("entity_id") - assert "virgo" == call.data.get("hello") - - def test_intent_with_no_action(self): - """Test an intent with no defined action.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "", - "actionIncomplete": False, - "parameters": { - "ZodiacSign": "" - }, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - assert \ - "You have not defined an action in your Dialogflow intent." == text - - def test_intent_with_unknown_action(self): - """Test an intent with an action not defined in the conf.""" - data = { - "id": REQUEST_ID, - "timestamp": REQUEST_TIMESTAMP, - "result": { - "source": "agent", - "resolvedQuery": "my zodiac sign is virgo", - "speech": "", - "action": "unknown", - "actionIncomplete": False, - "parameters": { - "ZodiacSign": "" - }, - "contexts": [], - "metadata": { - "intentId": INTENT_ID, - "webhookUsed": "true", - "webhookForSlotFillingUsed": "false", - "intentName": INTENT_NAME - }, - "fulfillment": { - "speech": "", - "messages": [ - { - "type": 0, - "speech": "" - } - ] - }, - "score": 1 - }, - "status": { - "code": 200, - "errorType": "success" - }, - "sessionId": SESSION_ID, - "originalRequest": None - } - req = _intent_req(data) - assert 200 == req.status_code - text = req.json().get("speech") - assert \ - "This intent is not yet configured within Home Assistant." == text From 0f877711a0ad4cd45dc9edbfd6f361eaa7fd3a18 Mon Sep 17 00:00:00 2001 From: Luca Angemi Date: Sun, 28 Oct 2018 19:39:23 +0100 Subject: [PATCH 092/230] Fixes Telegram webhooks (#17554) --- homeassistant/components/telegram_bot/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 72e8c557fe5..5406ba60b13 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -120,4 +120,4 @@ class BotPushReceiver(HomeAssistantView, BaseTelegramBotEntity): if not self.process_message(data): return self.json_message('Invalid message', HTTP_BAD_REQUEST) - return self.json({}) + return None From c9c707e368be159f0138a40d21fdea7a2a650ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 28 Oct 2018 21:12:52 +0200 Subject: [PATCH 093/230] Start type annotating/testing helpers (#17858) * Add type hints to helpers.intent and location * Test typing for helpers.icon, json, and typing * Add type hints to helpers.state * Add type hints to helpers.translation --- homeassistant/helpers/intent.py | 64 +++++++++++++++++----------- homeassistant/helpers/location.py | 9 ++-- homeassistant/helpers/state.py | 37 ++++++++++------ homeassistant/helpers/translation.py | 35 +++++++++------ tox.ini | 2 +- 5 files changed, 92 insertions(+), 55 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 8f26d4fe0ee..d942aabccce 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -1,17 +1,20 @@ """Module to coordinate user intentions.""" import logging import re +from typing import Any, Callable, Dict, Iterable, Optional import voluptuous as vol from homeassistant.const import ATTR_SUPPORTED_FEATURES -from homeassistant.core import callback +from homeassistant.core import callback, State, T from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.const import ATTR_ENTITY_ID _LOGGER = logging.getLogger(__name__) +_SlotsType = Dict[str, Any] INTENT_TURN_OFF = 'HassTurnOff' INTENT_TURN_ON = 'HassTurnOn' @@ -28,7 +31,7 @@ SPEECH_TYPE_SSML = 'ssml' @callback @bind_hass -def async_register(hass, handler): +def async_register(hass: HomeAssistantType, handler: 'IntentHandler') -> None: """Register an intent with Home Assistant.""" intents = hass.data.get(DATA_KEY) if intents is None: @@ -44,10 +47,12 @@ def async_register(hass, handler): @bind_hass -async def async_handle(hass, platform, intent_type, slots=None, - text_input=None): +async def async_handle(hass: HomeAssistantType, platform: str, + intent_type: str, slots: Optional[_SlotsType] = None, + text_input: Optional[str] = None) -> 'IntentResponse': """Handle an intent.""" - handler = hass.data.get(DATA_KEY, {}).get(intent_type) + handler = \ + hass.data.get(DATA_KEY, {}).get(intent_type) # type: IntentHandler if handler is None: raise UnknownIntent('Unknown intent {}'.format(intent_type)) @@ -93,7 +98,8 @@ class IntentUnexpectedError(IntentError): @callback @bind_hass -def async_match_state(hass, name, states=None): +def async_match_state(hass: HomeAssistantType, name: str, + states: Optional[Iterable[State]] = None) -> State: """Find a state that matches the name.""" if states is None: states = hass.states.async_all() @@ -108,7 +114,7 @@ def async_match_state(hass, name, states=None): @callback -def async_test_feature(state, feature, feature_name): +def async_test_feature(state: State, feature: int, feature_name: str) -> None: """Test is state supports a feature.""" if state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & feature == 0: raise IntentHandleError( @@ -119,18 +125,18 @@ def async_test_feature(state, feature, feature_name): class IntentHandler: """Intent handler registration.""" - intent_type = None - slot_schema = None + intent_type = None # type: Optional[str] + slot_schema = None # type: Optional[vol.Schema] _slot_schema = None - platforms = [] + platforms = [] # type: Optional[Iterable[str]] @callback - def async_can_handle(self, intent_obj): + def async_can_handle(self, intent_obj: 'Intent') -> bool: """Test if an intent can be handled.""" return self.platforms is None or intent_obj.platform in self.platforms @callback - def async_validate_slots(self, slots): + def async_validate_slots(self, slots: _SlotsType) -> _SlotsType: """Validate slot information.""" if self.slot_schema is None: return slots @@ -141,18 +147,19 @@ class IntentHandler: for key, validator in self.slot_schema.items()}, extra=vol.ALLOW_EXTRA) - return self._slot_schema(slots) + return self._slot_schema(slots) # type: ignore - async def async_handle(self, intent_obj): + async def async_handle(self, intent_obj: 'Intent') -> 'IntentResponse': """Handle the intent.""" raise NotImplementedError() - def __repr__(self): + def __repr__(self) -> str: """Represent a string of an intent handler.""" return '<{} - {}>'.format(self.__class__.__name__, self.intent_type) -def _fuzzymatch(name, items, key): +def _fuzzymatch(name: str, items: Iterable[T], key: Callable[[T], str]) \ + -> Optional[T]: """Fuzzy matching function.""" matches = [] pattern = '.*?'.join(name) @@ -176,14 +183,15 @@ class ServiceIntentHandler(IntentHandler): vol.Required('name'): cv.string, } - def __init__(self, intent_type, domain, service, speech): + def __init__(self, intent_type: str, domain: str, service: str, + speech: str) -> None: """Create Service Intent Handler.""" self.intent_type = intent_type self.domain = domain self.service = service self.speech = speech - async def async_handle(self, intent_obj): + async def async_handle(self, intent_obj: 'Intent') -> 'IntentResponse': """Handle the hass intent.""" hass = intent_obj.hass slots = self.async_validate_slots(intent_obj.slots) @@ -203,7 +211,9 @@ class Intent: __slots__ = ['hass', 'platform', 'intent_type', 'slots', 'text_input'] - def __init__(self, hass, platform, intent_type, slots, text_input): + def __init__(self, hass: HomeAssistantType, platform: str, + intent_type: str, slots: _SlotsType, + text_input: Optional[str]) -> None: """Initialize an intent.""" self.hass = hass self.platform = platform @@ -212,7 +222,7 @@ class Intent: self.text_input = text_input @callback - def create_response(self): + def create_response(self) -> 'IntentResponse': """Create a response.""" return IntentResponse(self) @@ -220,14 +230,15 @@ class Intent: class IntentResponse: """Response to an intent.""" - def __init__(self, intent=None): + def __init__(self, intent: Optional[Intent] = None) -> None: """Initialize an IntentResponse.""" self.intent = intent - self.speech = {} - self.card = {} + self.speech = {} # type: Dict[str, Dict[str, Any]] + self.card = {} # type: Dict[str, Dict[str, str]] @callback - def async_set_speech(self, speech, speech_type='plain', extra_data=None): + def async_set_speech(self, speech: str, speech_type: str = 'plain', + extra_data: Optional[Any] = None) -> None: """Set speech response.""" self.speech[speech_type] = { 'speech': speech, @@ -235,7 +246,8 @@ class IntentResponse: } @callback - def async_set_card(self, title, content, card_type='simple'): + def async_set_card(self, title: str, content: str, + card_type: str = 'simple') -> None: """Set speech response.""" self.card[card_type] = { 'title': title, @@ -243,7 +255,7 @@ class IntentResponse: } @callback - def as_dict(self): + def as_dict(self) -> Dict[str, Dict[str, Dict[str, Any]]]: """Return a dictionary representation of an intent response.""" return { 'speech': self.speech, diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 13c72195ed0..04a5514b740 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -1,6 +1,6 @@ """Location helpers for Home Assistant.""" -from typing import Sequence +from typing import Optional, Sequence from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import State @@ -18,7 +18,7 @@ def has_location(state: State) -> bool: def closest(latitude: float, longitude: float, - states: Sequence[State]) -> State: + states: Sequence[State]) -> Optional[State]: """Return closest state to point. Async friendly. @@ -31,6 +31,7 @@ def closest(latitude: float, longitude: float, return min( with_location, key=lambda state: loc_util.distance( - latitude, longitude, state.attributes.get(ATTR_LATITUDE), - state.attributes.get(ATTR_LONGITUDE)) + state.attributes.get(ATTR_LATITUDE), + state.attributes.get(ATTR_LONGITUDE), + latitude, longitude) ) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 4a3f915e810..fa728026eeb 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,8 +1,12 @@ """Helpers that help with state related things.""" import asyncio +import datetime as dt import json import logging from collections import defaultdict +from types import TracebackType +from typing import ( # noqa: F401 pylint: disable=unused-import + Awaitable, Dict, Iterable, List, Optional, Tuple, Type, Union) from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util @@ -42,6 +46,7 @@ from homeassistant.const import ( STATE_UNLOCKED, SERVICE_SELECT_OPTION) from homeassistant.core import State from homeassistant.util.async_ import run_coroutine_threadsafe +from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) @@ -102,43 +107,50 @@ class AsyncTrackStates: Must be run within the event loop. """ - def __init__(self, hass): + def __init__(self, hass: HomeAssistantType) -> None: """Initialize a TrackStates block.""" self.hass = hass - self.states = [] + self.states = [] # type: List[State] # pylint: disable=attribute-defined-outside-init - def __enter__(self): + def __enter__(self) -> List[State]: """Record time from which to track changes.""" self.now = dt_util.utcnow() return self.states - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType]) -> None: """Add changes states to changes list.""" self.states.extend(get_changed_since(self.hass.states.async_all(), self.now)) -def get_changed_since(states, utc_point_in_time): +def get_changed_since(states: Iterable[State], + utc_point_in_time: dt.datetime) -> List[State]: """Return list of states that have been changed since utc_point_in_time.""" return [state for state in states if state.last_updated >= utc_point_in_time] @bind_hass -def reproduce_state(hass, states, blocking=False): +def reproduce_state(hass: HomeAssistantType, + states: Union[State, Iterable[State]], + blocking: bool = False) -> None: """Reproduce given state.""" - return run_coroutine_threadsafe( + return run_coroutine_threadsafe( # type: ignore async_reproduce_state(hass, states, blocking), hass.loop).result() @bind_hass -async def async_reproduce_state(hass, states, blocking=False): +async def async_reproduce_state(hass: HomeAssistantType, + states: Union[State, Iterable[State]], + blocking: bool = False) -> None: """Reproduce given state.""" if isinstance(states, State): states = [states] - to_call = defaultdict(list) + to_call = defaultdict(list) # type: Dict[Tuple[str, str, str], List[str]] for state in states: @@ -182,7 +194,7 @@ async def async_reproduce_state(hass, states, blocking=False): json.dumps(dict(state.attributes), sort_keys=True)) to_call[key].append(state.entity_id) - domain_tasks = {} + domain_tasks = {} # type: Dict[str, List[Awaitable[Optional[bool]]]] for (service_domain, service, service_data), entity_ids in to_call.items(): data = json.loads(service_data) data[ATTR_ENTITY_ID] = entity_ids @@ -194,7 +206,8 @@ async def async_reproduce_state(hass, states, blocking=False): hass.services.async_call(service_domain, service, data, blocking) ) - async def async_handle_service_calls(coro_list): + async def async_handle_service_calls( + coro_list: Iterable[Awaitable]) -> None: """Handle service calls by domain sequence.""" for coro in coro_list: await coro @@ -205,7 +218,7 @@ async def async_reproduce_state(hass, states, blocking=False): await asyncio.wait(execute_tasks, loop=hass.loop) -def state_as_number(state): +def state_as_number(state: State) -> float: """ Try to coerce our state to a number. diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 81ec046f2e9..2ca621154a1 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -1,17 +1,19 @@ """Translation string lookup helpers.""" import logging from os import path +from typing import Any, Dict, Iterable from homeassistant import config_entries from homeassistant.loader import get_component, bind_hass from homeassistant.util.json import load_json +from .typing import HomeAssistantType _LOGGER = logging.getLogger(__name__) TRANSLATION_STRING_CACHE = 'translation_string_cache' -def recursive_flatten(prefix, data): +def recursive_flatten(prefix: Any, data: Dict) -> Dict[str, Any]: """Return a flattened representation of dict data.""" output = {} for key, value in data.items(): @@ -23,12 +25,13 @@ def recursive_flatten(prefix, data): return output -def flatten(data): +def flatten(data: Dict) -> Dict[str, Any]: """Return a flattened representation of dict data.""" return recursive_flatten('', data) -def component_translation_file(hass, component, language): +def component_translation_file(hass: HomeAssistantType, component: str, + language: str) -> str: """Return the translation json file location for a component.""" if '.' in component: name = component.split('.', 1)[1] @@ -36,6 +39,7 @@ def component_translation_file(hass, component, language): name = component module = get_component(hass, component) + assert module is not None component_path = path.dirname(module.__file__) # If loading translations for the package root, (__init__.py), the @@ -48,19 +52,23 @@ def component_translation_file(hass, component, language): return path.join(component_path, '.translations', filename) -def load_translations_files(translation_files): +def load_translations_files(translation_files: Dict[str, str]) \ + -> Dict[str, Dict[str, Any]]: """Load and parse translation.json files.""" loaded = {} for component, translation_file in translation_files.items(): - loaded[component] = load_json(translation_file) + loaded_json = load_json(translation_file) + assert isinstance(loaded_json, dict) + loaded[component] = loaded_json return loaded -def build_resources(translation_cache, components): +def build_resources(translation_cache: Dict[str, Dict[str, Any]], + components: Iterable[str]) -> Dict[str, Dict[str, Any]]: """Build the resources response for the given components.""" # Build response - resources = {} + resources = {} # type: Dict[str, Dict[str, Any]] for component in components: if '.' not in component: domain = component @@ -79,7 +87,8 @@ def build_resources(translation_cache, components): @bind_hass -async def async_get_component_resources(hass, language): +async def async_get_component_resources(hass: HomeAssistantType, + language: str) -> Dict[str, Any]: """Return translation resources for all components.""" if TRANSLATION_STRING_CACHE not in hass.data: hass.data[TRANSLATION_STRING_CACHE] = {} @@ -99,12 +108,13 @@ async def async_get_component_resources(hass, language): # Load missing files if missing_files: - loaded_translations = await hass.async_add_job( + load_translations_job = hass.async_add_job( load_translations_files, missing_files) + assert load_translations_job is not None + loaded_translations = await load_translations_job # Update cache - for component, translation_data in loaded_translations.items(): - translation_cache[component] = translation_data + translation_cache.update(loaded_translations) resources = build_resources(translation_cache, components) @@ -114,7 +124,8 @@ async def async_get_component_resources(hass, language): @bind_hass -async def async_get_translations(hass, language): +async def async_get_translations(hass: HomeAssistantType, + language: str) -> Dict[str, Any]: """Return all backend translations.""" resources = await async_get_component_resources(hass, language) if language != 'en': diff --git a/tox.ini b/tox.ini index dcfb209ef3a..4a44feb6c7f 100644 --- a/tox.ini +++ b/tox.ini @@ -60,4 +60,4 @@ whitelist_externals=/bin/bash deps = -r{toxinidir}/requirements_test.txt commands = - /bin/bash -c 'mypy homeassistant/*.py homeassistant/auth/ homeassistant/util/' + /bin/bash -c 'mypy homeassistant/*.py homeassistant/{auth,util}/ homeassistant/helpers/{icon,intent,json,location,state,translation,typing}.py' From df65d2151d09571cc78855ff55b2c434b09ebb92 Mon Sep 17 00:00:00 2001 From: Julius Mittenzwei Date: Sun, 28 Oct 2018 23:03:27 +0100 Subject: [PATCH 094/230] updated version of xknx (#17912) --- homeassistant/components/knx.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index 6aef3ea4ec5..abedc9862c2 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script -REQUIREMENTS = ['xknx==0.8.5'] +REQUIREMENTS = ['xknx==0.9.1'] DOMAIN = "knx" DATA_KNX = "data_knx" diff --git a/requirements_all.txt b/requirements_all.txt index 8d86af1429a..f8ff629a9ba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1557,7 +1557,7 @@ xbee-helper==0.0.7 xboxapi==0.1.1 # homeassistant.components.knx -xknx==0.8.5 +xknx==0.9.1 # homeassistant.components.media_player.bluesound # homeassistant.components.sensor.startca From 62752e006562246398a517bf1b75818a0f2023ff Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Sun, 28 Oct 2018 23:03:43 +0100 Subject: [PATCH 095/230] Updated Brunt code owner (#17854) * Updated Brunt code owner * Fix platform --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index b4bd5883393..d3562e8a72a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -56,6 +56,7 @@ homeassistant/components/climate/ephember.py @ttroy50 homeassistant/components/climate/eq3btsmart.py @rytilahti homeassistant/components/climate/mill.py @danielhiversen homeassistant/components/climate/sensibo.py @andrey-git +homeassistant/components/cover/brunt.py @eavanvalkenburg homeassistant/components/cover/group.py @cdce8p homeassistant/components/cover/template.py @PhracturedBlue homeassistant/components/device_tracker/asuswrt.py @kennedyshead From b62b3b26f2ae6525a53b211c7f291aa7be51d377 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 28 Oct 2018 23:37:28 +0100 Subject: [PATCH 096/230] Monitor all sensor types by default to rtorrent (#17894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Default to all sensor types for monitoring * Cleanup code * 👀 * Chop long line --- homeassistant/components/sensor/rtorrent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/rtorrent.py b/homeassistant/components/sensor/rtorrent.py index f71b9c6dbdb..7822bcd58b7 100644 --- a/homeassistant/components/sensor/rtorrent.py +++ b/homeassistant/components/sensor/rtorrent.py @@ -28,8 +28,8 @@ SENSOR_TYPES = { PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_URL): cv.url, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_MONITORED_VARIABLES, default=list(SENSOR_TYPES)): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) From 3802fec5686600ed15b27e8d0579d49a0a69d06c Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Sun, 28 Oct 2018 23:49:55 +0100 Subject: [PATCH 097/230] Merge conflicting changes (#17761) --- tests/components/fan/test_mqtt.py | 153 +++++++++++++++--------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/tests/components/fan/test_mqtt.py b/tests/components/fan/test_mqtt.py index 7bbb8467ed1..a3f76058c76 100644 --- a/tests/components/fan/test_mqtt.py +++ b/tests/components/fan/test_mqtt.py @@ -1,110 +1,111 @@ """Test MQTT fans.""" import json -import unittest -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.setup import async_setup_component from homeassistant.components import fan from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE -from tests.common import ( - mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message, - get_test_home_assistant, async_mock_mqtt_component, MockConfigEntry) +from tests.common import async_fire_mqtt_message, MockConfigEntry, \ + async_mock_mqtt_component -class TestMqttFan(unittest.TestCase): - """Test the MQTT fan platform.""" +async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): + """Test if command fails with command topic.""" + assert await async_setup_component(hass, fan.DOMAIN, { + fan.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + } + }) + assert hass.states.get('fan.test') is None - 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_publish = mock_mqtt_component(self.hass) - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() +async def test_default_availability_payload(hass, mqtt_mock): + """Test the availability payload.""" + assert await async_setup_component(hass, fan.DOMAIN, { + fan.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic' + } + }) - def test_default_availability_payload(self): - """Test the availability payload.""" - assert setup_component(self.hass, fan.DOMAIN, { - fan.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'availability_topic': 'availability_topic' - } - }) + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + async_fire_mqtt_message(hass, 'availability_topic', 'online') + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'availability_topic', 'online') - self.hass.block_till_done() + state = hass.states.get('fan.test') + assert state.state is not STATE_UNAVAILABLE + assert not state.attributes.get(ATTR_ASSUMED_STATE) - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE != state.state - assert not state.attributes.get(ATTR_ASSUMED_STATE) + async_fire_mqtt_message(hass, 'availability_topic', 'offline') + await hass.async_block_till_done() + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'availability_topic', 'offline') - self.hass.block_till_done() + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + async_fire_mqtt_message(hass, 'state-topic', '1') + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'state-topic', '1') - self.hass.block_till_done() + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + async_fire_mqtt_message(hass, 'availability_topic', 'online') + await hass.async_block_till_done() + await hass.async_block_till_done() - fire_mqtt_message(self.hass, 'availability_topic', 'online') - self.hass.block_till_done() + state = hass.states.get('fan.test') + assert state.state is not STATE_UNAVAILABLE - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE != state.state - def test_custom_availability_payload(self): - """Test the availability payload.""" - assert setup_component(self.hass, fan.DOMAIN, { - fan.DOMAIN: { - 'platform': 'mqtt', - 'name': 'test', - 'state_topic': 'state-topic', - 'command_topic': 'command-topic', - 'availability_topic': 'availability_topic', - 'payload_available': 'good', - 'payload_not_available': 'nogood' - } - }) +async def test_custom_availability_payload(hass, mqtt_mock): + """Test the availability payload.""" + assert await async_setup_component(hass, fan.DOMAIN, { + fan.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic', + 'payload_available': 'good', + 'payload_not_available': 'nogood' + } + }) - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - fire_mqtt_message(self.hass, 'availability_topic', 'good') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'availability_topic', 'good') + await hass.async_block_till_done() - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE != state.state - assert not state.attributes.get(ATTR_ASSUMED_STATE) + state = hass.states.get('fan.test') + assert state.state is not STATE_UNAVAILABLE + assert not state.attributes.get(ATTR_ASSUMED_STATE) - fire_mqtt_message(self.hass, 'availability_topic', 'nogood') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'availability_topic', 'nogood') + await hass.async_block_till_done() + await hass.async_block_till_done() - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - fire_mqtt_message(self.hass, 'state-topic', '1') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'state-topic', '1') + await hass.async_block_till_done() - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE == state.state + state = hass.states.get('fan.test') + assert state.state is STATE_UNAVAILABLE - fire_mqtt_message(self.hass, 'availability_topic', 'good') - self.hass.block_till_done() + async_fire_mqtt_message(hass, 'availability_topic', 'good') + await hass.async_block_till_done() - state = self.hass.states.get('fan.test') - assert STATE_UNAVAILABLE != state.state + state = hass.states.get('fan.test') + assert state.state is not STATE_UNAVAILABLE async def test_discovery_removal_fan(hass, mqtt_mock, caplog): From 457971731770c44ac16797c4d83bf2172cb4367c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 29 Oct 2018 06:52:30 +0100 Subject: [PATCH 098/230] Axis - prepare for config entry (#17566) Make component more in line with other more modern components in preparation for config entry support. --- .../components/{axis.py => axis/__init__.py} | 98 ++++++------------- homeassistant/components/axis/services.yaml | 15 +++ .../components/binary_sensor/axis.py | 92 ++++++++++------- homeassistant/components/services.yaml | 17 ---- requirements_all.txt | 2 +- 5 files changed, 106 insertions(+), 118 deletions(-) rename homeassistant/components/{axis.py => axis/__init__.py} (79%) create mode 100644 homeassistant/components/axis/services.yaml diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis/__init__.py similarity index 79% rename from homeassistant/components/axis.py rename to homeassistant/components/axis/__init__.py index 63fce8a74ee..26fe41724f9 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis/__init__.py @@ -10,24 +10,21 @@ import voluptuous as vol from homeassistant.components.discovery import SERVICE_AXIS from homeassistant.const import ( - ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE, + ATTR_LOCATION, CONF_EVENT, CONF_HOST, CONF_INCLUDE, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.entity import Entity from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['axis==14'] +REQUIREMENTS = ['axis==16'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'axis' CONFIG_FILE = 'axis.conf' -AXIS_DEVICES = {} - EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', 'daynight', 'tampering', 'input'] @@ -99,8 +96,6 @@ def request_configuration(hass, config, name, host, serialnumber): return False if setup_device(hass, config, device_config): - del device_config['events'] - del device_config['signal'] config_file = load_json(hass.config.path(CONFIG_FILE)) config_file[serialnumber] = dict(device_config) save_json(hass.config.path(CONFIG_FILE), config_file) @@ -146,9 +141,11 @@ def request_configuration(hass, config, name, host, serialnumber): def setup(hass, config): """Set up for Axis devices.""" + hass.data[DOMAIN] = {} + def _shutdown(call): """Stop the event stream on shutdown.""" - for serialnumber, device in AXIS_DEVICES.items(): + for serialnumber, device in hass.data[DOMAIN].items(): _LOGGER.info("Stopping event stream for %s.", serialnumber) device.stop() @@ -160,7 +157,7 @@ def setup(hass, config): name = discovery_info['hostname'] serialnumber = discovery_info['properties']['macaddress'] - if serialnumber not in AXIS_DEVICES: + if serialnumber not in hass.data[DOMAIN]: config_file = load_json(hass.config.path(CONFIG_FILE)) if serialnumber in config_file: # Device config previously saved to file @@ -178,7 +175,7 @@ def setup(hass, config): request_configuration(hass, config, name, host, serialnumber) else: # Device already registered, but on a different IP - device = AXIS_DEVICES[serialnumber] + device = hass.data[DOMAIN][serialnumber] device.config.host = host dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host) @@ -195,7 +192,7 @@ def setup(hass, config): def vapix_service(call): """Service to send a message.""" - for _, device in AXIS_DEVICES.items(): + for device in hass.data[DOMAIN].values(): if device.name == call.data[CONF_NAME]: response = device.vapix.do_request( call.data[SERVICE_CGI], @@ -214,7 +211,7 @@ def setup(hass, config): def setup_device(hass, config, device_config): """Set up an Axis device.""" - from axis import AxisDevice + import axis def signal_callback(action, event): """Call to configure events when initialized on event stream.""" @@ -229,18 +226,32 @@ def setup_device(hass, config, device_config): discovery.load_platform( hass, component, DOMAIN, event_config, config) - event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], - EVENT_TYPES)) - device_config['events'] = event_types - device_config['signal'] = signal_callback - device = AxisDevice(hass.loop, **device_config) - device.name = device_config[CONF_NAME] + event_types = [ + event + for event in device_config[CONF_INCLUDE] + if event in EVENT_TYPES + ] - if device.serial_number is None: - # If there is no serial number a connection could not be made - _LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST]) + device = axis.AxisDevice( + loop=hass.loop, host=device_config[CONF_HOST], + username=device_config[CONF_USERNAME], + password=device_config[CONF_PASSWORD], + port=device_config[CONF_PORT], web_proto='http', + event_types=event_types, signal=signal_callback) + + try: + hass.data[DOMAIN][device.vapix.serial_number] = device + + except axis.Unauthorized: + _LOGGER.error("Credentials for %s are faulty", + device_config[CONF_HOST]) return False + except axis.RequestError: + return False + + device.name = device_config[CONF_NAME] + for component in device_config[CONF_INCLUDE]: if component == 'camera': camera_config = { @@ -253,51 +264,6 @@ def setup_device(hass, config, device_config): discovery.load_platform( hass, component, DOMAIN, camera_config, config) - AXIS_DEVICES[device.serial_number] = device if event_types: hass.add_job(device.start) return True - - -class AxisDeviceEvent(Entity): - """Representation of a Axis device event.""" - - def __init__(self, event_config): - """Initialize the event.""" - self.axis_event = event_config[CONF_EVENT] - self._name = '{}_{}_{}'.format( - event_config[CONF_NAME], self.axis_event.event_type, - self.axis_event.id) - self.location = event_config[ATTR_LOCATION] - self.axis_event.callback = self._update_callback - - def _update_callback(self): - """Update the sensor's state, if needed.""" - self.schedule_update_ha_state(True) - - @property - def name(self): - """Return the name of the event.""" - return self._name - - @property - def device_class(self): - """Return the class of the event.""" - return self.axis_event.event_class - - @property - def should_poll(self): - """Return the polling state. No polling needed.""" - return False - - @property - def device_state_attributes(self): - """Return the state attributes of the event.""" - attr = {} - - tripped = self.axis_event.is_tripped - attr[ATTR_TRIPPED] = 'True' if tripped else 'False' - - attr[ATTR_LOCATION] = self.location - - return attr diff --git a/homeassistant/components/axis/services.yaml b/homeassistant/components/axis/services.yaml new file mode 100644 index 00000000000..03db5ce7af8 --- /dev/null +++ b/homeassistant/components/axis/services.yaml @@ -0,0 +1,15 @@ +vapix_call: + description: Configure device using Vapix parameter management. + fields: + name: + description: Name of device to Configure. [Required] + example: M1065-W + cgi: + description: Which cgi to call on device. [Optional] Default is 'param.cgi' + example: 'applications/control.cgi' + action: + description: What type of call. [Optional] Default is 'update' + example: 'start' + param: + description: What parameter to operate on. [Required] + example: 'package=VideoMotionDetection' \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/axis.py b/homeassistant/components/binary_sensor/axis.py index b66a766ca4a..671bbc730d0 100644 --- a/homeassistant/components/binary_sensor/axis.py +++ b/homeassistant/components/binary_sensor/axis.py @@ -7,10 +7,11 @@ https://home-assistant.io/components/binary_sensor.axis/ from datetime import timedelta import logging -from homeassistant.components.axis import AxisDeviceEvent from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import CONF_TRIGGER_TIME -from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.const import ( + ATTR_LOCATION, CONF_EVENT, CONF_NAME, CONF_TRIGGER_TIME) +from homeassistant.core import callback +from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow DEPENDENCIES = ['axis'] @@ -20,48 +21,71 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Axis binary devices.""" - add_entities([AxisBinarySensor(hass, discovery_info)], True) + add_entities([AxisBinarySensor(discovery_info)], True) -class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice): +class AxisBinarySensor(BinarySensorDevice): """Representation of a binary Axis event.""" - def __init__(self, hass, event_config): + def __init__(self, event_config): """Initialize the Axis binary sensor.""" - self.hass = hass - self._state = False - self._delay = event_config[CONF_TRIGGER_TIME] - self._timer = None - AxisDeviceEvent.__init__(self, event_config) + self.axis_event = event_config[CONF_EVENT] + self.device_name = event_config[CONF_NAME] + self.location = event_config[ATTR_LOCATION] + self.delay = event_config[CONF_TRIGGER_TIME] + self.remove_timer = None + + async def async_added_to_hass(self): + """Subscribe sensors events.""" + self.axis_event.callback = self._update_callback + + def _update_callback(self): + """Update the sensor's state, if needed.""" + if self.remove_timer is not None: + self.remove_timer() + self.remove_timer = None + + if self.delay == 0 or self.is_on: + self.schedule_update_ha_state() + else: # Run timer to delay updating the state + @callback + def _delay_update(now): + """Timer callback for sensor update.""" + _LOGGER.debug("%s called delayed (%s sec) update", + self.name, self.delay) + self.async_schedule_update_ha_state() + self.remove_timer = None + + self.remove_timer = async_track_point_in_utc_time( + self.hass, _delay_update, + utcnow() + timedelta(seconds=self.delay)) @property def is_on(self): """Return true if event is active.""" - return self._state + return self.axis_event.is_tripped - def update(self): - """Get the latest data and update the state.""" - self._state = self.axis_event.is_tripped + @property + def name(self): + """Return the name of the event.""" + return '{}_{}_{}'.format( + self.device_name, self.axis_event.event_type, self.axis_event.id) - def _update_callback(self): - """Update the sensor's state, if needed.""" - self.update() + @property + def device_class(self): + """Return the class of the event.""" + return self.axis_event.event_class - if self._timer is not None: - self._timer() - self._timer = None + @property + def should_poll(self): + """No polling needed.""" + return False - if self._delay > 0 and not self.is_on: - # Set timer to wait until updating the state - def _delay_update(now): - """Timer callback for sensor update.""" - _LOGGER.debug("%s called delayed (%s sec) update", - self._name, self._delay) - self.schedule_update_ha_state() - self._timer = None + @property + def device_state_attributes(self): + """Return the state attributes of the event.""" + attr = {} - self._timer = track_point_in_utc_time( - self.hass, _delay_update, - utcnow() + timedelta(seconds=self._delay)) - else: - self.schedule_update_ha_state() + attr[ATTR_LOCATION] = self.location + + return attr diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index e8512d67fc4..8988021a5b6 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -260,23 +260,6 @@ eight_sleep: description: Duration to heat at the target level in seconds. example: 3600 -axis: - vapix_call: - description: Configure device using Vapix parameter management. - fields: - name: - description: Name of device to Configure. [Required] - example: M1065-W - cgi: - description: Which cgi to call on device. [Optional] Default is 'param.cgi' - example: 'applications/control.cgi' - action: - description: What type of call. [Optional] Default is 'update' - example: 'start' - param: - description: What parameter to operate on. [Required] - example: 'package=VideoMotionDetection' - apple_tv: apple_tv_authenticate: description: Start AirPlay device authentication. diff --git a/requirements_all.txt b/requirements_all.txt index f8ff629a9ba..5f2f8b2ea33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -162,7 +162,7 @@ async-upnp-client==0.12.7 # avion==0.7 # homeassistant.components.axis -axis==14 +axis==16 # homeassistant.components.tts.baidu baidu-aip==1.6.6 From 3e6de21302ea87aa079c2e80423c6a4e5963cd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 29 Oct 2018 08:02:34 +0200 Subject: [PATCH 099/230] Upgrade pytest to 3.9.3 (#17921) --- requirements_test.txt | 2 +- requirements_test_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 29e31c977ac..68248a47cdb 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,5 +13,5 @@ pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 pytest-timeout==1.3.2 -pytest==3.9.2 +pytest==3.9.3 requests_mock==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b49781f8bad..512dfbde7de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -14,7 +14,7 @@ pytest-aiohttp==0.3.0 pytest-cov==2.5.1 pytest-sugar==0.9.1 pytest-timeout==1.3.2 -pytest==3.9.2 +pytest==3.9.3 requests_mock==1.5.2 From e5c97fdcab35f391b9955de104b87fdf1868a6d7 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 29 Oct 2018 08:03:10 +0100 Subject: [PATCH 100/230] Extract entity ids from all templates (#17902) --- .../components/binary_sensor/template.py | 39 +++++++++++++------ .../components/binary_sensor/test_template.py | 28 ++++++++++++- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index 89547dffbc9..1f386fc2293 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -15,7 +15,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, - CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START) + CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, MATCH_ALL) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id @@ -55,22 +55,37 @@ async def async_setup_platform(hass, config, async_add_entities, icon_template = device_config.get(CONF_ICON_TEMPLATE) entity_picture_template = device_config.get( CONF_ENTITY_PICTURE_TEMPLATE) - entity_ids = (device_config.get(ATTR_ENTITY_ID) or - value_template.extract_entities()) + entity_ids = set() + manual_entity_ids = device_config.get(ATTR_ENTITY_ID) + + for template in ( + value_template, + icon_template, + entity_picture_template, + ): + if template is None: + continue + template.hass = hass + + if manual_entity_ids is not None: + continue + + template_entity_ids = template.extract_entities() + if template_entity_ids == MATCH_ALL: + entity_ids = MATCH_ALL + elif entity_ids != MATCH_ALL: + entity_ids |= set(template_entity_ids) + + if manual_entity_ids is not None: + entity_ids = manual_entity_ids + elif entity_ids != MATCH_ALL: + entity_ids = list(entity_ids) + friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) device_class = device_config.get(CONF_DEVICE_CLASS) delay_on = device_config.get(CONF_DELAY_ON) delay_off = device_config.get(CONF_DELAY_OFF) - if value_template is not None: - value_template.hass = hass - - if icon_template is not None: - icon_template.hass = hass - - if entity_picture_template is not None: - entity_picture_template.hass = hass - sensors.append( BinarySensorTemplate( hass, device, friendly_name, device_class, value_template, diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index 7307b222436..f448bcc47a2 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -106,7 +106,7 @@ class TestBinarySensorTemplate(unittest.TestCase): 'platform': 'template', 'sensors': { 'test_template_sensor': { - 'value_template': "State", + 'value_template': "{{ states.sensor.xyz.state }}", 'icon_template': "{% if " "states.binary_sensor.test_state.state == " @@ -137,7 +137,7 @@ class TestBinarySensorTemplate(unittest.TestCase): 'platform': 'template', 'sensors': { 'test_template_sensor': { - 'value_template': "State", + 'value_template': "{{ states.sensor.xyz.state }}", 'entity_picture_template': "{% if " "states.binary_sensor.test_state.state == " @@ -160,6 +160,30 @@ class TestBinarySensorTemplate(unittest.TestCase): state = self.hass.states.get('binary_sensor.test_template_sensor') assert state.attributes['entity_picture'] == '/local/sensor.png' + @mock.patch('homeassistant.components.binary_sensor.template.' + 'BinarySensorTemplate._async_render') + def test_match_all(self, _async_render): + """Test MATCH_ALL in template.""" + with assert_setup_component(1): + assert setup.setup_component(self.hass, 'binary_sensor', { + 'binary_sensor': { + 'platform': 'template', + 'sensors': { + 'match_all_template_sensor': { + 'value_template': "{{ 42 }}", + }, + } + } + }) + + self.hass.start() + self.hass.block_till_done() + init_calls = len(_async_render.mock_calls) + + self.hass.states.set('sensor.any_state', 'update') + self.hass.block_till_done() + assert len(_async_render.mock_calls) > init_calls + def test_attributes(self): """Test the attributes.""" vs = run_callback_threadsafe( From 851d7e22e7382f1890c4a3afa16fa90b4eef78ef Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Mon, 29 Oct 2018 00:07:57 -0700 Subject: [PATCH 101/230] Make light.yeelight stop doing IO when accessing properties (#17917) --- homeassistant/components/light/yeelight.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/yeelight.py b/homeassistant/components/light/yeelight.py index 1275d854e0b..15f2d24fa8a 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/light/yeelight.py @@ -276,7 +276,9 @@ class YeelightLight(Light): @property def _properties(self) -> dict: - return self._bulb.last_properties + if self._bulb_device is None: + return {} + return self._bulb_device.last_properties # F821: https://github.com/PyCQA/pyflakes/issues/373 @property From 96c5e4c5075204ca15fde18fa20cd3349c0948e9 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Mon, 29 Oct 2018 08:10:01 +0100 Subject: [PATCH 102/230] Fixes for upnp-component/#17753 and missing hass-data when only setup from config entry (#17868) * Upgrade to async_upnp_client==0.13.0, fixing #17753 * Fix missing 'local_ip' when upnp-component itself is not setup, but ConfigEntry is --- homeassistant/components/media_player/dlna_dmr.py | 2 +- homeassistant/components/upnp/__init__.py | 12 ++++-------- homeassistant/components/upnp/config_flow.py | 14 +++++++++----- requirements_all.txt | 2 +- tests/components/upnp/test_init.py | 14 +++++++------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/media_player/dlna_dmr.py b/homeassistant/components/media_player/dlna_dmr.py index bf3fce97650..7e87925dcc7 100644 --- a/homeassistant/components/media_player/dlna_dmr.py +++ b/homeassistant/components/media_player/dlna_dmr.py @@ -25,7 +25,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.util import get_local_ip -REQUIREMENTS = ['async-upnp-client==0.12.7'] +REQUIREMENTS = ['async-upnp-client==0.13.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index c667327b71b..e69943ae8b2 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -16,7 +16,6 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers import dispatcher from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util import get_local_ip from .const import ( CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS, @@ -26,11 +25,11 @@ from .const import ( ) from .const import DOMAIN from .const import LOGGER as _LOGGER -from .config_flow import ensure_domain_data +from .config_flow import async_ensure_domain_data from .device import Device -REQUIREMENTS = ['async-upnp-client==0.12.7'] +REQUIREMENTS = ['async-upnp-client==0.13.0'] NOTIFICATION_ID = 'upnp_notification' NOTIFICATION_TITLE = 'UPnP/IGD Setup' @@ -87,7 +86,7 @@ def _substitute_hass_ports(ports, hass_port=None): # config async def async_setup(hass: HomeAssistantType, config: ConfigType): """Register a port mapping for Home Assistant via UPnP.""" - ensure_domain_data(hass) + await async_ensure_domain_data(hass) # ensure sane config if DOMAIN not in config: @@ -97,9 +96,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): # overridden local ip if CONF_LOCAL_IP in upnp_config: hass.data[DOMAIN]['local_ip'] = upnp_config[CONF_LOCAL_IP] - else: - hass.data[DOMAIN]['local_ip'] = \ - await hass.async_add_executor_job(get_local_ip) # determine ports ports = {CONF_HASS: CONF_HASS} # default, port_mapping disabled by default @@ -121,7 +117,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry): """Set up UPnP/IGD-device from a config entry.""" - ensure_domain_data(hass) + await async_ensure_domain_data(hass) data = config_entry.data # build UPnP/IGD device diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index f695e3ada75..c7cd55f0477 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant import data_entry_flow +from homeassistant.util import get_local_ip from .const import ( CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS, @@ -13,7 +14,7 @@ from .const import ( from .const import DOMAIN -def ensure_domain_data(hass): +async def async_ensure_domain_data(hass): """Ensure hass.data is filled properly.""" hass.data[DOMAIN] = hass.data.get(DOMAIN, {}) hass.data[DOMAIN]['devices'] = hass.data[DOMAIN].get('devices', {}) @@ -24,6 +25,9 @@ def ensure_domain_data(hass): 'enable_port_mapping': False, 'ports': {'hass': 'hass'}, }) + if 'local_ip' not in hass.data[DOMAIN]: + hass.data[DOMAIN]['local_ip'] = \ + await hass.async_add_executor_job(get_local_ip) @config_entries.HANDLERS.register(DOMAIN) @@ -64,7 +68,7 @@ class UpnpFlowHandler(data_entry_flow.FlowHandler): This flow is triggered by the discovery component. It will check if the host is already configured and delegate to the import step if not. """ - ensure_domain_data(self.hass) + await async_ensure_domain_data(self.hass) # store discovered device discovery_info['friendly_name'] = \ @@ -91,7 +95,7 @@ class UpnpFlowHandler(data_entry_flow.FlowHandler): async def async_step_user(self, user_input=None): """Manual set up.""" - ensure_domain_data(self.hass) + await async_ensure_domain_data(self.hass) # if user input given, handle it user_input = user_input or {} @@ -132,13 +136,13 @@ class UpnpFlowHandler(data_entry_flow.FlowHandler): async def async_step_import(self, import_info): """Import a new UPnP/IGD as a config entry.""" - ensure_domain_data(self.hass) + await async_ensure_domain_data(self.hass) return await self._async_save_entry(import_info) async def _async_save_entry(self, import_info): """Store UPNP/IGD as new entry.""" - ensure_domain_data(self.hass) + await async_ensure_domain_data(self.hass) # ensure we know the host name = import_info['name'] diff --git a/requirements_all.txt b/requirements_all.txt index 5f2f8b2ea33..b3257ae6303 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,7 +156,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.upnp # homeassistant.components.media_player.dlna_dmr -async-upnp-client==0.12.7 +async-upnp-client==0.13.0 # homeassistant.components.light.avion # avion==0.7 diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 7f163d5bcef..6b2611b2509 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -55,7 +55,7 @@ async def test_async_setup_no_auto_config(hass): # no upnp } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'upnp', config) await hass.async_block_till_done() @@ -76,7 +76,7 @@ async def test_async_setup_auto_config(hass): 'upnp': {}, } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'upnp', config) await hass.async_block_till_done() @@ -100,7 +100,7 @@ async def test_async_setup_auto_config_port_mapping(hass): }, } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'upnp', config) await hass.async_block_till_done() @@ -121,7 +121,7 @@ async def test_async_setup_auto_config_no_sensors(hass): 'upnp': {'sensors': False}, } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'upnp', config) await hass.async_block_till_done() @@ -150,7 +150,7 @@ async def test_async_setup_entry_default(hass): # no upnp } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'http', config) await async_setup_component(hass, 'upnp', config) @@ -160,7 +160,7 @@ async def test_async_setup_entry_default(hass): mock_device = MockDevice(udn) with patch.object(Device, 'async_create_device') as create_device: create_device.return_value = mock_coro(return_value=mock_device) - with patch('homeassistant.components.upnp.get_local_ip', + with patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): assert await upnp.async_setup_entry(hass, entry) is True @@ -194,7 +194,7 @@ async def test_async_setup_entry_port_mapping(hass): }, } with MockDependency('netdisco.discovery'), \ - patch('homeassistant.components.upnp.get_local_ip', + patch('homeassistant.components.upnp.config_flow.get_local_ip', return_value='192.168.1.10'): await async_setup_component(hass, 'http', config) await async_setup_component(hass, 'upnp', config) From d1ef875132d5afe110622d602ddce6a6aa3d3d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 29 Oct 2018 11:27:37 +0100 Subject: [PATCH 103/230] Fix for verify_ssl in the pi_hole sensor. (#17910) --- homeassistant/components/sensor/pi_hole.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/pi_hole.py b/homeassistant/components/sensor/pi_hole.py index a318618724e..ae9aca5bc79 100644 --- a/homeassistant/components/sensor/pi_hole.py +++ b/homeassistant/components/sensor/pi_hole.py @@ -81,10 +81,9 @@ async def async_setup_platform( location = config.get(CONF_LOCATION) verify_tls = config.get(CONF_VERIFY_SSL) - session = async_get_clientsession(hass) + session = async_get_clientsession(hass, verify_tls) pi_hole = PiHoleData(Hole( - host, hass.loop, session, location=location, tls=use_tls, - verify_tls=verify_tls)) + host, hass.loop, session, location=location, tls=use_tls)) await pi_hole.async_update() From f4ac317d6445c5fbb008dff79293f82f26c348b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 11:28:04 +0100 Subject: [PATCH 104/230] Permissions improv (#17811) * Break up permissions file. * Granular entity permissions * Add "all" entity permission * Lint * Fix types --- homeassistant/auth/permissions.py | 252 --------------------- homeassistant/auth/permissions/__init__.py | 97 ++++++++ homeassistant/auth/permissions/common.py | 33 +++ homeassistant/auth/permissions/entities.py | 149 ++++++++++++ homeassistant/auth/permissions/merge.py | 65 ++++++ tests/auth/permissions/__init__.py | 1 + tests/auth/permissions/test_entities.py | 187 +++++++++++++++ tests/auth/permissions/test_init.py | 46 ++++ tests/auth/permissions/test_merge.py | 44 ++++ tests/auth/test_models.py | 6 +- tests/auth/test_permissions.py | 198 ---------------- 11 files changed, 625 insertions(+), 453 deletions(-) delete mode 100644 homeassistant/auth/permissions.py create mode 100644 homeassistant/auth/permissions/__init__.py create mode 100644 homeassistant/auth/permissions/common.py create mode 100644 homeassistant/auth/permissions/entities.py create mode 100644 homeassistant/auth/permissions/merge.py create mode 100644 tests/auth/permissions/__init__.py create mode 100644 tests/auth/permissions/test_entities.py create mode 100644 tests/auth/permissions/test_init.py create mode 100644 tests/auth/permissions/test_merge.py delete mode 100644 tests/auth/test_permissions.py diff --git a/homeassistant/auth/permissions.py b/homeassistant/auth/permissions.py deleted file mode 100644 index 82de61da7f9..00000000000 --- a/homeassistant/auth/permissions.py +++ /dev/null @@ -1,252 +0,0 @@ -"""Permissions for Home Assistant.""" -from typing import ( # noqa: F401 - cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union) - -import voluptuous as vol - -from homeassistant.core import State - -CategoryType = Union[Mapping[str, 'CategoryType'], bool, None] -PolicyType = Mapping[str, CategoryType] - - -# Default policy if group has no policy applied. -DEFAULT_POLICY = { - "entities": True -} # type: PolicyType - -CAT_ENTITIES = 'entities' -ENTITY_DOMAINS = 'domains' -ENTITY_ENTITY_IDS = 'entity_ids' - -VALUES_SCHEMA = vol.Any(True, vol.Schema({ - str: True -})) - -ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({ - vol.Optional(ENTITY_DOMAINS): VALUES_SCHEMA, - vol.Optional(ENTITY_ENTITY_IDS): VALUES_SCHEMA, -})) - -POLICY_SCHEMA = vol.Schema({ - vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA -}) - - -class AbstractPermissions: - """Default permissions class.""" - - def check_entity(self, entity_id: str, *keys: str) -> bool: - """Test if we can access entity.""" - raise NotImplementedError - - def filter_states(self, states: List[State]) -> List[State]: - """Filter a list of states for what the user is allowed to see.""" - raise NotImplementedError - - -class PolicyPermissions(AbstractPermissions): - """Handle permissions.""" - - def __init__(self, policy: PolicyType) -> None: - """Initialize the permission class.""" - self._policy = policy - self._compiled = {} # type: Dict[str, Callable[..., bool]] - - def check_entity(self, entity_id: str, *keys: str) -> bool: - """Test if we can access entity.""" - func = self._policy_func(CAT_ENTITIES, _compile_entities) - return func(entity_id, keys) - - def filter_states(self, states: List[State]) -> List[State]: - """Filter a list of states for what the user is allowed to see.""" - func = self._policy_func(CAT_ENTITIES, _compile_entities) - keys = ('read',) - return [entity for entity in states if func(entity.entity_id, keys)] - - def _policy_func(self, category: str, - compile_func: Callable[[CategoryType], Callable]) \ - -> Callable[..., bool]: - """Get a policy function.""" - func = self._compiled.get(category) - - if func: - return func - - func = self._compiled[category] = compile_func( - self._policy.get(category)) - return func - - def __eq__(self, other: Any) -> bool: - """Equals check.""" - # pylint: disable=protected-access - return (isinstance(other, PolicyPermissions) and - other._policy == self._policy) - - -class _OwnerPermissions(AbstractPermissions): - """Owner permissions.""" - - # pylint: disable=no-self-use - - def check_entity(self, entity_id: str, *keys: str) -> bool: - """Test if we can access entity.""" - return True - - def filter_states(self, states: List[State]) -> List[State]: - """Filter a list of states for what the user is allowed to see.""" - return states - - -OwnerPermissions = _OwnerPermissions() # pylint: disable=invalid-name - - -def _compile_entities(policy: CategoryType) \ - -> Callable[[str, Tuple[str]], bool]: - """Compile policy into a function that tests policy.""" - # None, Empty Dict, False - if not policy: - def apply_policy_deny_all(entity_id: str, keys: Tuple[str]) -> bool: - """Decline all.""" - return False - - return apply_policy_deny_all - - if policy is True: - def apply_policy_allow_all(entity_id: str, keys: Tuple[str]) -> bool: - """Approve all.""" - return True - - return apply_policy_allow_all - - assert isinstance(policy, dict) - - domains = policy.get(ENTITY_DOMAINS) - entity_ids = policy.get(ENTITY_ENTITY_IDS) - - funcs = [] # type: List[Callable[[str, Tuple[str]], Union[None, bool]]] - - # The order of these functions matter. The more precise are at the top. - # If a function returns None, they cannot handle it. - # If a function returns a boolean, that's the result to return. - - # Setting entity_ids to a boolean is final decision for permissions - # So return right away. - if isinstance(entity_ids, bool): - def apply_entity_id_policy(entity_id: str, keys: Tuple[str]) -> bool: - """Test if allowed entity_id.""" - return entity_ids # type: ignore - - return apply_entity_id_policy - - if entity_ids is not None: - def allowed_entity_id(entity_id: str, keys: Tuple[str]) \ - -> Union[None, bool]: - """Test if allowed entity_id.""" - return entity_ids.get(entity_id) # type: ignore - - funcs.append(allowed_entity_id) - - if isinstance(domains, bool): - def allowed_domain(entity_id: str, keys: Tuple[str]) \ - -> Union[None, bool]: - """Test if allowed domain.""" - return domains - - funcs.append(allowed_domain) - - elif domains is not None: - def allowed_domain(entity_id: str, keys: Tuple[str]) \ - -> Union[None, bool]: - """Test if allowed domain.""" - domain = entity_id.split(".", 1)[0] - return domains.get(domain) # type: ignore - - funcs.append(allowed_domain) - - # Can happen if no valid subcategories specified - if not funcs: - def apply_policy_deny_all_2(entity_id: str, keys: Tuple[str]) -> bool: - """Decline all.""" - return False - - return apply_policy_deny_all_2 - - if len(funcs) == 1: - func = funcs[0] - - def apply_policy_func(entity_id: str, keys: Tuple[str]) -> bool: - """Apply a single policy function.""" - return func(entity_id, keys) is True - - return apply_policy_func - - def apply_policy_funcs(entity_id: str, keys: Tuple[str]) -> bool: - """Apply several policy functions.""" - for func in funcs: - result = func(entity_id, keys) - if result is not None: - return result - return False - - return apply_policy_funcs - - -def merge_policies(policies: List[PolicyType]) -> PolicyType: - """Merge policies.""" - new_policy = {} # type: Dict[str, CategoryType] - seen = set() # type: Set[str] - for policy in policies: - for category in policy: - if category in seen: - continue - seen.add(category) - new_policy[category] = _merge_policies([ - policy.get(category) for policy in policies]) - cast(PolicyType, new_policy) - return new_policy - - -def _merge_policies(sources: List[CategoryType]) -> CategoryType: - """Merge a policy.""" - # When merging policies, the most permissive wins. - # This means we order it like this: - # True > Dict > None - # - # True: allow everything - # Dict: specify more granular permissions - # None: no opinion - # - # If there are multiple sources with a dict as policy, we recursively - # merge each key in the source. - - policy = None # type: CategoryType - seen = set() # type: Set[str] - for source in sources: - if source is None: - continue - - # A source that's True will always win. Shortcut return. - if source is True: - return True - - assert isinstance(source, dict) - - if policy is None: - policy = {} - - assert isinstance(policy, dict) - - for key in source: - if key in seen: - continue - seen.add(key) - - key_sources = [] - for src in sources: - if isinstance(src, dict): - key_sources.append(src.get(key)) - - policy[key] = _merge_policies(key_sources) - - return policy diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py new file mode 100644 index 00000000000..ee0d3af0c54 --- /dev/null +++ b/homeassistant/auth/permissions/__init__.py @@ -0,0 +1,97 @@ +"""Permissions for Home Assistant.""" +import logging +from typing import ( # noqa: F401 + cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union) + +import voluptuous as vol + +from homeassistant.core import State + +from .common import CategoryType, PolicyType +from .entities import ENTITY_POLICY_SCHEMA, compile_entities +from .merge import merge_policies # noqa + + +# Default policy if group has no policy applied. +DEFAULT_POLICY = { + "entities": True +} # type: PolicyType + +CAT_ENTITIES = 'entities' + +POLICY_SCHEMA = vol.Schema({ + vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA +}) + +_LOGGER = logging.getLogger(__name__) + + +class AbstractPermissions: + """Default permissions class.""" + + def check_entity(self, entity_id: str, key: str) -> bool: + """Test if we can access entity.""" + raise NotImplementedError + + def filter_states(self, states: List[State]) -> List[State]: + """Filter a list of states for what the user is allowed to see.""" + raise NotImplementedError + + +class PolicyPermissions(AbstractPermissions): + """Handle permissions.""" + + def __init__(self, policy: PolicyType) -> None: + """Initialize the permission class.""" + self._policy = policy + self._compiled = {} # type: Dict[str, Callable[..., bool]] + + def check_entity(self, entity_id: str, key: str) -> bool: + """Test if we can access entity.""" + func = self._policy_func(CAT_ENTITIES, compile_entities) + return func(entity_id, (key,)) + + def filter_states(self, states: List[State]) -> List[State]: + """Filter a list of states for what the user is allowed to see.""" + func = self._policy_func(CAT_ENTITIES, compile_entities) + keys = ('read',) + return [entity for entity in states if func(entity.entity_id, keys)] + + def _policy_func(self, category: str, + compile_func: Callable[[CategoryType], Callable]) \ + -> Callable[..., bool]: + """Get a policy function.""" + func = self._compiled.get(category) + + if func: + return func + + func = self._compiled[category] = compile_func( + self._policy.get(category)) + + _LOGGER.debug("Compiled %s func: %s", category, func) + + return func + + def __eq__(self, other: Any) -> bool: + """Equals check.""" + # pylint: disable=protected-access + return (isinstance(other, PolicyPermissions) and + other._policy == self._policy) + + +class _OwnerPermissions(AbstractPermissions): + """Owner permissions.""" + + # pylint: disable=no-self-use + + def check_entity(self, entity_id: str, key: str) -> bool: + """Test if we can access entity.""" + return True + + def filter_states(self, states: List[State]) -> List[State]: + """Filter a list of states for what the user is allowed to see.""" + return states + + +OwnerPermissions = _OwnerPermissions() # pylint: disable=invalid-name diff --git a/homeassistant/auth/permissions/common.py b/homeassistant/auth/permissions/common.py new file mode 100644 index 00000000000..f87f9d70ddf --- /dev/null +++ b/homeassistant/auth/permissions/common.py @@ -0,0 +1,33 @@ +"""Common code for permissions.""" +from typing import ( # noqa: F401 + Mapping, Union, Any) + +# MyPy doesn't support recursion yet. So writing it out as far as we need. + +ValueType = Union[ + # Example: entities.all = { read: true, control: true } + Mapping[str, bool], + bool, + None +] + +SubCategoryType = Union[ + # Example: entities.domains = { light: … } + Mapping[str, ValueType], + bool, + None +] + +CategoryType = Union[ + # Example: entities.domains + Mapping[str, SubCategoryType], + # Example: entities.all + Mapping[str, ValueType], + bool, + None +] + +# Example: { entities: … } +PolicyType = Mapping[str, CategoryType] + +SUBCAT_ALL = 'all' diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py new file mode 100644 index 00000000000..b38600fe130 --- /dev/null +++ b/homeassistant/auth/permissions/entities.py @@ -0,0 +1,149 @@ +"""Entity permissions.""" +from functools import wraps +from typing import ( # noqa: F401 + Callable, Dict, List, Tuple, Union) + +import voluptuous as vol + +from .common import CategoryType, ValueType, SUBCAT_ALL + + +POLICY_READ = 'read' +POLICY_CONTROL = 'control' +POLICY_EDIT = 'edit' + +SINGLE_ENTITY_SCHEMA = vol.Any(True, vol.Schema({ + vol.Optional(POLICY_READ): True, + vol.Optional(POLICY_CONTROL): True, + vol.Optional(POLICY_EDIT): True, +})) + +ENTITY_DOMAINS = 'domains' +ENTITY_ENTITY_IDS = 'entity_ids' + +ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({ + str: SINGLE_ENTITY_SCHEMA +})) + +ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({ + vol.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA, + vol.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA, + vol.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA, +})) + + +def _entity_allowed(schema: ValueType, keys: Tuple[str]) \ + -> Union[bool, None]: + """Test if an entity is allowed based on the keys.""" + if schema is None or isinstance(schema, bool): + return schema + assert isinstance(schema, dict) + return schema.get(keys[0]) + + +def compile_entities(policy: CategoryType) \ + -> Callable[[str, Tuple[str]], bool]: + """Compile policy into a function that tests policy.""" + # None, Empty Dict, False + if not policy: + def apply_policy_deny_all(entity_id: str, keys: Tuple[str]) -> bool: + """Decline all.""" + return False + + return apply_policy_deny_all + + if policy is True: + def apply_policy_allow_all(entity_id: str, keys: Tuple[str]) -> bool: + """Approve all.""" + return True + + return apply_policy_allow_all + + assert isinstance(policy, dict) + + domains = policy.get(ENTITY_DOMAINS) + entity_ids = policy.get(ENTITY_ENTITY_IDS) + all_entities = policy.get(SUBCAT_ALL) + + funcs = [] # type: List[Callable[[str, Tuple[str]], Union[None, bool]]] + + # The order of these functions matter. The more precise are at the top. + # If a function returns None, they cannot handle it. + # If a function returns a boolean, that's the result to return. + + # Setting entity_ids to a boolean is final decision for permissions + # So return right away. + if isinstance(entity_ids, bool): + def allowed_entity_id_bool(entity_id: str, keys: Tuple[str]) -> bool: + """Test if allowed entity_id.""" + return entity_ids # type: ignore + + return allowed_entity_id_bool + + if entity_ids is not None: + def allowed_entity_id_dict(entity_id: str, keys: Tuple[str]) \ + -> Union[None, bool]: + """Test if allowed entity_id.""" + return _entity_allowed( + entity_ids.get(entity_id), keys) # type: ignore + + funcs.append(allowed_entity_id_dict) + + if isinstance(domains, bool): + def allowed_domain_bool(entity_id: str, keys: Tuple[str]) \ + -> Union[None, bool]: + """Test if allowed domain.""" + return domains + + funcs.append(allowed_domain_bool) + + elif domains is not None: + def allowed_domain_dict(entity_id: str, keys: Tuple[str]) \ + -> Union[None, bool]: + """Test if allowed domain.""" + domain = entity_id.split(".", 1)[0] + return _entity_allowed(domains.get(domain), keys) # type: ignore + + funcs.append(allowed_domain_dict) + + if isinstance(all_entities, bool): + def allowed_all_entities_bool(entity_id: str, keys: Tuple[str]) \ + -> Union[None, bool]: + """Test if allowed domain.""" + return all_entities + funcs.append(allowed_all_entities_bool) + + elif all_entities is not None: + def allowed_all_entities_dict(entity_id: str, keys: Tuple[str]) \ + -> Union[None, bool]: + """Test if allowed domain.""" + return _entity_allowed(all_entities, keys) + funcs.append(allowed_all_entities_dict) + + # Can happen if no valid subcategories specified + if not funcs: + def apply_policy_deny_all_2(entity_id: str, keys: Tuple[str]) -> bool: + """Decline all.""" + return False + + return apply_policy_deny_all_2 + + if len(funcs) == 1: + func = funcs[0] + + @wraps(func) + def apply_policy_func(entity_id: str, keys: Tuple[str]) -> bool: + """Apply a single policy function.""" + return func(entity_id, keys) is True + + return apply_policy_func + + def apply_policy_funcs(entity_id: str, keys: Tuple[str]) -> bool: + """Apply several policy functions.""" + for func in funcs: + result = func(entity_id, keys) + if result is not None: + return result + return False + + return apply_policy_funcs diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py new file mode 100644 index 00000000000..32cbfefcf1c --- /dev/null +++ b/homeassistant/auth/permissions/merge.py @@ -0,0 +1,65 @@ +"""Merging of policies.""" +from typing import ( # noqa: F401 + cast, Dict, List, Set) + +from .common import PolicyType, CategoryType + + +def merge_policies(policies: List[PolicyType]) -> PolicyType: + """Merge policies.""" + new_policy = {} # type: Dict[str, CategoryType] + seen = set() # type: Set[str] + for policy in policies: + for category in policy: + if category in seen: + continue + seen.add(category) + new_policy[category] = _merge_policies([ + policy.get(category) for policy in policies]) + cast(PolicyType, new_policy) + return new_policy + + +def _merge_policies(sources: List[CategoryType]) -> CategoryType: + """Merge a policy.""" + # When merging policies, the most permissive wins. + # This means we order it like this: + # True > Dict > None + # + # True: allow everything + # Dict: specify more granular permissions + # None: no opinion + # + # If there are multiple sources with a dict as policy, we recursively + # merge each key in the source. + + policy = None # type: CategoryType + seen = set() # type: Set[str] + for source in sources: + if source is None: + continue + + # A source that's True will always win. Shortcut return. + if source is True: + return True + + assert isinstance(source, dict) + + if policy is None: + policy = cast(CategoryType, {}) + + assert isinstance(policy, dict) + + for key in source: + if key in seen: + continue + seen.add(key) + + key_sources = [] + for src in sources: + if isinstance(src, dict): + key_sources.append(src.get(key)) + + policy[key] = _merge_policies(key_sources) + + return policy diff --git a/tests/auth/permissions/__init__.py b/tests/auth/permissions/__init__.py new file mode 100644 index 00000000000..dd0343dadc3 --- /dev/null +++ b/tests/auth/permissions/__init__.py @@ -0,0 +1 @@ +"""Tests for permissions.""" diff --git a/tests/auth/permissions/test_entities.py b/tests/auth/permissions/test_entities.py new file mode 100644 index 00000000000..33c164d12b4 --- /dev/null +++ b/tests/auth/permissions/test_entities.py @@ -0,0 +1,187 @@ +"""Tests for entity permissions.""" +import pytest +import voluptuous as vol + +from homeassistant.auth.permissions.entities import ( + compile_entities, ENTITY_POLICY_SCHEMA) + + +def test_entities_none(): + """Test entity ID policy.""" + policy = None + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is False + + +def test_entities_empty(): + """Test entity ID policy.""" + policy = {} + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is False + + +def test_entities_false(): + """Test entity ID policy.""" + policy = False + with pytest.raises(vol.Invalid): + ENTITY_POLICY_SCHEMA(policy) + + +def test_entities_true(): + """Test entity ID policy.""" + policy = True + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + + +def test_entities_domains_true(): + """Test entity ID policy.""" + policy = { + 'domains': True + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + + +def test_entities_domains_domain_true(): + """Test entity ID policy.""" + policy = { + 'domains': { + 'light': True + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('switch.kitchen', ('read',)) is False + + +def test_entities_domains_domain_false(): + """Test entity ID policy.""" + policy = { + 'domains': { + 'light': False + } + } + with pytest.raises(vol.Invalid): + ENTITY_POLICY_SCHEMA(policy) + + +def test_entities_entity_ids_true(): + """Test entity ID policy.""" + policy = { + 'entity_ids': True + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + + +def test_entities_entity_ids_false(): + """Test entity ID policy.""" + policy = { + 'entity_ids': False + } + with pytest.raises(vol.Invalid): + ENTITY_POLICY_SCHEMA(policy) + + +def test_entities_entity_ids_entity_id_true(): + """Test entity ID policy.""" + policy = { + 'entity_ids': { + 'light.kitchen': True + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('switch.kitchen', ('read',)) is False + + +def test_entities_entity_ids_entity_id_false(): + """Test entity ID policy.""" + policy = { + 'entity_ids': { + 'light.kitchen': False + } + } + with pytest.raises(vol.Invalid): + ENTITY_POLICY_SCHEMA(policy) + + +def test_entities_control_only(): + """Test policy granting control only.""" + policy = { + 'entity_ids': { + 'light.kitchen': { + 'read': True, + } + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('light.kitchen', ('control',)) is False + assert compiled('light.kitchen', ('edit',)) is False + + +def test_entities_read_control(): + """Test policy granting control only.""" + policy = { + 'domains': { + 'light': { + 'read': True, + 'control': True, + } + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('light.kitchen', ('control',)) is True + assert compiled('light.kitchen', ('edit',)) is False + + +def test_entities_all_allow(): + """Test policy allowing all entities.""" + policy = { + 'all': True + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('light.kitchen', ('control',)) is True + assert compiled('switch.kitchen', ('read',)) is True + + +def test_entities_all_read(): + """Test policy applying read to all entities.""" + policy = { + 'all': { + 'read': True + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is True + assert compiled('light.kitchen', ('control',)) is False + assert compiled('switch.kitchen', ('read',)) is True + + +def test_entities_all_control(): + """Test entity ID policy applying control to all.""" + policy = { + 'all': { + 'control': True + } + } + ENTITY_POLICY_SCHEMA(policy) + compiled = compile_entities(policy) + assert compiled('light.kitchen', ('read',)) is False + assert compiled('light.kitchen', ('control',)) is True + assert compiled('switch.kitchen', ('read',)) is False + assert compiled('switch.kitchen', ('control',)) is True diff --git a/tests/auth/permissions/test_init.py b/tests/auth/permissions/test_init.py new file mode 100644 index 00000000000..60ec3cb4314 --- /dev/null +++ b/tests/auth/permissions/test_init.py @@ -0,0 +1,46 @@ +"""Tests for the auth permission system.""" +from homeassistant.core import State +from homeassistant.auth import permissions + + +def test_policy_perm_filter_states(): + """Test filtering entitites.""" + states = [ + State('light.kitchen', 'on'), + State('light.living_room', 'off'), + State('light.balcony', 'on'), + ] + perm = permissions.PolicyPermissions({ + 'entities': { + 'entity_ids': { + 'light.kitchen': True, + 'light.balcony': True, + } + } + }) + filtered = perm.filter_states(states) + assert len(filtered) == 2 + assert filtered == [states[0], states[2]] + + +def test_owner_permissions(): + """Test owner permissions access all.""" + assert permissions.OwnerPermissions.check_entity('light.kitchen', 'write') + states = [ + State('light.kitchen', 'on'), + State('light.living_room', 'off'), + State('light.balcony', 'on'), + ] + assert permissions.OwnerPermissions.filter_states(states) == states + + +def test_default_policy_allow_all(): + """Test that the default policy is to allow all entity actions.""" + perm = permissions.PolicyPermissions(permissions.DEFAULT_POLICY) + assert perm.check_entity('light.kitchen', 'read') + states = [ + State('light.kitchen', 'on'), + State('light.living_room', 'off'), + State('light.balcony', 'on'), + ] + assert perm.filter_states(states) == states diff --git a/tests/auth/permissions/test_merge.py b/tests/auth/permissions/test_merge.py new file mode 100644 index 00000000000..901e027a146 --- /dev/null +++ b/tests/auth/permissions/test_merge.py @@ -0,0 +1,44 @@ +"""Tests for permissions merging.""" +from homeassistant.auth.permissions.merge import merge_policies + + +def test_merging_permissions_true_rules_dict(): + """Test merging policy with two entities.""" + policy1 = { + 'something_else': True, + 'entities': { + 'entity_ids': { + 'light.kitchen': True, + } + } + } + policy2 = { + 'entities': { + 'entity_ids': True + } + } + assert merge_policies([policy1, policy2]) == { + 'something_else': True, + 'entities': { + 'entity_ids': True + } + } + + +def test_merging_permissions_multiple_subcategories(): + """Test merging policy with two entities.""" + policy1 = { + 'entities': None + } + policy2 = { + 'entities': { + 'entity_ids': True, + } + } + policy3 = { + 'entities': True + } + assert merge_policies([policy1, policy2]) == policy2 + assert merge_policies([policy1, policy3]) == policy3 + + assert merge_policies([policy2, policy3]) == policy3 diff --git a/tests/auth/test_models.py b/tests/auth/test_models.py index c84bdc7390b..b02111e8d02 100644 --- a/tests/auth/test_models.py +++ b/tests/auth/test_models.py @@ -29,6 +29,6 @@ def test_permissions_merged(): # Make sure we cache instance assert user.permissions is user.permissions - assert user.permissions.check_entity('switch.bla') is True - assert user.permissions.check_entity('light.kitchen') is True - assert user.permissions.check_entity('light.not_kitchen') is False + assert user.permissions.check_entity('switch.bla', 'read') is True + assert user.permissions.check_entity('light.kitchen', 'read') is True + assert user.permissions.check_entity('light.not_kitchen', 'read') is False diff --git a/tests/auth/test_permissions.py b/tests/auth/test_permissions.py deleted file mode 100644 index 71582dc281d..00000000000 --- a/tests/auth/test_permissions.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Tests for the auth permission system.""" -import pytest -import voluptuous as vol - -from homeassistant.core import State -from homeassistant.auth import permissions - - -def test_entities_none(): - """Test entity ID policy.""" - policy = None - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is False - - -def test_entities_empty(): - """Test entity ID policy.""" - policy = {} - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is False - - -def test_entities_false(): - """Test entity ID policy.""" - policy = False - with pytest.raises(vol.Invalid): - permissions.ENTITY_POLICY_SCHEMA(policy) - - -def test_entities_true(): - """Test entity ID policy.""" - policy = True - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is True - - -def test_entities_domains_true(): - """Test entity ID policy.""" - policy = { - 'domains': True - } - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is True - - -def test_entities_domains_domain_true(): - """Test entity ID policy.""" - policy = { - 'domains': { - 'light': True - } - } - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is True - assert compiled('switch.kitchen', []) is False - - -def test_entities_domains_domain_false(): - """Test entity ID policy.""" - policy = { - 'domains': { - 'light': False - } - } - with pytest.raises(vol.Invalid): - permissions.ENTITY_POLICY_SCHEMA(policy) - - -def test_entities_entity_ids_true(): - """Test entity ID policy.""" - policy = { - 'entity_ids': True - } - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is True - - -def test_entities_entity_ids_false(): - """Test entity ID policy.""" - policy = { - 'entity_ids': False - } - with pytest.raises(vol.Invalid): - permissions.ENTITY_POLICY_SCHEMA(policy) - - -def test_entities_entity_ids_entity_id_true(): - """Test entity ID policy.""" - policy = { - 'entity_ids': { - 'light.kitchen': True - } - } - permissions.ENTITY_POLICY_SCHEMA(policy) - compiled = permissions._compile_entities(policy) - assert compiled('light.kitchen', []) is True - assert compiled('switch.kitchen', []) is False - - -def test_entities_entity_ids_entity_id_false(): - """Test entity ID policy.""" - policy = { - 'entity_ids': { - 'light.kitchen': False - } - } - with pytest.raises(vol.Invalid): - permissions.ENTITY_POLICY_SCHEMA(policy) - - -def test_policy_perm_filter_states(): - """Test filtering entitites.""" - states = [ - State('light.kitchen', 'on'), - State('light.living_room', 'off'), - State('light.balcony', 'on'), - ] - perm = permissions.PolicyPermissions({ - 'entities': { - 'entity_ids': { - 'light.kitchen': True, - 'light.balcony': True, - } - } - }) - filtered = perm.filter_states(states) - assert len(filtered) == 2 - assert filtered == [states[0], states[2]] - - -def test_owner_permissions(): - """Test owner permissions access all.""" - assert permissions.OwnerPermissions.check_entity('light.kitchen') - states = [ - State('light.kitchen', 'on'), - State('light.living_room', 'off'), - State('light.balcony', 'on'), - ] - assert permissions.OwnerPermissions.filter_states(states) == states - - -def test_default_policy_allow_all(): - """Test that the default policy is to allow all entity actions.""" - perm = permissions.PolicyPermissions(permissions.DEFAULT_POLICY) - assert perm.check_entity('light.kitchen') - states = [ - State('light.kitchen', 'on'), - State('light.living_room', 'off'), - State('light.balcony', 'on'), - ] - assert perm.filter_states(states) == states - - -def test_merging_permissions_true_rules_dict(): - """Test merging policy with two entities.""" - policy1 = { - 'something_else': True, - 'entities': { - 'entity_ids': { - 'light.kitchen': True, - } - } - } - policy2 = { - 'entities': { - 'entity_ids': True - } - } - assert permissions.merge_policies([policy1, policy2]) == { - 'something_else': True, - 'entities': { - 'entity_ids': True - } - } - - -def test_merging_permissions_multiple_subcategories(): - """Test merging policy with two entities.""" - policy1 = { - 'entities': None - } - policy2 = { - 'entities': { - 'entity_ids': True, - } - } - policy3 = { - 'entities': True - } - assert permissions.merge_policies([policy1, policy2]) == policy2 - assert permissions.merge_policies([policy1, policy3]) == policy3 - - assert permissions.merge_policies([policy2, policy3]) == policy3 From 360addfb0b69b5e8deeac7e182ab3998a9acf86e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 15:49:57 +0100 Subject: [PATCH 105/230] Fix incorrect chevy discovery (#17942) --- homeassistant/components/mychevy.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mychevy.py b/homeassistant/components/mychevy.py index 292e56418fc..baac86f4bf1 100644 --- a/homeassistant/components/mychevy.py +++ b/homeassistant/components/mychevy.py @@ -70,7 +70,8 @@ def setup(hass, base_config): email = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - hass.data[DOMAIN] = MyChevyHub(mc.MyChevy(email, password), hass) + hass.data[DOMAIN] = MyChevyHub(mc.MyChevy(email, password), hass, + base_config) hass.data[DOMAIN].start() return True @@ -90,11 +91,12 @@ class MyChevyHub(threading.Thread): starts. """ - def __init__(self, client, hass): + def __init__(self, client, hass, hass_config): """Initialize MyChevy Hub.""" super().__init__() self._client = client self.hass = hass + self.hass_config = hass_config self.cars = [] self.status = None self.ready = False @@ -111,8 +113,10 @@ class MyChevyHub(threading.Thread): self._client.get_cars() self.cars = self._client.cars if self.ready is not True: - discovery.load_platform(self.hass, 'sensor', DOMAIN, {}, {}) - discovery.load_platform(self.hass, 'binary_sensor', DOMAIN, {}, {}) + discovery.load_platform(self.hass, 'sensor', DOMAIN, {}, + self.hass_config) + discovery.load_platform(self.hass, 'binary_sensor', DOMAIN, {}, + self.hass_config) self.ready = True self.cars = self._client.update_cars() From 374042472518ace7ee35b1781186aa014bf5f592 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 15:50:44 +0100 Subject: [PATCH 106/230] Fix venv check (#17939) * Fix venv check * Lint --- homeassistant/components/updater.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index 6f7b75cf549..2e32960573d 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -24,6 +24,7 @@ from homeassistant.helpers import event from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util +from homeassistant.util.package import is_virtual_env REQUIREMENTS = ['distro==1.3.0'] @@ -133,7 +134,7 @@ async def get_system_info(hass, include_components): 'python_version': platform.python_version(), 'timezone': dt_util.DEFAULT_TIME_ZONE.zone, 'version': current_version, - 'virtualenv': os.environ.get('VIRTUAL_ENV') is not None, + 'virtualenv': is_virtual_env(), 'hassio': hass.components.hassio.is_hassio(), } From 98dfbf2565aa47365c75cdd17a3e819070eb4f4b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 15:52:30 +0100 Subject: [PATCH 107/230] Disable upnp from being discovered (#17937) --- homeassistant/components/discovery.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index d7bb966a2d3..96c79053dff 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -51,7 +51,6 @@ CONFIG_ENTRY_HANDLERS = { SERVICE_HUE: 'hue', SERVICE_IKEA_TRADFRI: 'tradfri', 'sonos': 'sonos', - 'igd': 'upnp', } SERVICE_HANDLERS = { From b03e6050c51bd0cea031dbed60c09c0b430fb88a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 29 Oct 2018 19:09:54 +0100 Subject: [PATCH 108/230] Fix controller not being stored when setup fails and sequentially fails the retry functionality (#17927) --- homeassistant/components/unifi/__init__.py | 9 +++++---- tests/components/unifi/test_init.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index 26b60aecf42..1e07d8cb83d 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -32,20 +32,21 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up the UniFi component.""" - controller = UniFiController(hass, config_entry) - if DOMAIN not in hass.data: hass.data[DOMAIN] = {} + + controller = UniFiController(hass, config_entry) + controller_id = CONTROLLER_ID.format( host=config_entry.data[CONF_CONTROLLER][CONF_HOST], site=config_entry.data[CONF_CONTROLLER][CONF_SITE_ID] ) + hass.data[DOMAIN][controller_id] = controller + if not await controller.async_setup(): return False - hass.data[DOMAIN][controller_id] = controller - if controller.mac is None: return True diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 400dd3fd93e..0115801eec6 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -54,7 +54,7 @@ async def test_successful_config_entry(hass): async def test_controller_fail_setup(hass): - """Test that configured options for a host are loaded via config entry.""" + """Test that a failed setup still stores controller.""" entry = MockConfigEntry(domain=unifi.DOMAIN, data={ 'controller': { 'host': '0.0.0.0', @@ -75,7 +75,7 @@ async def test_controller_fail_setup(hass): controller_id = unifi.CONTROLLER_ID.format( host='0.0.0.0', site='default' ) - assert controller_id not in hass.data[unifi.DOMAIN] + assert controller_id in hass.data[unifi.DOMAIN] async def test_controller_no_mac(hass): From 6ae345b01c1a7bcbd8cccc3a107be1eb0231c200 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 19:21:21 +0100 Subject: [PATCH 109/230] Pass hass_config to load_platform (#17952) * Pass hass_config to load_platform * Fix tests * Lint --- homeassistant/components/apple_tv.py | 10 ++++----- homeassistant/components/elkm1/__init__.py | 3 ++- homeassistant/components/evohome.py | 2 +- homeassistant/components/google.py | 22 +++++++++++-------- homeassistant/components/maxcube.py | 4 ++-- homeassistant/components/melissa.py | 4 ++-- .../components/mysensors/__init__.py | 2 +- homeassistant/components/mysensors/gateway.py | 21 ++++++++++-------- homeassistant/components/octoprint.py | 5 +++-- homeassistant/components/opentherm_gw.py | 11 +++++----- homeassistant/components/smappee.py | 4 ++-- homeassistant/components/spc.py | 4 ++-- homeassistant/components/spider.py | 2 +- homeassistant/helpers/discovery.py | 9 ++++---- tests/components/device_tracker/test_init.py | 2 +- tests/components/light/test_mqtt_json.py | 2 +- tests/components/lock/test_mqtt.py | 2 +- tests/components/notify/test_demo.py | 3 ++- tests/components/test_google.py | 3 ++- tests/helpers/test_discovery.py | 9 ++++---- tests/helpers/test_entity_component.py | 2 +- 21 files changed, 70 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index 012e71a08a7..b8774d76873 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -163,7 +163,7 @@ async def async_setup(hass, config): async def atv_discovered(service, info): """Set up an Apple TV that was auto discovered.""" - await _setup_atv(hass, { + await _setup_atv(hass, config, { CONF_NAME: info['name'], CONF_HOST: info['host'], CONF_LOGIN_ID: info['properties']['hG'], @@ -172,7 +172,7 @@ async def async_setup(hass, config): discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered) - tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])] + tasks = [_setup_atv(hass, config, conf) for conf in config.get(DOMAIN, [])] if tasks: await asyncio.wait(tasks, loop=hass.loop) @@ -187,7 +187,7 @@ async def async_setup(hass, config): return True -async def _setup_atv(hass, atv_config): +async def _setup_atv(hass, hass_config, atv_config): """Set up an Apple TV.""" import pyatv name = atv_config.get(CONF_NAME) @@ -212,10 +212,10 @@ async def _setup_atv(hass, atv_config): } hass.async_create_task(discovery.async_load_platform( - hass, 'media_player', DOMAIN, atv_config)) + hass, 'media_player', DOMAIN, atv_config, hass_config)) hass.async_create_task(discovery.async_load_platform( - hass, 'remote', DOMAIN, atv_config)) + hass, 'remote', DOMAIN, atv_config, hass_config)) class AppleTVPowerManager: diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 76594e16736..aa7b9973c8e 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -146,7 +146,8 @@ async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: hass.data[DOMAIN] = {'elk': elk, 'config': config, 'keypads': {}} for component in SUPPORTED_DOMAINS: hass.async_create_task( - discovery.async_load_platform(hass, component, DOMAIN, {})) + discovery.async_load_platform(hass, component, DOMAIN, {}, + hass_config)) return True diff --git a/homeassistant/components/evohome.py b/homeassistant/components/evohome.py index ceeec407b05..397d3b9f6c0 100644 --- a/homeassistant/components/evohome.py +++ b/homeassistant/components/evohome.py @@ -140,6 +140,6 @@ def setup(hass, config): _LOGGER.debug("setup(), location = %s", tmp_loc) - load_platform(hass, 'climate', DOMAIN) + load_platform(hass, 'climate', DOMAIN, {}, config) return True diff --git a/homeassistant/components/google.py b/homeassistant/components/google.py index e37b3ba7ff7..49cb195d6c9 100644 --- a/homeassistant/components/google.py +++ b/homeassistant/components/google.py @@ -88,7 +88,7 @@ DEVICE_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -def do_authentication(hass, config): +def do_authentication(hass, hass_config, config): """Notify user of actions and authenticate. Notify user of user_code and verification_url then poll @@ -145,7 +145,7 @@ def do_authentication(hass, config): storage = Storage(hass.config.path(TOKEN_FILE)) storage.put(credentials) - do_setup(hass, config) + do_setup(hass, hass_config, config) listener() hass.components.persistent_notification.create( 'We are all setup now. Check {} for calendars that have ' @@ -167,14 +167,15 @@ def setup(hass, config): token_file = hass.config.path(TOKEN_FILE) if not os.path.isfile(token_file): - do_authentication(hass, conf) + do_authentication(hass, config, conf) else: - do_setup(hass, conf) + do_setup(hass, config, conf) return True -def setup_services(hass, track_new_found_calendars, calendar_service): +def setup_services(hass, hass_config, track_new_found_calendars, + calendar_service): """Set up the service listeners.""" def _found_calendar(call): """Check if we know about a calendar and generate PLATFORM_DISCOVER.""" @@ -190,7 +191,8 @@ def setup_services(hass, track_new_found_calendars, calendar_service): ) discovery.load_platform(hass, 'calendar', DOMAIN, - hass.data[DATA_INDEX][calendar[CONF_CAL_ID]]) + hass.data[DATA_INDEX][calendar[CONF_CAL_ID]], + hass_config) hass.services.register( DOMAIN, SERVICE_FOUND_CALENDARS, _found_calendar) @@ -210,7 +212,7 @@ def setup_services(hass, track_new_found_calendars, calendar_service): return True -def do_setup(hass, config): +def do_setup(hass, hass_config, config): """Run the setup after we have everything configured.""" # Load calendars the user has configured hass.data[DATA_INDEX] = load_config(hass.config.path(YAML_DEVICES)) @@ -218,13 +220,15 @@ def do_setup(hass, config): calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE)) track_new_found_calendars = convert(config.get(CONF_TRACK_NEW), bool, DEFAULT_CONF_TRACK_NEW) - setup_services(hass, track_new_found_calendars, calendar_service) + setup_services(hass, hass_config, track_new_found_calendars, + calendar_service) # Ensure component is loaded setup_component(hass, 'calendar', config) for calendar in hass.data[DATA_INDEX].values(): - discovery.load_platform(hass, 'calendar', DOMAIN, calendar) + discovery.load_platform(hass, 'calendar', DOMAIN, calendar, + hass_config) # Look for any new calendars hass.services.call(DOMAIN, SERVICE_SCAN_CALENDARS, None) diff --git a/homeassistant/components/maxcube.py b/homeassistant/components/maxcube.py index b574f0bcb15..9980d554232 100644 --- a/homeassistant/components/maxcube.py +++ b/homeassistant/components/maxcube.py @@ -73,8 +73,8 @@ def setup(hass, config): if connection_failed >= len(gateways): return False - load_platform(hass, 'climate', DOMAIN) - load_platform(hass, 'binary_sensor', DOMAIN) + load_platform(hass, 'climate', DOMAIN, {}, config) + load_platform(hass, 'binary_sensor', DOMAIN, {}, config) return True diff --git a/homeassistant/components/melissa.py b/homeassistant/components/melissa.py index f5a757dbcf3..49e7d62f5cb 100644 --- a/homeassistant/components/melissa.py +++ b/homeassistant/components/melissa.py @@ -39,6 +39,6 @@ def setup(hass, config): api = melissa.Melissa(username=username, password=password) hass.data[DATA_MELISSA] = api - load_platform(hass, 'sensor', DOMAIN, {}) - load_platform(hass, 'climate', DOMAIN, {}) + load_platform(hass, 'sensor', DOMAIN, {}, config) + load_platform(hass, 'climate', DOMAIN, {}, config) return True diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index 4f00247495a..883175340ce 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -111,7 +111,7 @@ async def async_setup(hass, config): hass.data[MYSENSORS_GATEWAYS] = gateways - hass.async_create_task(finish_setup(hass, gateways)) + hass.async_create_task(finish_setup(hass, config, gateways)) return True diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 558e944f727..cb1dad922f8 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -137,7 +137,7 @@ async def _get_gateway(hass, config, gateway_conf, persistence_file): gateway.metric = hass.config.units.is_metric gateway.optimistic = conf[CONF_OPTIMISTIC] gateway.device = device - gateway.event_callback = _gw_callback_factory(hass) + gateway.event_callback = _gw_callback_factory(hass, config) gateway.nodes_config = gateway_conf[CONF_NODES] if persistence: await gateway.start_persistence() @@ -145,12 +145,13 @@ async def _get_gateway(hass, config, gateway_conf, persistence_file): return gateway -async def finish_setup(hass, gateways): +async def finish_setup(hass, hass_config, gateways): """Load any persistent devices and platforms and start gateway.""" discover_tasks = [] start_tasks = [] for gateway in gateways.values(): - discover_tasks.append(_discover_persistent_devices(hass, gateway)) + discover_tasks.append(_discover_persistent_devices( + hass, hass_config, gateway)) start_tasks.append(_gw_start(hass, gateway)) if discover_tasks: # Make sure all devices and platforms are loaded before gateway start. @@ -159,7 +160,7 @@ async def finish_setup(hass, gateways): await asyncio.wait(start_tasks, loop=hass.loop) -async def _discover_persistent_devices(hass, gateway): +async def _discover_persistent_devices(hass, hass_config, gateway): """Discover platforms for devices loaded via persistence file.""" tasks = [] new_devices = defaultdict(list) @@ -170,17 +171,18 @@ async def _discover_persistent_devices(hass, gateway): for platform, dev_ids in validated.items(): new_devices[platform].extend(dev_ids) for platform, dev_ids in new_devices.items(): - tasks.append(_discover_mysensors_platform(hass, platform, dev_ids)) + tasks.append(_discover_mysensors_platform( + hass, hass_config, platform, dev_ids)) if tasks: await asyncio.wait(tasks, loop=hass.loop) @callback -def _discover_mysensors_platform(hass, platform, new_devices): +def _discover_mysensors_platform(hass, hass_config, platform, new_devices): """Discover a MySensors platform.""" task = hass.async_create_task(discovery.async_load_platform( hass, platform, DOMAIN, - {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN})) + {ATTR_DEVICES: new_devices, CONF_NAME: DOMAIN}, hass_config)) return task @@ -215,7 +217,7 @@ async def _gw_start(hass, gateway): hass.data.pop(gateway_ready_key, None) -def _gw_callback_factory(hass): +def _gw_callback_factory(hass, hass_config): """Return a new callback for the gateway.""" @callback def mysensors_callback(msg): @@ -246,7 +248,8 @@ def _gw_callback_factory(hass): else: new_dev_ids.append(dev_id) if new_dev_ids: - _discover_mysensors_platform(hass, platform, new_dev_ids) + _discover_mysensors_platform( + hass, hass_config, platform, new_dev_ids) for signal in set(signals): # Only one signal per device is needed. # A device can have multiple platforms, ie multiple schemas. diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index 2a39ac2c44a..b626e9a93b5 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -114,11 +114,12 @@ def setup(hass, config): sensors = printer[CONF_SENSORS][CONF_MONITORED_CONDITIONS] load_platform(hass, 'sensor', DOMAIN, {'name': name, 'base_url': base_url, - 'sensors': sensors}) + 'sensors': sensors}, config) b_sensors = printer[CONF_BINARY_SENSORS][CONF_MONITORED_CONDITIONS] load_platform(hass, 'binary_sensor', DOMAIN, {'name': name, 'base_url': base_url, - 'sensors': b_sensors}) + 'sensors': b_sensors}, + config) success = True return success diff --git a/homeassistant/components/opentherm_gw.py b/homeassistant/components/opentherm_gw.py index 08807a2d2a6..7152a58afcc 100644 --- a/homeassistant/components/opentherm_gw.py +++ b/homeassistant/components/opentherm_gw.py @@ -64,9 +64,10 @@ async def async_setup(hass, config): hass.async_create_task(connect_and_subscribe( hass, conf[CONF_DEVICE], gateway)) hass.async_create_task(async_load_platform( - hass, 'climate', DOMAIN, conf.get(CONF_CLIMATE))) + hass, 'climate', DOMAIN, conf.get(CONF_CLIMATE), config)) if monitored_vars: - hass.async_create_task(setup_monitored_vars(hass, monitored_vars)) + hass.async_create_task(setup_monitored_vars( + hass, config, monitored_vars)) return True @@ -82,7 +83,7 @@ async def connect_and_subscribe(hass, device_path, gateway): gateway.subscribe(handle_report) -async def setup_monitored_vars(hass, monitored_vars): +async def setup_monitored_vars(hass, config, monitored_vars): """Set up requested sensors.""" gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] sensor_type_map = { @@ -200,6 +201,6 @@ async def setup_monitored_vars(hass, monitored_vars): _LOGGER.error("Monitored variable not supported: %s", var) if binary_sensors: hass.async_create_task(async_load_platform( - hass, COMP_BINARY_SENSOR, DOMAIN, binary_sensors)) + hass, COMP_BINARY_SENSOR, DOMAIN, binary_sensors, config)) if sensors: - await async_load_platform(hass, COMP_SENSOR, DOMAIN, sensors) + await async_load_platform(hass, COMP_SENSOR, DOMAIN, sensors, config) diff --git a/homeassistant/components/smappee.py b/homeassistant/components/smappee.py index 7904f0a6cce..d8b7a68a506 100644 --- a/homeassistant/components/smappee.py +++ b/homeassistant/components/smappee.py @@ -66,8 +66,8 @@ def setup(hass, config): return False hass.data[DATA_SMAPPEE] = smappee - load_platform(hass, 'switch', DOMAIN) - load_platform(hass, 'sensor', DOMAIN) + load_platform(hass, 'switch', DOMAIN, {}, config) + load_platform(hass, 'sensor', DOMAIN, {}, config) return True diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py index 5aa987bd0a8..dd1931e27f1 100644 --- a/homeassistant/components/spc.py +++ b/homeassistant/components/spc.py @@ -63,11 +63,11 @@ async def async_setup(hass, config): # add sensor devices for each zone (typically motion/fire/door sensors) hass.async_create_task(discovery.async_load_platform( - hass, 'binary_sensor', DOMAIN, {})) + hass, 'binary_sensor', DOMAIN, {}, config)) # create a separate alarm panel for each area hass.async_create_task(discovery.async_load_platform( - hass, 'alarm_control_panel', DOMAIN, {})) + hass, 'alarm_control_panel', DOMAIN, {}, config)) # start listening for incoming events over websocket spc.start() diff --git a/homeassistant/components/spider.py b/homeassistant/components/spider.py index 48632be6bad..a10fa129fe5 100644 --- a/homeassistant/components/spider.py +++ b/homeassistant/components/spider.py @@ -56,7 +56,7 @@ def setup(hass, config): } for component in SPIDER_COMPONENTS: - load_platform(hass, component, DOMAIN, {}) + load_platform(hass, component, DOMAIN, {}, config) _LOGGER.debug("Connection with Spider API succeeded") return True diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 698cee0fcc2..405861eeb75 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -114,8 +114,7 @@ def async_listen_platform(hass, component, callback): @bind_hass -def load_platform(hass, component, platform, discovered=None, - hass_config=None): +def load_platform(hass, component, platform, discovered, hass_config): """Load a component and platform dynamically. Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be @@ -132,8 +131,8 @@ def load_platform(hass, component, platform, discovered=None, @bind_hass -async def async_load_platform(hass, component, platform, discovered=None, - hass_config=None): +async def async_load_platform(hass, component, platform, discovered, + hass_config): """Load a component and platform dynamically. Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be @@ -149,6 +148,8 @@ async def async_load_platform(hass, component, platform, discovered=None, This method is a coroutine. """ + assert hass_config, 'You need to pass in the real hass config' + if component in DEPENDENCY_BLACKLIST: raise HomeAssistantError( 'Cannot discover the {} component.'.format(component)) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 6ceb4674faf..93de359610f 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -181,7 +181,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): assert device_tracker.DOMAIN not in self.hass.config.components discovery.load_platform( self.hass, device_tracker.DOMAIN, 'demo', {'test_key': 'test_val'}, - {}) + {'demo': {}}) self.hass.block_till_done() assert device_tracker.DOMAIN in self.hass.config.components assert mock_demo_setup_scanner.called diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index 9ae12bb1ca1..03a3927472a 100644 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -524,7 +524,7 @@ async def test_custom_availability_payload(hass, mqtt_mock): async def test_discovery_removal(hass, mqtt_mock, caplog): """Test removal of discovered mqtt_json lights.""" - await async_start(hass, 'homeassistant', {}) + await async_start(hass, 'homeassistant', {'mqtt': {}}) data = ( '{ "name": "Beer",' ' "platform": "mqtt_json",' diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index fd9bbad2dc4..347005c75ac 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -136,7 +136,7 @@ async def test_custom_availability_payload(hass, mqtt_mock): async def test_discovery_removal_lock(hass, mqtt_mock, caplog): """Test removal of discovered lock.""" - await async_start(hass, 'homeassistant', {}) + await async_start(hass, 'homeassistant', {'mqtt': {}}) data = ( '{ "name": "Beer",' ' "command_topic": "test_topic" }' diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 270736d1b86..57397e21ba2 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -65,7 +65,8 @@ class TestNotifyDemo(unittest.TestCase): """Test discovery of notify demo platform.""" assert notify.DOMAIN not in self.hass.config.components discovery.load_platform( - self.hass, 'notify', 'demo', {'test_key': 'test_val'}, {}) + self.hass, 'notify', 'demo', {'test_key': 'test_val'}, + {'notify': {}}) self.hass.block_till_done() assert notify.DOMAIN in self.hass.config.components assert mock_demo_get_service.called diff --git a/tests/components/test_google.py b/tests/components/test_google.py index 36c4b572bab..d08fbb7451c 100644 --- a/tests/components/test_google.py +++ b/tests/components/test_google.py @@ -85,7 +85,8 @@ class TestGoogle(unittest.TestCase): calendar_service = google.GoogleCalendarService( self.hass.config.path(google.TOKEN_FILE)) - assert google.setup_services(self.hass, True, calendar_service) + assert google.setup_services( + self.hass, {'google': {}}, True, calendar_service) # self.hass.services.call('google', 'found_calendar', calendar, # blocking=True) diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index 64f90ee7452..ffafd3ca146 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -79,15 +79,15 @@ class TestHelpersDiscovery: platform_callback) discovery.load_platform(self.hass, 'test_component', 'test_platform', - 'discovery info') + 'discovery info', {'test_component': {}}) self.hass.block_till_done() assert mock_setup_component.called assert mock_setup_component.call_args[0] == \ - (self.hass, 'test_component', None) + (self.hass, 'test_component', {'test_component': {}}) self.hass.block_till_done() discovery.load_platform(self.hass, 'test_component_2', 'test_platform', - 'discovery info') + 'discovery info', {'test_component': {}}) self.hass.block_till_done() assert len(calls) == 1 @@ -202,7 +202,8 @@ class TestHelpersDiscovery: async def test_load_platform_forbids_config(): """Test you cannot setup config component with load_platform.""" with pytest.raises(HomeAssistantError): - await discovery.async_load_platform(None, 'config', 'zwave') + await discovery.async_load_platform(None, 'config', 'zwave', {}, + {'config': {}}) async def test_discover_forbids_config(): diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index c853d0b3447..2bef8c0b53e 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -131,7 +131,7 @@ class TestHelpersEntityComponent(unittest.TestCase): component.setup({}) discovery.load_platform(self.hass, DOMAIN, 'platform_test', - {'msg': 'discovery_info'}) + {'msg': 'discovery_info'}, {DOMAIN: {}}) self.hass.block_till_done() From 027f173a08e2752554ce5c8f2b4d7b042ebcc411 Mon Sep 17 00:00:00 2001 From: jxwolstenholme Date: Mon, 29 Oct 2018 18:27:03 +0000 Subject: [PATCH 110/230] Added codeowner for bt_smarthub (#17947) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index d3562e8a72a..edec0cf0345 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -64,6 +64,7 @@ homeassistant/components/device_tracker/automatic.py @armills homeassistant/components/device_tracker/huawei_router.py @abmantis homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan homeassistant/components/device_tracker/tile.py @bachya +homeassistant/components/device_tracker/bt_smarthub.py @jxwolstenholme homeassistant/components/history_graph.py @andrey-git homeassistant/components/influx.py @fabaff homeassistant/components/light/lifx_legacy.py @amelchio From a87a5d266ec899656bfe72c3f5996d058499aae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=81uci=C3=B3w?= Date: Mon, 29 Oct 2018 19:32:58 +0100 Subject: [PATCH 111/230] Fixed copy-paste errors (#17948) --- tests/components/google_assistant/test_trait.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 3b3a158fbf8..a347b6c6fc0 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -16,6 +16,7 @@ from homeassistant.components import ( script, switch, vacuum, + group, ) from homeassistant.components.google_assistant import trait, helpers, const from homeassistant.util import color @@ -106,7 +107,7 @@ async def test_brightness_media_player(hass): async def test_onoff_group(hass): """Test OnOff trait support for group domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(group.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('group.bla', STATE_ON)) @@ -142,7 +143,7 @@ async def test_onoff_group(hass): async def test_onoff_input_boolean(hass): """Test OnOff trait support for input_boolean domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(input_boolean.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('input_boolean.bla', STATE_ON)) @@ -179,7 +180,7 @@ async def test_onoff_input_boolean(hass): async def test_onoff_switch(hass): """Test OnOff trait support for switch domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(switch.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('switch.bla', STATE_ON)) @@ -215,7 +216,7 @@ async def test_onoff_switch(hass): async def test_onoff_fan(hass): """Test OnOff trait support for fan domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(fan.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('fan.bla', STATE_ON)) @@ -251,7 +252,7 @@ async def test_onoff_fan(hass): async def test_onoff_light(hass): """Test OnOff trait support for light domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(light.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('light.bla', STATE_ON)) @@ -287,7 +288,7 @@ async def test_onoff_light(hass): async def test_onoff_cover(hass): """Test OnOff trait support for cover domain.""" - assert trait.OnOffTrait.supported(media_player.DOMAIN, 0) + assert trait.OnOffTrait.supported(cover.DOMAIN, 0) trt_on = trait.OnOffTrait(hass, State('cover.bla', cover.STATE_OPEN)) From af5eacf30372931aef0fb70c3ca201907878774f Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Mon, 29 Oct 2018 14:40:32 -0400 Subject: [PATCH 112/230] Fix spelling error in log output (#17963) --- homeassistant/components/recorder/migration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 7b257e223db..45c8f939faf 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -74,7 +74,7 @@ def _create_index(engine, table_name, index_name): if 'already exists' not in str(err).lower(): raise - _LOGGER.warning('Index %s already exists on %s, continueing', + _LOGGER.warning('Index %s already exists on %s, continuing', index_name, table_name) _LOGGER.debug("Finished creating %s", index_name) @@ -157,7 +157,7 @@ def _add_columns(engine, table_name, columns_def): return except OperationalError: # Some engines support adding all columns at once, - # this error is when they dont' + # this error is when they don't _LOGGER.info('Unable to use quick column add. Adding 1 by 1.') for column_def in columns_def: From d6913c69143f964c563b31ea3e9fbce2a35ffd2a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 20:52:34 +0100 Subject: [PATCH 113/230] Fix operation mode for Alexa thermostat (#17972) --- homeassistant/components/alexa/smart_home.py | 24 ++++++++++++-------- tests/components/alexa/test_smart_home.py | 8 +++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index d3c18131cfc..c7fedc34e03 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1,4 +1,5 @@ """Support for alexa Smart Home Skill API.""" +from collections import OrderedDict import logging import math from datetime import datetime @@ -37,16 +38,19 @@ API_TEMP_UNITS = { TEMP_CELSIUS: 'CELSIUS', } -API_THERMOSTAT_MODES = { - climate.STATE_HEAT: 'HEAT', - climate.STATE_COOL: 'COOL', - climate.STATE_AUTO: 'AUTO', - climate.STATE_ECO: 'ECO', - climate.STATE_OFF: 'OFF', - climate.STATE_IDLE: 'OFF', - climate.STATE_FAN_ONLY: 'OFF', - climate.STATE_DRY: 'OFF', -} +# Needs to be ordered dict for `async_api_set_thermostat_mode` which does a +# reverse mapping of this dict and we want to map the first occurrance of OFF +# back to HA state. +API_THERMOSTAT_MODES = OrderedDict([ + (climate.STATE_HEAT, 'HEAT'), + (climate.STATE_COOL, 'COOL'), + (climate.STATE_AUTO, 'AUTO'), + (climate.STATE_ECO, 'ECO'), + (climate.STATE_OFF, 'OFF'), + (climate.STATE_IDLE, 'OFF'), + (climate.STATE_FAN_ONLY, 'OFF'), + (climate.STATE_DRY, 'OFF') +]) SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home' diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 9f77a1b8c09..83aabb5dd4c 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -902,6 +902,14 @@ async def test_thermostat(hass): assert msg['event']['payload']['type'] == 'UNSUPPORTED_THERMOSTAT_MODE' hass.config.units.temperature_unit = TEMP_CELSIUS + call, _ = await assert_request_calls_service( + 'Alexa.ThermostatController', 'SetThermostatMode', + 'climate#test_thermostat', 'climate.set_operation_mode', + hass, + payload={'thermostatMode': 'OFF'} + ) + assert call.data['operation_mode'] == 'off' + @asyncio.coroutine def test_exclude_filters(hass): From deeb288daff33241286edfc1b1e07c7af02bf87c Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Mon, 29 Oct 2018 14:06:37 -0600 Subject: [PATCH 114/230] Change source, add attributes, and improve state of DirecTV (#17536) * Enhancements for DirecTV media player Following enhancements have been made: 1. Added debug logging 2. Added ability to change channel using select_source service of the remote platform. 3. State will now show paused if a recorded program is paused, for live TV playing will always be returned. 4. Added the following attributes: a. media_position: current position of the media (in seconds) b. media_position_updated_at: timestamp when media_position was updated. c. source: current source (channel). d. media_isbeingrecorded: if current media is being recorded or not. e. media_rating: TV/Movie rating of the media f. media_recorded: if current media is recorded or live TV g. media_starttime: Timestamp media was aired Reordered properties to follow same order as how they are in __init__.py of remote platform. * Fixed error and cleaned up few items Fixed an issue when determining if a program is recorded or not. Cleaned up some coding. * Attribute last position update only updated when position changed. The attribute media_position_updated_at will only be updated if the position changed (thus media is playing for recorded or live TV). Added assumed_state; will be set to False if in standby or when a recorded show is watched. For live TV it will be set to True. * Added some empty lines for easier reading Added some empty lines before returns to improve readability. * Seperated words in constants Seperated the words in constants. * Fix _lastupdate to _last_update Split words in _lastupdate to _last_update as I missed it. --- .../components/media_player/directv.py | 170 ++++++++++++++++-- 1 file changed, 151 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/media_player/directv.py b/homeassistant/components/media_player/directv.py index 42293ba25fe..4767428894b 100644 --- a/homeassistant/components/media_player/directv.py +++ b/homeassistant/components/media_player/directv.py @@ -4,26 +4,37 @@ Support for the DirecTV receivers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.directv/ """ +import logging import requests import voluptuous as vol from homeassistant.components.media_player import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_TVSHOW, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, - SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, MediaPlayerDevice) + SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + MediaPlayerDevice) from homeassistant.const import ( - CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PLAYING) + CONF_DEVICE, CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, + STATE_PLAYING) import homeassistant.helpers.config_validation as cv +import homeassistant.util.dt as dt_util REQUIREMENTS = ['directpy==0.5'] +_LOGGER = logging.getLogger(__name__) + +ATTR_MEDIA_CURRENTLY_RECORDING = 'media_currently_recording' +ATTR_MEDIA_RATING = 'media_rating' +ATTR_MEDIA_RECORDED = 'media_recorded' +ATTR_MEDIA_START_TIME = 'media_start_time' + DEFAULT_DEVICE = '0' DEFAULT_NAME = "DirecTV Receiver" DEFAULT_PORT = 8080 SUPPORT_DTV = SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | \ - SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_NEXT_TRACK | \ - SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY + SUPPORT_PLAY_MEDIA | SUPPORT_SELECT_SOURCE | SUPPORT_STOP | \ + SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK | SUPPORT_PLAY DATA_DIRECTV = 'data_directv' @@ -88,14 +99,46 @@ class DirecTvDevice(MediaPlayerDevice): self._name = name self._is_standby = True self._current = None + self._last_update = None + self._paused = None + self._last_position = None + self._is_recorded = None + self._assumed_state = None + + _LOGGER.debug("Created DirecTV device for %s", self._name) def update(self): """Retrieve latest state.""" + _LOGGER.debug("Updating state for %s", self._name) self._is_standby = self.dtv.get_standby() if self._is_standby: self._current = None + self._is_recorded = None + self._paused = None + self._assumed_state = False + self._last_position = None + self._last_update = None else: self._current = self.dtv.get_tuned() + self._is_recorded = self._current.get('uniqueId') is not None + self._paused = self._last_position == self._current['offset'] + self._assumed_state = self._is_recorded + self._last_position = self._current['offset'] + self._last_update = dt_util.now() if not self._paused or\ + self._last_update is None else self._last_update + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + attributes = {} + if not self._is_standby: + attributes[ATTR_MEDIA_CURRENTLY_RECORDING] =\ + self.media_currently_recording + attributes[ATTR_MEDIA_RATING] = self.media_rating + attributes[ATTR_MEDIA_RECORDED] = self.media_recorded + attributes[ATTR_MEDIA_START_TIME] = self.media_start_time + + return attributes @property def name(self): @@ -108,28 +151,72 @@ class DirecTvDevice(MediaPlayerDevice): """Return the state of the device.""" if self._is_standby: return STATE_OFF - # Haven't determined a way to see if the content is paused + + # For recorded media we can determine if it is paused or not. + # For live media we're unable to determine and will always return + # playing instead. + if self._paused: + return STATE_PAUSED + return STATE_PLAYING + @property + def assumed_state(self): + """Return if we assume the state or not.""" + return self._assumed_state + @property def media_content_id(self): """Return the content ID of current playing media.""" if self._is_standby: return None + return self._current['programId'] + @property + def media_content_type(self): + """Return the content type of current playing media.""" + if self._is_standby: + return None + + if 'episodeTitle' in self._current: + return MEDIA_TYPE_TVSHOW + + return MEDIA_TYPE_MOVIE + @property def media_duration(self): """Return the duration of current playing media in seconds.""" if self._is_standby: return None + return self._current['duration'] + @property + def media_position(self): + """Position of current playing media in seconds.""" + if self._is_standby: + return None + + return self._last_position + + @property + def media_position_updated_at(self): + """When was the position of the current playing media valid. + + Returns value from homeassistant.util.dt.utcnow(). + """ + if self._is_standby: + return None + + return self._last_update + @property def media_title(self): """Return the title of current playing media.""" if self._is_standby: return None + return self._current['title'] @property @@ -137,21 +224,8 @@ class DirecTvDevice(MediaPlayerDevice): """Return the title of current episode of TV show.""" if self._is_standby: return None - if 'episodeTitle' in self._current: - return self._current['episodeTitle'] - return None - @property - def supported_features(self): - """Flag media player features that are supported.""" - return SUPPORT_DTV - - @property - def media_content_type(self): - """Return the content type of current playing media.""" - if 'episodeTitle' in self._current: - return MEDIA_TYPE_TVSHOW - return MEDIA_TYPE_MOVIE + return self._current.get('episodeTitle') @property def media_channel(self): @@ -162,30 +236,88 @@ class DirecTvDevice(MediaPlayerDevice): return "{} ({})".format( self._current['callsign'], self._current['major']) + @property + def source(self): + """Name of the current input source.""" + if self._is_standby: + return None + + return self._current['major'] + + @property + def supported_features(self): + """Flag media player features that are supported.""" + return SUPPORT_DTV + + @property + def media_currently_recording(self): + """If the media is currently being recorded or not.""" + if self._is_standby: + return None + + return self._current['isRecording'] + + @property + def media_rating(self): + """TV Rating of the current playing media.""" + if self._is_standby: + return None + + return self._current['rating'] + + @property + def media_recorded(self): + """If the media was recorded or live.""" + if self._is_standby: + return None + + return self._is_recorded + + @property + def media_start_time(self): + """Start time the program aired.""" + if self._is_standby: + return None + + return dt_util.as_local( + dt_util.utc_from_timestamp(self._current['startTime'])) + def turn_on(self): """Turn on the receiver.""" + _LOGGER.debug("Turn on %s", self._name) self.dtv.key_press('poweron') def turn_off(self): """Turn off the receiver.""" + _LOGGER.debug("Turn off %s", self._name) self.dtv.key_press('poweroff') def media_play(self): """Send play command.""" + _LOGGER.debug("Play on %s", self._name) self.dtv.key_press('play') def media_pause(self): """Send pause command.""" + _LOGGER.debug("Pause on %s", self._name) self.dtv.key_press('pause') def media_stop(self): """Send stop command.""" + _LOGGER.debug("Stop on %s", self._name) self.dtv.key_press('stop') def media_previous_track(self): """Send rewind command.""" + _LOGGER.debug("Rewind on %s", self._name) self.dtv.key_press('rew') def media_next_track(self): """Send fast forward command.""" + _LOGGER.debug("Fast forward on %s", self._name) self.dtv.key_press('ffwd') + + def select_source(self, source): + """Select input source.""" + _LOGGER.debug("Changing channel on %s to %s", self._name, source) + self.dtv.tune_channel(source) From c38a0f1bf033caf3883a4ab55dd58d568000672d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 21:16:05 +0100 Subject: [PATCH 115/230] Update requests to 2.20.0 (#17978) --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fa0d675f4b1..193bb42dba0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ cryptography==2.3.1 pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 -requests==2.19.1 +requests==2.20.0 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index b3257ae6303..99caed7b23c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -11,7 +11,7 @@ cryptography==2.3.1 pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 -requests==2.19.1 +requests==2.20.0 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/setup.py b/setup.py index 90f2e8357fd..5bca2cc43db 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ REQUIRES = [ 'pip>=8.0.3', 'pytz>=2018.04', 'pyyaml>=3.13,<4', - 'requests==2.19.1', + 'requests==2.20.0', 'voluptuous==0.11.5', 'voluptuous-serialize==2.0.0', ] From 2f71f8908b2359be3241ea1cb20948e5c4821231 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 29 Oct 2018 21:25:17 +0100 Subject: [PATCH 116/230] Don't use keyset (#17984) --- homeassistant/components/cloud/__init__.py | 68 +--------------------- tests/components/cloud/test_init.py | 25 ++++---- 2 files changed, 13 insertions(+), 80 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 3bfc5909b0b..ba2d41a9feb 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -4,20 +4,16 @@ Component to integrate the Home Assistant cloud. For more details about this component, please refer to the documentation at https://home-assistant.io/components/cloud/ """ -import asyncio from datetime import datetime, timedelta import json import logging import os -import aiohttp -import async_timeout import voluptuous as vol from homeassistant.const import ( EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME) from homeassistant.helpers import entityfilter, config_validation as cv -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util from homeassistant.components.alexa import smart_home as alexa_sh from homeassistant.components.google_assistant import helpers as ga_h @@ -129,7 +125,6 @@ class Cloud: self._google_actions = google_actions self._gactions_config = None self._prefs = None - self.jwt_keyset = None self.id_token = None self.access_token = None self.refresh_token = None @@ -262,13 +257,6 @@ class Cloud: } self._prefs = prefs - success = await self._fetch_jwt_keyset() - - # Fetching keyset can fail if internet is not up yet. - if not success: - self.hass.helpers.event.async_call_later(5, self.async_start) - return - def load_config(): """Load config.""" # Ensure config dir exists @@ -288,14 +276,6 @@ class Cloud: if info is None: return - # Validate tokens - try: - for token in 'id_token', 'access_token': - self._decode_claims(info[token]) - except ValueError as err: # Raised when token is invalid - _LOGGER.warning("Found invalid token %s: %s", token, err) - return - self.id_token = info['id_token'] self.access_token = info['access_token'] self.refresh_token = info['refresh_token'] @@ -311,49 +291,7 @@ class Cloud: self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled await self._store.async_save(self._prefs) - async def _fetch_jwt_keyset(self): - """Fetch the JWT keyset for the Cognito instance.""" - session = async_get_clientsession(self.hass) - url = ("https://cognito-idp.us-east-1.amazonaws.com/" - "{}/.well-known/jwks.json".format(self.user_pool_id)) - - try: - with async_timeout.timeout(10, loop=self.hass.loop): - req = await session.get(url) - self.jwt_keyset = await req.json() - - return True - - except (asyncio.TimeoutError, aiohttp.ClientError) as err: - _LOGGER.error("Error fetching Cognito keyset: %s", err) - return False - - def _decode_claims(self, token): + def _decode_claims(self, token): # pylint: disable=no-self-use """Decode the claims in a token.""" - from jose import jwt, exceptions as jose_exceptions - try: - header = jwt.get_unverified_header(token) - except jose_exceptions.JWTError as err: - raise ValueError(str(err)) from None - kid = header.get('kid') - - if kid is None: - raise ValueError("No kid in header") - - # Locate the key for this kid - key = None - for key_dict in self.jwt_keyset['keys']: - if key_dict['kid'] == kid: - key = key_dict - break - if not key: - raise ValueError( - "Unable to locate kid ({}) in keyset".format(kid)) - - try: - return jwt.decode( - token, key, audience=self.cognito_client_id, options={ - 'verify_exp': False, - }) - except jose_exceptions.JWTError as err: - raise ValueError(str(err)) from None + from jose import jwt + return jwt.get_unverified_claims(token) diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 61518f0f0e8..44d56566f75 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -32,8 +32,7 @@ def test_constructor_loads_info_from_constant(): 'google_actions_sync_url': 'test-google_actions_sync_url', 'subscription_info_url': 'test-subscription-info-url' } - }), patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset', - return_value=mock_coro(True)): + }): result = yield from cloud.async_setup(hass, { 'cloud': {cloud.CONF_MODE: 'beer'} }) @@ -54,17 +53,15 @@ def test_constructor_loads_info_from_config(): """Test non-dev mode loads info from SERVERS constant.""" hass = MagicMock(data={}) - with patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset', - return_value=mock_coro(True)): - result = yield from cloud.async_setup(hass, { - 'cloud': { - cloud.CONF_MODE: cloud.MODE_DEV, - 'cognito_client_id': 'test-cognito_client_id', - 'user_pool_id': 'test-user_pool_id', - 'region': 'test-region', - 'relayer': 'test-relayer', - } - }) + result = yield from cloud.async_setup(hass, { + 'cloud': { + cloud.CONF_MODE: cloud.MODE_DEV, + 'cognito_client_id': 'test-cognito_client_id', + 'user_pool_id': 'test-user_pool_id', + 'region': 'test-region', + 'relayer': 'test-relayer', + } + }) assert result cl = hass.data['cloud'] @@ -89,8 +86,6 @@ async def test_initialize_loads_info(mock_os, hass): cl.iot.connect.return_value = mock_coro() with patch('homeassistant.components.cloud.open', mopen, create=True), \ - patch('homeassistant.components.cloud.Cloud._fetch_jwt_keyset', - return_value=mock_coro(True)), \ patch('homeassistant.components.cloud.Cloud._decode_claims'): await cl.async_start(None) From 3169c0416e37ea28db3df4a998c5a3b6dd3aeb0d Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Mon, 29 Oct 2018 17:57:27 -0400 Subject: [PATCH 117/230] Update Alexa tests to async syntax (#17965) See https://github.com/home-assistant/home-assistant/issues/12614 --- tests/components/alexa/test_smart_home.py | 345 ++++++++++------------ 1 file changed, 152 insertions(+), 193 deletions(-) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 83aabb5dd4c..ab268fe860f 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1,5 +1,4 @@ """Test for smart home alexa support.""" -import asyncio import json from uuid import uuid4 @@ -105,25 +104,23 @@ def test_create_api_message_special(): assert 'endpoint' not in msg -@asyncio.coroutine -def test_wrong_version(hass): +async def test_wrong_version(hass): """Test with wrong version.""" msg = get_new_request('Alexa.PowerController', 'TurnOn') msg['directive']['header']['payloadVersion'] = '2' with pytest.raises(AssertionError): - yield from smart_home.async_handle_message(hass, DEFAULT_CONFIG, msg) + await smart_home.async_handle_message(hass, DEFAULT_CONFIG, msg) -@asyncio.coroutine -def discovery_test(device, hass, expected_endpoints=1): +async def discovery_test(device, hass, expected_endpoints=1): """Test alexa discovery request.""" request = get_new_request('Alexa.Discovery', 'Discover') # setup test devices hass.states.async_set(*device) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) assert 'event' in msg @@ -156,54 +153,51 @@ def assert_endpoint_capabilities(endpoint, *interfaces): return capabilities -@asyncio.coroutine -def test_switch(hass, events): +async def test_switch(hass, events): """Test switch discovery.""" device = ('switch.test', 'on', {'friendly_name': "Test switch"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'switch#test' assert appliance['displayCategories'][0] == "SWITCH" assert appliance['friendlyName'] == "Test switch" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'switch#test', 'switch.turn_on', 'switch.turn_off', hass) - properties = yield from reported_properties(hass, 'switch#test') + properties = await reported_properties(hass, 'switch#test') properties.assert_equal('Alexa.PowerController', 'powerState', 'ON') -@asyncio.coroutine -def test_light(hass): +async def test_light(hass): """Test light discovery.""" device = ('light.test_1', 'on', {'friendly_name': "Test light 1"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'light#test_1' assert appliance['displayCategories'][0] == "LIGHT" assert appliance['friendlyName'] == "Test light 1" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'light#test_1', 'light.turn_on', 'light.turn_off', hass) -@asyncio.coroutine -def test_dimmable_light(hass): +async def test_dimmable_light(hass): """Test dimmable light discovery.""" device = ( 'light.test_2', 'on', { 'brightness': 128, 'friendly_name': "Test light 2", 'supported_features': 1 }) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'light#test_2' assert appliance['displayCategories'][0] == "LIGHT" @@ -215,11 +209,11 @@ def test_dimmable_light(hass): 'Alexa.PowerController', ) - properties = yield from reported_properties(hass, 'light#test_2') + properties = await reported_properties(hass, 'light#test_2') properties.assert_equal('Alexa.PowerController', 'powerState', 'ON') properties.assert_equal('Alexa.BrightnessController', 'brightness', 50) - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.BrightnessController', 'SetBrightness', 'light#test_2', 'light.turn_on', hass, @@ -227,8 +221,7 @@ def test_dimmable_light(hass): assert call.data['brightness_pct'] == 50 -@asyncio.coroutine -def test_color_light(hass): +async def test_color_light(hass): """Test color light discovery.""" device = ( 'light.test_3', @@ -240,7 +233,7 @@ def test_color_light(hass): 'color_temp': '333', } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'light#test_3' assert appliance['displayCategories'][0] == "LIGHT" @@ -258,11 +251,10 @@ def test_color_light(hass): # tests -@asyncio.coroutine -def test_script(hass): +async def test_script(hass): """Test script discovery.""" device = ('script.test', 'off', {'friendly_name': "Test script"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'script#test' assert appliance['displayCategories'][0] == "ACTIVITY_TRIGGER" @@ -273,22 +265,21 @@ def test_script(hass): 'Alexa.SceneController') assert not capability['supportsDeactivation'] - yield from assert_scene_controller_works( + await assert_scene_controller_works( 'script#test', 'script.turn_on', None, hass) -@asyncio.coroutine -def test_cancelable_script(hass): +async def test_cancelable_script(hass): """Test cancalable script discovery.""" device = ( 'script.test_2', 'off', {'friendly_name': "Test script 2", 'can_cancel': True}, ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'script#test_2' (capability,) = assert_endpoint_capabilities( @@ -296,40 +287,38 @@ def test_cancelable_script(hass): 'Alexa.SceneController') assert capability['supportsDeactivation'] - yield from assert_scene_controller_works( + await assert_scene_controller_works( 'script#test_2', 'script.turn_on', 'script.turn_off', hass) -@asyncio.coroutine -def test_input_boolean(hass): +async def test_input_boolean(hass): """Test input boolean discovery.""" device = ( 'input_boolean.test', 'off', {'friendly_name': "Test input boolean"}, ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'input_boolean#test' assert appliance['displayCategories'][0] == "OTHER" assert appliance['friendlyName'] == "Test input boolean" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'input_boolean#test', 'input_boolean.turn_on', 'input_boolean.turn_off', hass) -@asyncio.coroutine -def test_scene(hass): +async def test_scene(hass): """Test scene discovery.""" device = ('scene.test', 'off', {'friendly_name': "Test scene"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'scene#test' assert appliance['displayCategories'][0] == "SCENE_TRIGGER" @@ -340,18 +329,17 @@ def test_scene(hass): 'Alexa.SceneController') assert not capability['supportsDeactivation'] - yield from assert_scene_controller_works( + await assert_scene_controller_works( 'scene#test', 'scene.turn_on', None, hass) -@asyncio.coroutine -def test_fan(hass): +async def test_fan(hass): """Test fan discovery.""" device = ('fan.test_1', 'off', {'friendly_name': "Test fan 1"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'fan#test_1' assert appliance['displayCategories'][0] == "OTHER" @@ -359,8 +347,7 @@ def test_fan(hass): assert_endpoint_capabilities(appliance, 'Alexa.PowerController') -@asyncio.coroutine -def test_variable_fan(hass): +async def test_variable_fan(hass): """Test fan discovery. This one has variable speed. @@ -374,7 +361,7 @@ def test_variable_fan(hass): 'speed': 'high', } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'fan#test_2' assert appliance['displayCategories'][0] == "OTHER" @@ -386,14 +373,14 @@ def test_variable_fan(hass): 'Alexa.PowerController', ) - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.PercentageController', 'SetPercentage', 'fan#test_2', 'fan.set_speed', hass, payload={'percentage': '50'}) assert call.data['speed'] == 'medium' - yield from assert_percentage_changes( + await assert_percentage_changes( hass, [('high', '-5'), ('off', '5'), ('low', '-80')], 'Alexa.PercentageController', 'AdjustPercentage', 'fan#test_2', @@ -402,18 +389,17 @@ def test_variable_fan(hass): 'speed') -@asyncio.coroutine -def test_lock(hass): +async def test_lock(hass): """Test lock discovery.""" device = ('lock.test', 'off', {'friendly_name': "Test lock"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'lock#test' assert appliance['displayCategories'][0] == "SMARTLOCK" assert appliance['friendlyName'] == "Test lock" assert_endpoint_capabilities(appliance, 'Alexa.LockController') - _, msg = yield from assert_request_calls_service( + _, msg = await assert_request_calls_service( 'Alexa.LockController', 'Lock', 'lock#test', 'lock.lock', hass) @@ -425,8 +411,7 @@ def test_lock(hass): assert properties['value'] == 'LOCKED' -@asyncio.coroutine -def test_media_player(hass): +async def test_media_player(hass): """Test media player discovery.""" device = ( 'media_player.test', @@ -436,7 +421,7 @@ def test_media_player(hass): 'volume_level': 0.75 } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'media_player#test' assert appliance['displayCategories'][0] == "TV" @@ -451,59 +436,59 @@ def test_media_player(hass): 'Alexa.PlaybackController', ) - yield from assert_power_controller_works( + await assert_power_controller_works( 'media_player#test', 'media_player.turn_on', 'media_player.turn_off', hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PlaybackController', 'Play', 'media_player#test', 'media_player.media_play', hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PlaybackController', 'Pause', 'media_player#test', 'media_player.media_pause', hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PlaybackController', 'Stop', 'media_player#test', 'media_player.media_stop', hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PlaybackController', 'Next', 'media_player#test', 'media_player.media_next_track', hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PlaybackController', 'Previous', 'media_player#test', 'media_player.media_previous_track', hass) - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.Speaker', 'SetVolume', 'media_player#test', 'media_player.volume_set', hass, payload={'volume': 50}) assert call.data['volume_level'] == 0.5 - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.Speaker', 'SetMute', 'media_player#test', 'media_player.volume_mute', hass, payload={'mute': True}) assert call.data['is_volume_muted'] - call, _, = yield from assert_request_calls_service( + call, _, = await assert_request_calls_service( 'Alexa.Speaker', 'SetMute', 'media_player#test', 'media_player.volume_mute', hass, payload={'mute': False}) assert not call.data['is_volume_muted'] - yield from assert_percentage_changes( + await assert_percentage_changes( hass, [(0.7, '-5'), (0.8, '5'), (0, '-80')], 'Alexa.Speaker', 'AdjustVolume', 'media_player#test', @@ -511,89 +496,85 @@ def test_media_player(hass): 'media_player.volume_set', 'volume_level') - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.StepSpeaker', 'SetMute', 'media_player#test', 'media_player.volume_mute', hass, payload={'mute': True}) assert call.data['is_volume_muted'] - call, _, = yield from assert_request_calls_service( + call, _, = await assert_request_calls_service( 'Alexa.StepSpeaker', 'SetMute', 'media_player#test', 'media_player.volume_mute', hass, payload={'mute': False}) assert not call.data['is_volume_muted'] - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test', 'media_player.volume_up', hass, payload={'volumeSteps': 20}) - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.StepSpeaker', 'AdjustVolume', 'media_player#test', 'media_player.volume_down', hass, payload={'volumeSteps': -20}) -@asyncio.coroutine -def test_alert(hass): +async def test_alert(hass): """Test alert discovery.""" device = ('alert.test', 'off', {'friendly_name': "Test alert"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'alert#test' assert appliance['displayCategories'][0] == "OTHER" assert appliance['friendlyName'] == "Test alert" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'alert#test', 'alert.turn_on', 'alert.turn_off', hass) -@asyncio.coroutine -def test_automation(hass): +async def test_automation(hass): """Test automation discovery.""" device = ('automation.test', 'off', {'friendly_name': "Test automation"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'automation#test' assert appliance['displayCategories'][0] == "OTHER" assert appliance['friendlyName'] == "Test automation" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'automation#test', 'automation.turn_on', 'automation.turn_off', hass) -@asyncio.coroutine -def test_group(hass): +async def test_group(hass): """Test group discovery.""" device = ('group.test', 'off', {'friendly_name': "Test group"}) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'group#test' assert appliance['displayCategories'][0] == "OTHER" assert appliance['friendlyName'] == "Test group" assert_endpoint_capabilities(appliance, 'Alexa.PowerController') - yield from assert_power_controller_works( + await assert_power_controller_works( 'group#test', 'homeassistant.turn_on', 'homeassistant.turn_off', hass) -@asyncio.coroutine -def test_cover(hass): +async def test_cover(hass): """Test cover discovery.""" device = ( 'cover.test', @@ -603,7 +584,7 @@ def test_cover(hass): 'position': 30, } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'cover#test' assert appliance['displayCategories'][0] == "DOOR" @@ -615,20 +596,20 @@ def test_cover(hass): 'Alexa.PowerController', ) - yield from assert_power_controller_works( + await assert_power_controller_works( 'cover#test', 'cover.open_cover', 'cover.close_cover', hass) - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.PercentageController', 'SetPercentage', 'cover#test', 'cover.set_cover_position', hass, payload={'percentage': '50'}) assert call.data['position'] == 50 - yield from assert_percentage_changes( + await assert_percentage_changes( hass, [(25, '-5'), (35, '5'), (0, '-80')], 'Alexa.PercentageController', 'AdjustPercentage', 'cover#test', @@ -637,8 +618,7 @@ def test_cover(hass): 'position') -@asyncio.coroutine -def assert_percentage_changes( +async def assert_percentage_changes( hass, adjustments, namespace, @@ -657,15 +637,14 @@ def assert_percentage_changes( else: payload = {} - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( namespace, name, endpoint, service, hass, payload=payload) assert call.data[changed_parameter] == result_volume -@asyncio.coroutine -def test_temp_sensor(hass): +async def test_temp_sensor(hass): """Test temperature sensor discovery.""" device = ( 'sensor.test_temp', @@ -675,7 +654,7 @@ def test_temp_sensor(hass): 'unit_of_measurement': TEMP_FAHRENHEIT, } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'sensor#test_temp' assert appliance['displayCategories'][0] == 'TEMPERATURE_SENSOR' @@ -689,13 +668,12 @@ def test_temp_sensor(hass): assert properties['retrievable'] is True assert {'name': 'temperature'} in properties['supported'] - properties = yield from reported_properties(hass, 'sensor#test_temp') + properties = await reported_properties(hass, 'sensor#test_temp') properties.assert_equal('Alexa.TemperatureSensor', 'temperature', {'value': 42.0, 'scale': 'FAHRENHEIT'}) -@asyncio.coroutine -def test_contact_sensor(hass): +async def test_contact_sensor(hass): """Test contact sensor discovery.""" device = ( 'binary_sensor.test_contact', @@ -705,7 +683,7 @@ def test_contact_sensor(hass): 'device_class': 'door', } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'binary_sensor#test_contact' assert appliance['displayCategories'][0] == 'CONTACT_SENSOR' @@ -719,14 +697,13 @@ def test_contact_sensor(hass): assert properties['retrievable'] is True assert {'name': 'detectionState'} in properties['supported'] - properties = yield from reported_properties(hass, - 'binary_sensor#test_contact') + properties = await reported_properties(hass, + 'binary_sensor#test_contact') properties.assert_equal('Alexa.ContactSensor', 'detectionState', 'DETECTED') -@asyncio.coroutine -def test_motion_sensor(hass): +async def test_motion_sensor(hass): """Test motion sensor discovery.""" device = ( 'binary_sensor.test_motion', @@ -736,7 +713,7 @@ def test_motion_sensor(hass): 'device_class': 'motion', } ) - appliance = yield from discovery_test(device, hass) + appliance = await discovery_test(device, hass) assert appliance['endpointId'] == 'binary_sensor#test_motion' assert appliance['displayCategories'][0] == 'MOTION_SENSOR' @@ -750,21 +727,20 @@ def test_motion_sensor(hass): assert properties['retrievable'] is True assert {'name': 'detectionState'} in properties['supported'] - properties = yield from reported_properties(hass, - 'binary_sensor#test_motion') + properties = await reported_properties(hass, + 'binary_sensor#test_motion') properties.assert_equal('Alexa.MotionSensor', 'detectionState', 'DETECTED') -@asyncio.coroutine -def test_unknown_sensor(hass): +async def test_unknown_sensor(hass): """Test sensors of unknown quantities are not discovered.""" device = ( 'sensor.test_sickness', '0.1', { 'friendly_name': "Test Space Sickness Sensor", 'unit_of_measurement': 'garn', }) - yield from discovery_test(device, hass, expected_endpoints=0) + await discovery_test(device, hass, expected_endpoints=0) async def test_thermostat(hass): @@ -911,8 +887,7 @@ async def test_thermostat(hass): assert call.data['operation_mode'] == 'off' -@asyncio.coroutine -def test_exclude_filters(hass): +async def test_exclude_filters(hass): """Test exclusion filters.""" request = get_new_request('Alexa.Discovery', 'Discover') @@ -933,16 +908,15 @@ def test_exclude_filters(hass): exclude_entities=['cover.deny'], )) - msg = yield from smart_home.async_handle_message(hass, config, request) - yield from hass.async_block_till_done() + msg = await smart_home.async_handle_message(hass, config, request) + await hass.async_block_till_done() msg = msg['event'] assert len(msg['payload']['endpoints']) == 1 -@asyncio.coroutine -def test_include_filters(hass): +async def test_include_filters(hass): """Test inclusion filters.""" request = get_new_request('Alexa.Discovery', 'Discover') @@ -966,24 +940,23 @@ def test_include_filters(hass): exclude_entities=[], )) - msg = yield from smart_home.async_handle_message(hass, config, request) - yield from hass.async_block_till_done() + msg = await smart_home.async_handle_message(hass, config, request) + await hass.async_block_till_done() msg = msg['event'] assert len(msg['payload']['endpoints']) == 3 -@asyncio.coroutine -def test_api_entity_not_exists(hass): +async def test_api_entity_not_exists(hass): """Test api turn on process without entity.""" request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#test') call_switch = async_mock_service(hass, 'switch', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -994,11 +967,10 @@ def test_api_entity_not_exists(hass): assert msg['payload']['type'] == 'NO_SUCH_ENDPOINT' -@asyncio.coroutine -def test_api_function_not_implemented(hass): +async def test_api_function_not_implemented(hass): """Test api call that is not implemented to us.""" request = get_new_request('Alexa.HAHAAH', 'Sweet') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) assert 'event' in msg @@ -1009,8 +981,7 @@ def test_api_function_not_implemented(hass): assert msg['payload']['type'] == 'INTERNAL_ERROR' -@asyncio.coroutine -def assert_request_fails( +async def assert_request_fails( namespace, name, endpoint, @@ -1025,9 +996,9 @@ def assert_request_fails( domain, service_name = service_not_called.split('.') call = async_mock_service(hass, domain, service_name) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert not call assert 'event' in msg @@ -1036,8 +1007,7 @@ def assert_request_fails( return msg -@asyncio.coroutine -def assert_request_calls_service( +async def assert_request_calls_service( namespace, name, endpoint, @@ -1054,9 +1024,9 @@ def assert_request_calls_service( domain, service_name = service.split('.') calls = async_mock_service(hass, domain, service_name) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request, context) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert len(calls) == 1 call = calls[0] @@ -1068,26 +1038,29 @@ def assert_request_calls_service( return call, msg -@asyncio.coroutine -def assert_power_controller_works(endpoint, on_service, off_service, hass): +async def assert_power_controller_works( + endpoint, + on_service, + off_service, + hass +): """Assert PowerController API requests work.""" - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PowerController', 'TurnOn', endpoint, on_service, hass) - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.PowerController', 'TurnOff', endpoint, off_service, hass) -@asyncio.coroutine -def assert_scene_controller_works( +async def assert_scene_controller_works( endpoint, activate_service, deactivate_service, hass): """Assert SceneController API requests work.""" - _, response = yield from assert_request_calls_service( + _, response = await assert_request_calls_service( 'Alexa.SceneController', 'Activate', endpoint, activate_service, hass, response_type='ActivationStarted') @@ -1095,7 +1068,7 @@ def assert_scene_controller_works( assert 'timestamp' in response['event']['payload'] if deactivate_service: - yield from assert_request_calls_service( + await assert_request_calls_service( 'Alexa.SceneController', 'Deactivate', endpoint, deactivate_service, hass, response_type='DeactivationStarted') @@ -1104,10 +1077,9 @@ def assert_scene_controller_works( assert 'timestamp' in response['event']['payload'] -@asyncio.coroutine @pytest.mark.parametrize( "result,adjust", [(25, '-5'), (35, '5'), (0, '-80')]) -def test_api_adjust_brightness(hass, result, adjust): +async def test_api_adjust_brightness(hass, result, adjust): """Test api adjust brightness process.""" request = get_new_request( 'Alexa.BrightnessController', 'AdjustBrightness', 'light#test') @@ -1123,9 +1095,9 @@ def test_api_adjust_brightness(hass, result, adjust): call_light = async_mock_service(hass, 'light', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -1136,8 +1108,7 @@ def test_api_adjust_brightness(hass, result, adjust): assert msg['header']['name'] == 'Response' -@asyncio.coroutine -def test_api_set_color_rgb(hass): +async def test_api_set_color_rgb(hass): """Test api set color process.""" request = get_new_request( 'Alexa.ColorController', 'SetColor', 'light#test') @@ -1158,9 +1129,9 @@ def test_api_set_color_rgb(hass): call_light = async_mock_service(hass, 'light', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -1171,8 +1142,7 @@ def test_api_set_color_rgb(hass): assert msg['header']['name'] == 'Response' -@asyncio.coroutine -def test_api_set_color_temperature(hass): +async def test_api_set_color_temperature(hass): """Test api set color temperature process.""" request = get_new_request( 'Alexa.ColorTemperatureController', 'SetColorTemperature', @@ -1187,9 +1157,9 @@ def test_api_set_color_temperature(hass): call_light = async_mock_service(hass, 'light', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -1200,9 +1170,8 @@ def test_api_set_color_temperature(hass): assert msg['header']['name'] == 'Response' -@asyncio.coroutine @pytest.mark.parametrize("result,initial", [(383, '333'), (500, '500')]) -def test_api_decrease_color_temp(hass, result, initial): +async def test_api_decrease_color_temp(hass, result, initial): """Test api decrease color temp process.""" request = get_new_request( 'Alexa.ColorTemperatureController', 'DecreaseColorTemperature', @@ -1217,9 +1186,9 @@ def test_api_decrease_color_temp(hass, result, initial): call_light = async_mock_service(hass, 'light', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -1230,9 +1199,8 @@ def test_api_decrease_color_temp(hass, result, initial): assert msg['header']['name'] == 'Response' -@asyncio.coroutine @pytest.mark.parametrize("result,initial", [(283, '333'), (142, '142')]) -def test_api_increase_color_temp(hass, result, initial): +async def test_api_increase_color_temp(hass, result, initial): """Test api increase color temp process.""" request = get_new_request( 'Alexa.ColorTemperatureController', 'IncreaseColorTemperature', @@ -1247,9 +1215,9 @@ def test_api_increase_color_temp(hass, result, initial): call_light = async_mock_service(hass, 'light', 'turn_on') - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() assert 'event' in msg msg = msg['event'] @@ -1260,8 +1228,7 @@ def test_api_increase_color_temp(hass, result, initial): assert msg['header']['name'] == 'Response' -@asyncio.coroutine -def test_report_lock_state(hass): +async def test_report_lock_state(hass): """Test LockController implements lockState property.""" hass.states.async_set( 'lock.locked', STATE_LOCKED, {}) @@ -1270,18 +1237,17 @@ def test_report_lock_state(hass): hass.states.async_set( 'lock.unknown', STATE_UNKNOWN, {}) - properties = yield from reported_properties(hass, 'lock.locked') + properties = await reported_properties(hass, 'lock.locked') properties.assert_equal('Alexa.LockController', 'lockState', 'LOCKED') - properties = yield from reported_properties(hass, 'lock.unlocked') + properties = await reported_properties(hass, 'lock.unlocked') properties.assert_equal('Alexa.LockController', 'lockState', 'UNLOCKED') - properties = yield from reported_properties(hass, 'lock.unknown') + properties = await reported_properties(hass, 'lock.unknown') properties.assert_equal('Alexa.LockController', 'lockState', 'JAMMED') -@asyncio.coroutine -def test_report_dimmable_light_state(hass): +async def test_report_dimmable_light_state(hass): """Test BrightnessController reports brightness correctly.""" hass.states.async_set( 'light.test_on', 'on', {'friendly_name': "Test light On", @@ -1290,24 +1256,23 @@ def test_report_dimmable_light_state(hass): 'light.test_off', 'off', {'friendly_name': "Test light Off", 'supported_features': 1}) - properties = yield from reported_properties(hass, 'light.test_on') + properties = await reported_properties(hass, 'light.test_on') properties.assert_equal('Alexa.BrightnessController', 'brightness', 50) - properties = yield from reported_properties(hass, 'light.test_off') + properties = await reported_properties(hass, 'light.test_off') properties.assert_equal('Alexa.BrightnessController', 'brightness', 0) -@asyncio.coroutine -def reported_properties(hass, endpoint): +async def reported_properties(hass, endpoint): """Use ReportState to get properties and return them. The result is a _ReportedProperties instance, which has methods to make assertions about the properties. """ request = get_new_request('Alexa', 'ReportState', endpoint) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) - yield from hass.async_block_till_done() + await hass.async_block_till_done() return _ReportedProperties(msg['context']['properties']) @@ -1329,8 +1294,7 @@ class _ReportedProperties: ) -@asyncio.coroutine -def test_entity_config(hass): +async def test_entity_config(hass): """Test that we can configure things via entity config.""" request = get_new_request('Alexa.Discovery', 'Discover') @@ -1348,7 +1312,7 @@ def test_entity_config(hass): } ) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, config, request) assert 'event' in msg @@ -1366,15 +1330,14 @@ def test_entity_config(hass): 'Alexa.PowerController' -@asyncio.coroutine -def test_unsupported_domain(hass): +async def test_unsupported_domain(hass): """Discovery ignores entities of unknown domains.""" request = get_new_request('Alexa.Discovery', 'Discover') hass.states.async_set( 'woz.boop', 'on', {'friendly_name': "Boop Woz"}) - msg = yield from smart_home.async_handle_message( + msg = await smart_home.async_handle_message( hass, DEFAULT_CONFIG, request) assert 'event' in msg @@ -1383,22 +1346,20 @@ def test_unsupported_domain(hass): assert not msg['payload']['endpoints'] -@asyncio.coroutine -def do_http_discovery(config, hass, aiohttp_client): +async def do_http_discovery(config, hass, aiohttp_client): """Submit a request to the Smart Home HTTP API.""" - yield from async_setup_component(hass, alexa.DOMAIN, config) - http_client = yield from aiohttp_client(hass.http.app) + await async_setup_component(hass, alexa.DOMAIN, config) + http_client = await aiohttp_client(hass.http.app) request = get_new_request('Alexa.Discovery', 'Discover') - response = yield from http_client.post( + response = await http_client.post( smart_home.SMART_HOME_HTTP_ENDPOINT, data=json.dumps(request), headers={'content-type': 'application/json'}) return response -@asyncio.coroutine -def test_http_api(hass, aiohttp_client): +async def test_http_api(hass, aiohttp_client): """With `smart_home:` HTTP API is exposed.""" config = { 'alexa': { @@ -1406,26 +1367,24 @@ def test_http_api(hass, aiohttp_client): } } - response = yield from do_http_discovery(config, hass, aiohttp_client) - response_data = yield from response.json() + response = await do_http_discovery(config, hass, aiohttp_client) + response_data = await response.json() # Here we're testing just the HTTP view glue -- details of discovery are # covered in other tests. assert response_data['event']['header']['name'] == 'Discover.Response' -@asyncio.coroutine -def test_http_api_disabled(hass, aiohttp_client): +async def test_http_api_disabled(hass, aiohttp_client): """Without `smart_home:`, the HTTP API is disabled.""" config = { 'alexa': {} } - response = yield from do_http_discovery(config, hass, aiohttp_client) + response = await do_http_discovery(config, hass, aiohttp_client) assert response.status == 404 -@asyncio.coroutine @pytest.mark.parametrize( "domain,payload,source_list,idx", [ ('media_player', 'GAME CONSOLE', ['tv', 'game console'], 1), @@ -1434,7 +1393,7 @@ def test_http_api_disabled(hass, aiohttp_client): ('media_player', 'BAD DEVICE', ['satellite_tv', 'game console'], None), ] ) -def test_api_select_input(hass, domain, payload, source_list, idx): +async def test_api_select_input(hass, domain, payload, source_list, idx): """Test api set input process.""" hass.states.async_set( 'media_player.test', 'off', { @@ -1445,14 +1404,14 @@ def test_api_select_input(hass, domain, payload, source_list, idx): # test where no source matches if idx is None: - yield from assert_request_fails( + await assert_request_fails( 'Alexa.InputController', 'SelectInput', 'media_player#test', 'media_player.select_source', hass, payload={'input': payload}) return - call, _ = yield from assert_request_calls_service( + call, _ = await assert_request_calls_service( 'Alexa.InputController', 'SelectInput', 'media_player#test', 'media_player.select_source', hass, From cb73a8bbb08db5d8ba1e93029be7fd10637940f0 Mon Sep 17 00:00:00 2001 From: Victor Cerutti Date: Mon, 29 Oct 2018 23:22:47 +0100 Subject: [PATCH 118/230] =?UTF-8?q?M=C3=A9t=C3=A9o-france=20sensor=20:=20c?= =?UTF-8?q?urrent=20weather=20and=201=20hour=20rain=20forecast=20(#17773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌧 Meteo France rain forecast * do not fail on wrong config * Update name of sensor class * do not show sensor if not working * Update .coveragerc * ability to submit insee location code without final 0 needed by meteo-france * Lynting * more lynting * update comment * block comment linting * reducing length of long lines * linting * Update météo-france platform Now work with pypi package and monitored conditions * remove error log * Update requirements_all.txt * Increase scan interval to 5 minutes * Update meteo_france according to review * better error handling of location missing some monitored conditions * fix lint error * moving error log Errors are now catched at sensor initialization and state is set when updating the data * Update updating of sensor --- .coveragerc | 1 + .../components/sensor/meteo_france.py | 139 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 143 insertions(+) create mode 100644 homeassistant/components/sensor/meteo_france.py diff --git a/.coveragerc b/.coveragerc index 599e155f8f3..dea02d21f56 100644 --- a/.coveragerc +++ b/.coveragerc @@ -721,6 +721,7 @@ omit = homeassistant/components/sensor/luftdaten.py homeassistant/components/sensor/lyft.py homeassistant/components/sensor/magicseaweed.py + homeassistant/components/sensor/meteo_france.py homeassistant/components/sensor/metoffice.py homeassistant/components/sensor/miflora.py homeassistant/components/sensor/mitemp_bt.py diff --git a/homeassistant/components/sensor/meteo_france.py b/homeassistant/components/sensor/meteo_france.py new file mode 100644 index 00000000000..1e18b1518a7 --- /dev/null +++ b/homeassistant/components/sensor/meteo_france.py @@ -0,0 +1,139 @@ +""" +Support for Meteo France raining forecast. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.meteo_france/ +""" + +import logging +import datetime + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION, TEMP_CELSIUS) +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['meteofrance==0.2.7'] +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = "Data provided by Meteo-France" +CONF_POSTAL_CODE = 'postal_code' + +STATE_ATTR_FORECAST = '1h rain forecast' + +SCAN_INTERVAL = datetime.timedelta(minutes=5) + +SENSOR_TYPES = { + 'rain_chance': ['Rain chance', '%'], + 'freeze_chance': ['Freeze chance', '%'], + 'thunder_chance': ['Thunder chance', '%'], + 'snow_chance': ['Snow chance', '%'], + 'weather': ['Weather', None], + 'wind_speed': ['Wind Speed', 'km/h'], + 'next_rain': ['Next rain', 'min'], + 'temperature': ['Temperature', TEMP_CELSIUS], + 'uv': ['UV', None], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_POSTAL_CODE): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Meteo-France sensor.""" + postal_code = config[CONF_POSTAL_CODE] + + from meteofrance.client import meteofranceClient, meteofranceError + + try: + meteofrance_client = meteofranceClient(postal_code) + except meteofranceError as exp: + _LOGGER.error(exp) + return + + client = MeteoFranceUpdater(meteofrance_client) + + add_entities([MeteoFranceSensor(variable, client) + for variable in config[CONF_MONITORED_CONDITIONS]], + True) + + +class MeteoFranceSensor(Entity): + """Representation of a Sensor.""" + + def __init__(self, condition, client): + """Initialize the sensor.""" + self._condition = condition + self._client = client + self._state = None + self._data = {} + + @property + def name(self): + """Return the name of the sensor.""" + return "{} {}".format(self._data["name"], + SENSOR_TYPES[self._condition][0]) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + if self._condition == 'next_rain' and "rain_forecast" in self._data: + return { + **{ + STATE_ATTR_FORECAST: self._data["rain_forecast"], + }, + ** self._data["next_rain_intervals"], + **{ + ATTR_ATTRIBUTION: CONF_ATTRIBUTION + } + } + return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION} + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return SENSOR_TYPES[self._condition][1] + + def update(self): + """Fetch new state data for the sensor.""" + try: + self._client.update() + self._data = self._client.get_data() + self._state = self._data[self._condition] + except KeyError: + _LOGGER.error("No condition `%s` for location `%s`", + self._condition, self._data["name"]) + self._state = None + + +class MeteoFranceUpdater: + """Update data from Meteo-France.""" + + def __init__(self, client): + """Initialize the data object.""" + self._client = client + + def get_data(self): + """Get the latest data from Meteo-France.""" + return self._client.get_data() + + @Throttle(SCAN_INTERVAL) + def update(self): + """Get the latest data from Meteo-France.""" + from meteofrance.client import meteofranceError + try: + self._client.update() + except meteofranceError as exp: + _LOGGER.error(exp) diff --git a/requirements_all.txt b/requirements_all.txt index 99caed7b23c..1874b8112cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -604,6 +604,9 @@ maxcube-api==0.1.0 # homeassistant.components.notify.message_bird messagebird==1.2.0 +# homeassistant.components.sensor.meteo_france +meteofrance==0.2.7 + # homeassistant.components.sensor.mfi # homeassistant.components.switch.mfi mficlient==0.3.0 From 03dd1e6870d2a073ce1034e619abfbdbad831fcf Mon Sep 17 00:00:00 2001 From: Evan Bruhn Date: Tue, 30 Oct 2018 09:27:12 +1100 Subject: [PATCH 119/230] Updated ring_doorbell dependency to 0.2.2 (#17945) - Resolves an issue with the sensor platform for Ring Spotlight Cam devices --- homeassistant/components/ring.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ring.py b/homeassistant/components/ring.py index 3bfa1372fab..2e048caa52f 100644 --- a/homeassistant/components/ring.py +++ b/homeassistant/components/ring.py @@ -12,7 +12,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_USERNAME, CONF_PASSWORD -REQUIREMENTS = ['ring_doorbell==0.2.1'] +REQUIREMENTS = ['ring_doorbell==0.2.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 1874b8112cc..a0d0e9fea1c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1299,7 +1299,7 @@ restrictedpython==4.0b5 rflink==0.0.37 # homeassistant.components.ring -ring_doorbell==0.2.1 +ring_doorbell==0.2.2 # homeassistant.components.device_tracker.ritassist ritassist==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 512dfbde7de..affbbfdf322 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -212,7 +212,7 @@ restrictedpython==4.0b5 rflink==0.0.37 # homeassistant.components.ring -ring_doorbell==0.2.1 +ring_doorbell==0.2.2 # homeassistant.components.lovelace ruamel.yaml==0.15.72 From 32cb666dac5f45f11aaf239a538e66b4c42ef969 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Mon, 29 Oct 2018 18:29:27 -0400 Subject: [PATCH 120/230] Update Avi-On to work with latest API (#17780) * Update Avi-On to work with the API. * Use voluptuous defaults instead of .get() * Bump library version. * Remove unnecessary voluptuous defaults. Fix manually-configured devices. API-discovered devices are already Avion objects, but manually-configured devices need to be instantiated as Avion objects first. * Use .get() where appropriate. * Remove default --- homeassistant/components/light/avion.py | 54 ++++++++++--------------- requirements_all.txt | 6 +-- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/light/avion.py b/homeassistant/components/light/avion.py index d6e6776ea41..731f0e600fb 100644 --- a/homeassistant/components/light/avion.py +++ b/homeassistant/components/light/avion.py @@ -9,23 +9,23 @@ import time import voluptuous as vol -from homeassistant.const import CONF_API_KEY, CONF_DEVICES, CONF_NAME, \ - CONF_USERNAME, CONF_PASSWORD - from homeassistant.components.light import ( - ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light, - PLATFORM_SCHEMA) + ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, Light) +from homeassistant.const import ( + CONF_API_KEY, CONF_DEVICES, CONF_ID, CONF_NAME, CONF_PASSWORD, + CONF_USERNAME) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['avion==0.7'] +REQUIREMENTS = ['antsar-avion==0.9.1'] _LOGGER = logging.getLogger(__name__) -SUPPORT_AVION_LED = (SUPPORT_BRIGHTNESS) +SUPPORT_AVION_LED = SUPPORT_BRIGHTNESS DEVICE_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -42,24 +42,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): lights = [] if CONF_USERNAME in config and CONF_PASSWORD in config: - data = avion.avion_info(config[CONF_USERNAME], config[CONF_PASSWORD]) - for location in data['locations']: - for avion_device in location['location']['devices']: - device = {} - mac = avion_device['device']['mac_address'] - device['name'] = avion_device['device']['name'] - device['key'] = location['location']['passphrase'] - device['address'] = '%s%s:%s%s:%s%s:%s%s:%s%s:%s%s' % \ - (mac[8], mac[9], mac[10], mac[11], mac[4], - mac[5], mac[6], mac[7], mac[0], mac[1], - mac[2], mac[3]) - lights.append(AvionLight(device)) + devices = avion.get_devices( + config[CONF_USERNAME], config[CONF_PASSWORD]) + for device in devices: + lights.append(AvionLight(device)) for address, device_config in config[CONF_DEVICES].items(): - device = {} - device['name'] = device_config[CONF_NAME] - device['key'] = device_config[CONF_API_KEY] - device['address'] = address + device = avion.Avion( + mac=address, + passphrase=device_config[CONF_API_KEY], + name=device_config.get(CONF_NAME), + object_id=device_config.get(CONF_ID), + connect=False) lights.append(AvionLight(device)) add_entities(lights) @@ -70,15 +64,11 @@ class AvionLight(Light): def __init__(self, device): """Initialize the light.""" - # pylint: disable=no-member - import avion - - self._name = device['name'] - self._address = device['address'] - self._key = device['key'] + self._name = device.name + self._address = device.mac self._brightness = 255 self._state = False - self._switch = avion.avion(self._address, self._key) + self._switch = device @property def unique_id(self): @@ -129,7 +119,7 @@ class AvionLight(Light): try: self._switch.set_brightness(brightness) break - except avion.avionException: + except avion.AvionException: self._switch.connect() return True diff --git a/requirements_all.txt b/requirements_all.txt index a0d0e9fea1c..7e3f42749d1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -142,6 +142,9 @@ anel_pwrctrl-homeassistant==0.0.1.dev2 # homeassistant.components.media_player.anthemav anthemav==1.1.8 +# homeassistant.components.light.avion +# antsar-avion==0.9.1 + # homeassistant.components.apcupsd apcaccess==0.0.13 @@ -158,9 +161,6 @@ asterisk_mbox==0.5.0 # homeassistant.components.media_player.dlna_dmr async-upnp-client==0.13.0 -# homeassistant.components.light.avion -# avion==0.7 - # homeassistant.components.axis axis==16 From 98163504fbc0ea14e580460904b767a1f5bafacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Mon, 29 Oct 2018 23:36:49 +0100 Subject: [PATCH 121/230] Mill service (#17971) * Mill service * style --- homeassistant/components/climate/mill.py | 40 ++++++++++++++++--- .../components/climate/services.yaml | 16 ++++++++ requirements_all.txt | 2 +- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/climate/mill.py index 8f058a3f05e..57d14126a93 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -10,7 +10,7 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, + ClimateDevice, DOMAIN, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_ON_OFF) from homeassistant.const import ( @@ -19,12 +19,18 @@ from homeassistant.const import ( from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -REQUIREMENTS = ['millheater==0.2.1'] +REQUIREMENTS = ['millheater==0.2.2'] _LOGGER = logging.getLogger(__name__) +ATTR_AWAY_TEMP = 'away_temp' +ATTR_COMFORT_TEMP = 'comfort_temp' +ATTR_ROOM_NAME = 'room_name' +ATTR_SLEEP_TEMP = 'sleep_temp' MAX_TEMP = 35 MIN_TEMP = 5 +SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature' + SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_ON_OFF) @@ -33,6 +39,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PASSWORD): cv.string, }) +SET_ROOM_TEMP_SCHEMA = vol.Schema({ + vol.Required(ATTR_ROOM_NAME): cv.string, + vol.Optional(ATTR_AWAY_TEMP): cv.positive_int, + vol.Optional(ATTR_COMFORT_TEMP): cv.positive_int, + vol.Optional(ATTR_SLEEP_TEMP): cv.positive_int, +}) + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -52,6 +65,20 @@ async def async_setup_platform(hass, config, async_add_entities, dev.append(MillHeater(heater, mill_data_connection)) async_add_entities(dev) + async def set_room_temp(service): + """Set room temp.""" + room_name = service.data.get(ATTR_ROOM_NAME) + sleep_temp = service.data.get(ATTR_SLEEP_TEMP) + comfort_temp = service.data.get(ATTR_COMFORT_TEMP) + away_temp = service.data.get(ATTR_AWAY_TEMP) + await mill_data_connection.set_room_temperatures_by_name(room_name, + sleep_temp, + comfort_temp, + away_temp) + + hass.services.async_register(DOMAIN, SERVICE_SET_ROOM_TEMP, + set_room_temp, schema=SET_ROOM_TEMP_SCHEMA) + class MillHeater(ClimateDevice): """Representation of a Mill Thermostat device.""" @@ -88,9 +115,12 @@ class MillHeater(ClimateDevice): room = self._heater.room.name else: room = "Independent device" - return {"room": room, - "open_window": self._heater.open_window, - "heating": self._heater.is_heating} + return { + "room": room, + "open_window": self._heater.open_window, + "heating": self._heater.is_heating, + "controlled_by_tibber": self._heater.tibber_control, + } @property def temperature_unit(self): diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index e2a42770cb2..1460181ddc2 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -116,6 +116,22 @@ ecobee_resume_program: description: Resume all events and return to the scheduled program. This default to false which removes only the top event. example: true +mill_set_room_temperature: + description: Set Mill room temperatures. + fields: + room_name: + description: Name of room to change. + example: 'kitchen' + away_temp: + description: Away temp. + example: 12 + comfort_temp: + description: Comfort temp. + example: 22 + sleep_temp: + description: Sleep temp. + example: 17 + nuheat_resume_program: description: Resume the programmed schedule. fields: diff --git a/requirements_all.txt b/requirements_all.txt index 7e3f42749d1..0865c388f4d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -615,7 +615,7 @@ mficlient==0.3.0 miflora==0.4.0 # homeassistant.components.climate.mill -millheater==0.2.1 +millheater==0.2.2 # homeassistant.components.sensor.mitemp_bt mitemp_bt==0.0.1 From e16793013a65a42503a922866c828dc9bc64c2bb Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Mon, 29 Oct 2018 22:16:35 -0400 Subject: [PATCH 122/230] Refactor Alexa API, fix thermostats (#17969) * Refactor Alexa API to use objects for requests This introduces _AlexaDirective to stand in for the previous model of passing basic dict and list data structures to and from handlers. This gives a more expressive platform for functionality common to most or all handlers. I had two use cases in mind: 1) Most responses should include current properties. In the case of locks and thermostats, the response must include the properties or Alexa will give the user a vague error like "Hmm, $device is not responding." Locks currently work, but thermostats do not. I wanted a way to automatically include properties in all responses. This is implemented in a subsequent commit. 2) The previous model had a 1:1 mapping between Alexa endpoints and Home Assistant entities. This works most of the time, but sometimes it's not so great. For example, my Z-wave thermostat shows as three devices in Alexa: one for the temperature sensor, one for the heat, and one for the AC. I'd like to merge these into one device from Alexa's perspective. I believe this will be facilitated with the `endpoint` attribute on `_AlexaDirective`. * Include properties in all Alexa responses The added _AlexaResponse class provides a richer vocabulary for handlers. Among that vocabulary is .merge_context_properties(), which is invoked automatically for any request directed at an endpoint. This adds all supported properties to the response as recommended by the Alexa API docs, and in some cases (locks, thermostats at least) the user will get an error "Hmm, $device is not responding" if properties are not provided in the response. * Fix setting temperature with Alexa thermostats Fixes https://github.com/home-assistant/home-assistant/issues/16577 --- homeassistant/components/alexa/smart_home.py | 803 ++++++++++++------- homeassistant/components/cloud/iot.py | 6 +- tests/components/alexa/test_smart_home.py | 69 +- 3 files changed, 572 insertions(+), 306 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index c7fedc34e03..475f507439c 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1,28 +1,33 @@ -"""Support for alexa Smart Home Skill API.""" +"""Support for alexa Smart Home Skill API. + +API documentation: +https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.html +https://developer.amazon.com/docs/device-apis/message-guide.html +""" + from collections import OrderedDict +from datetime import datetime import logging import math -from datetime import datetime from uuid import uuid4 from homeassistant.components import ( - alert, automation, binary_sensor, cover, climate, fan, group, - input_boolean, light, lock, media_player, scene, script, switch, http, - sensor) -import homeassistant.core as ha -import homeassistant.util.color as color_util -from homeassistant.util.temperature import convert as convert_temperature -from homeassistant.util.decorator import Registry + alert, automation, binary_sensor, climate, cover, fan, group, http, + input_boolean, light, lock, media_player, scene, script, sensor, switch) from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS, - STATE_LOCKED, STATE_UNLOCKED, STATE_ON) + SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, STATE_UNLOCKED, + TEMP_CELSIUS, TEMP_FAHRENHEIT) +import homeassistant.core as ha +import homeassistant.util.color as color_util +from homeassistant.util.decorator import Registry +from homeassistant.util.temperature import convert as convert_temperature -from .const import CONF_FILTER, CONF_ENTITY_CONFIG +from .const import CONF_ENTITY_CONFIG, CONF_FILTER _LOGGER = logging.getLogger(__name__) @@ -166,6 +171,70 @@ class _UnsupportedProperty(Exception): """This entity does not support the requested Smart Home API property.""" +class _AlexaError(Exception): + """Base class for errors that can be serialized by the Alexa API. + + A handler can raise subclasses of this to return an error to the request. + """ + + namespace = None + error_type = None + + def __init__(self, error_message, payload=None): + Exception.__init__(self) + self.error_message = error_message + self.payload = None + + +class _AlexaInvalidEndpointError(_AlexaError): + """The endpoint in the request does not exist.""" + + namespace = 'Alexa' + error_type = 'NO_SUCH_ENDPOINT' + + def __init__(self, endpoint_id): + msg = 'The endpoint {} does not exist'.format(endpoint_id) + _AlexaError.__init__(self, msg) + self.endpoint_id = endpoint_id + + +class _AlexaInvalidValueError(_AlexaError): + namespace = 'Alexa' + error_type = 'INVALID_VALUE' + + +class _AlexaUnsupportedThermostatModeError(_AlexaError): + namespace = 'Alexa.ThermostatController' + error_type = 'UNSUPPORTED_THERMOSTAT_MODE' + + +class _AlexaTempRangeError(_AlexaError): + namespace = 'Alexa' + error_type = 'TEMPERATURE_VALUE_OUT_OF_RANGE' + + def __init__(self, hass, temp, min_temp, max_temp): + unit = hass.config.units.temperature_unit + temp_range = { + 'minimumValue': { + 'value': min_temp, + 'scale': API_TEMP_UNITS[unit], + }, + 'maximumValue': { + 'value': max_temp, + 'scale': API_TEMP_UNITS[unit], + }, + } + payload = {'validRange': temp_range} + msg = 'The requested temperature {} is out of range'.format(temp) + + _AlexaError.__init__(self, msg, payload) + + +class _AlexaBridgeUnreachableError(_AlexaError): + namespace = 'Alexa' + error_type = 'BRIDGE_UNREACHABLE' + + class _AlexaEntity: """An adaptation of an entity, expressed in Alexa's terms. @@ -221,8 +290,23 @@ class _AlexaEntity: """ raise NotImplementedError + def serialize_properties(self): + """Yield each supported property in API format.""" + for interface in self.interfaces(): + for prop in interface.serialize_properties(): + yield prop + class _AlexaInterface: + """Base class for Alexa capability interfaces. + + The Smart Home Skills API defines a number of "capability interfaces", + roughly analogous to domains in Home Assistant. The supported interfaces + describe what actions can be performed on a particular device. + + https://developer.amazon.com/docs/device-apis/message-guide.html + """ + def __init__(self, entity): self.entity = entity @@ -295,6 +379,11 @@ class _AlexaInterface: class _AlexaPowerController(_AlexaInterface): + """Implements Alexa.PowerController. + + https://developer.amazon.com/docs/device-apis/alexa-powercontroller.html + """ + def name(self): return 'Alexa.PowerController' @@ -314,6 +403,11 @@ class _AlexaPowerController(_AlexaInterface): class _AlexaLockController(_AlexaInterface): + """Implements Alexa.LockController. + + https://developer.amazon.com/docs/device-apis/alexa-lockcontroller.html + """ + def name(self): return 'Alexa.LockController' @@ -335,6 +429,11 @@ class _AlexaLockController(_AlexaInterface): class _AlexaSceneController(_AlexaInterface): + """Implements Alexa.SceneController. + + https://developer.amazon.com/docs/device-apis/alexa-scenecontroller.html + """ + def __init__(self, entity, supports_deactivation): _AlexaInterface.__init__(self, entity) self.supports_deactivation = lambda: supports_deactivation @@ -344,6 +443,11 @@ class _AlexaSceneController(_AlexaInterface): class _AlexaBrightnessController(_AlexaInterface): + """Implements Alexa.BrightnessController. + + https://developer.amazon.com/docs/device-apis/alexa-brightnesscontroller.html + """ + def name(self): return 'Alexa.BrightnessController' @@ -362,41 +466,81 @@ class _AlexaBrightnessController(_AlexaInterface): class _AlexaColorController(_AlexaInterface): + """Implements Alexa.ColorController. + + https://developer.amazon.com/docs/device-apis/alexa-colorcontroller.html + """ + def name(self): return 'Alexa.ColorController' class _AlexaColorTemperatureController(_AlexaInterface): + """Implements Alexa.ColorTemperatureController. + + https://developer.amazon.com/docs/device-apis/alexa-colortemperaturecontroller.html + """ + def name(self): return 'Alexa.ColorTemperatureController' class _AlexaPercentageController(_AlexaInterface): + """Implements Alexa.PercentageController. + + https://developer.amazon.com/docs/device-apis/alexa-percentagecontroller.html + """ + def name(self): return 'Alexa.PercentageController' class _AlexaSpeaker(_AlexaInterface): + """Implements Alexa.Speaker. + + https://developer.amazon.com/docs/device-apis/alexa-speaker.html + """ + def name(self): return 'Alexa.Speaker' class _AlexaStepSpeaker(_AlexaInterface): + """Implements Alexa.StepSpeaker. + + https://developer.amazon.com/docs/device-apis/alexa-stepspeaker.html + """ + def name(self): return 'Alexa.StepSpeaker' class _AlexaPlaybackController(_AlexaInterface): + """Implements Alexa.PlaybackController. + + https://developer.amazon.com/docs/device-apis/alexa-playbackcontroller.html + """ + def name(self): return 'Alexa.PlaybackController' class _AlexaInputController(_AlexaInterface): + """Implements Alexa.InputController. + + https://developer.amazon.com/docs/device-apis/alexa-inputcontroller.html + """ + def name(self): return 'Alexa.InputController' class _AlexaTemperatureSensor(_AlexaInterface): + """Implements Alexa.TemperatureSensor. + + https://developer.amazon.com/docs/device-apis/alexa-temperaturesensor.html + """ + def __init__(self, hass, entity): _AlexaInterface.__init__(self, entity) self.hass = hass @@ -427,6 +571,16 @@ class _AlexaTemperatureSensor(_AlexaInterface): class _AlexaContactSensor(_AlexaInterface): + """Implements Alexa.ContactSensor. + + The Alexa.ContactSensor interface describes the properties and events used + to report the state of an endpoint that detects contact between two + surfaces. For example, a contact sensor can report whether a door or window + is open. + + https://developer.amazon.com/docs/device-apis/alexa-contactsensor.html + """ + def __init__(self, hass, entity): _AlexaInterface.__init__(self, entity) self.hass = hass @@ -473,6 +627,11 @@ class _AlexaMotionSensor(_AlexaInterface): class _AlexaThermostatController(_AlexaInterface): + """Implements Alexa.ThermostatController. + + https://developer.amazon.com/docs/device-apis/alexa-thermostatcontroller.html + """ + def __init__(self, hass, entity): _AlexaInterface.__init__(self, entity) self.hass = hass @@ -803,111 +962,231 @@ class SmartHomeView(http.HomeAssistantView): return b'' if response is None else self.json(response) -async def async_handle_message(hass, config, request, context=None): - """Handle incoming API messages.""" +class _AlexaDirective: + def __init__(self, request): + self._directive = request[API_DIRECTIVE] + self.namespace = self._directive[API_HEADER]['namespace'] + self.name = self._directive[API_HEADER]['name'] + self.payload = self._directive[API_PAYLOAD] + self.has_endpoint = API_ENDPOINT in self._directive + + self.entity = self.entity_id = self.endpoint = None + + def load_entity(self, hass, config): + """Set attributes related to the entity for this request. + + Sets these attributes when self.has_endpoint is True: + + - entity + - entity_id + - endpoint + + Behavior when self.has_endpoint is False is undefined. + + Will raise _AlexaInvalidEndpointError if the endpoint in the request is + malformed or nonexistant. + """ + _endpoint_id = self._directive[API_ENDPOINT]['endpointId'] + self.entity_id = _endpoint_id.replace('#', '.') + + self.entity = hass.states.get(self.entity_id) + if not self.entity: + raise _AlexaInvalidEndpointError(_endpoint_id) + + self.endpoint = ENTITY_ADAPTERS[self.entity.domain]( + hass, config, self.entity) + + def response(self, + name='Response', + namespace='Alexa', + payload=None): + """Create an API formatted response. + + Async friendly. + """ + response = _AlexaResponse(name, namespace, payload) + + token = self._directive[API_HEADER].get('correlationToken') + if token: + response.set_correlation_token(token) + + if self.has_endpoint: + response.set_endpoint(self._directive[API_ENDPOINT].copy()) + + return response + + def error( + self, + namespace='Alexa', + error_type='INTERNAL_ERROR', + error_message="", + payload=None + ): + """Create a API formatted error response. + + Async friendly. + """ + payload = payload or {} + payload['type'] = error_type + payload['message'] = error_message + + _LOGGER.info("Request %s/%s error %s: %s", + self._directive[API_HEADER]['namespace'], + self._directive[API_HEADER]['name'], + error_type, error_message) + + return self.response( + name='ErrorResponse', + namespace=namespace, + payload=payload + ) + + +class _AlexaResponse: + def __init__(self, name, namespace, payload=None): + payload = payload or {} + self._response = { + API_EVENT: { + API_HEADER: { + 'namespace': namespace, + 'name': name, + 'messageId': str(uuid4()), + 'payloadVersion': '3', + }, + API_PAYLOAD: payload, + } + } + + @property + def name(self): + """Return the name of this response.""" + return self._response[API_EVENT][API_HEADER]['name'] + + @property + def namespace(self): + """Return the namespace of this response.""" + return self._response[API_EVENT][API_HEADER]['namespace'] + + def set_correlation_token(self, token): + """Set the correlationToken. + + This should normally mirror the value from a request, and is set by + _AlexaDirective.response() usually. + """ + self._response[API_EVENT][API_HEADER]['correlationToken'] = token + + def set_endpoint(self, endpoint): + """Set the endpoint. + + This should normally mirror the value from a request, and is set by + _AlexaDirective.response() usually. + """ + self._response[API_EVENT][API_ENDPOINT] = endpoint + + def _properties(self): + context = self._response.setdefault(API_CONTEXT, {}) + return context.setdefault('properties', []) + + def add_context_property(self, prop): + """Add a property to the response context. + + The Alexa response includes a list of properties which provides + feedback on how states have changed. For example if a user asks, + "Alexa, set theromstat to 20 degrees", the API expects a response with + the new value of the property, and Alexa will respond to the user + "Thermostat set to 20 degrees". + + async_handle_message() will call .merge_context_properties() for every + request automatically, however often handlers will call services to + change state but the effects of those changes are applied + asynchronously. Thus, handlers should call this method to confirm + changes before returning. + """ + self._properties().append(prop) + + def merge_context_properties(self, endpoint): + """Add all properties from given endpoint if not already set. + + Handlers should be using .add_context_property(). + """ + properties = self._properties() + already_set = {(p['namespace'], p['name']) for p in properties} + + for prop in endpoint.serialize_properties(): + if (prop['namespace'], prop['name']) not in already_set: + self.add_context_property(prop) + + def serialize(self): + """Return response as a JSON-able data structure.""" + return self._response + + +async def async_handle_message( + hass, + config, + request, + context=None, + enabled=True, +): + """Handle incoming API messages. + + If enabled is False, the response to all messagess will be a + BRIDGE_UNREACHABLE error. This can be used if the API has been disabled in + configuration. + """ assert request[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3' if context is None: context = ha.Context() - # Read head data - request = request[API_DIRECTIVE] - namespace = request[API_HEADER]['namespace'] - name = request[API_HEADER]['name'] + directive = _AlexaDirective(request) - # Do we support this API request? - funct_ref = HANDLERS.get((namespace, name)) - if funct_ref: - response = await funct_ref(hass, config, request, context) - else: - _LOGGER.warning( - "Unsupported API request %s/%s", namespace, name) - response = api_error(request) + try: + if not enabled: + raise _AlexaBridgeUnreachableError( + 'Alexa API not enabled in Home Assistant configuration') + + if directive.has_endpoint: + directive.load_entity(hass, config) + + funct_ref = HANDLERS.get((directive.namespace, directive.name)) + if funct_ref: + response = await funct_ref(hass, config, directive, context) + if directive.has_endpoint: + response.merge_context_properties(directive.endpoint) + else: + _LOGGER.warning( + "Unsupported API request %s/%s", + directive.namespace, + directive.name, + ) + response = directive.error() + except _AlexaError as err: + response = directive.error( + error_type=err.error_type, + error_message=err.error_message) request_info = { - 'namespace': namespace, - 'name': name, + 'namespace': directive.namespace, + 'name': directive.name, } - if API_ENDPOINT in request and 'endpointId' in request[API_ENDPOINT]: - request_info['entity_id'] = \ - request[API_ENDPOINT]['endpointId'].replace('#', '.') - - response_header = response[API_EVENT][API_HEADER] + if directive.has_endpoint: + request_info['entity_id'] = directive.entity_id hass.bus.async_fire(EVENT_ALEXA_SMART_HOME, { 'request': request_info, 'response': { - 'namespace': response_header['namespace'], - 'name': response_header['name'], + 'namespace': response.namespace, + 'name': response.name, } }, context=context) - return response - - -def api_message(request, - name='Response', - namespace='Alexa', - payload=None, - context=None): - """Create a API formatted response message. - - Async friendly. - """ - payload = payload or {} - - response = { - API_EVENT: { - API_HEADER: { - 'namespace': namespace, - 'name': name, - 'messageId': str(uuid4()), - 'payloadVersion': '3', - }, - API_PAYLOAD: payload, - } - } - - # If a correlation token exists, add it to header / Need by Async requests - token = request[API_HEADER].get('correlationToken') - if token: - response[API_EVENT][API_HEADER]['correlationToken'] = token - - # Extend event with endpoint object / Need by Async requests - if API_ENDPOINT in request: - response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy() - - if context is not None: - response[API_CONTEXT] = context - - return response - - -def api_error(request, - namespace='Alexa', - error_type='INTERNAL_ERROR', - error_message="", - payload=None): - """Create a API formatted error response. - - Async friendly. - """ - payload = payload or {} - payload['type'] = error_type - payload['message'] = error_message - - _LOGGER.info("Request %s/%s error %s: %s", - request[API_HEADER]['namespace'], - request[API_HEADER]['name'], - error_type, error_message) - - return api_message( - request, name='ErrorResponse', namespace=namespace, payload=payload) + return response.serialize() @HANDLERS.register(('Alexa.Discovery', 'Discover')) -async def async_api_discovery(hass, config, request, context): +async def async_api_discovery(hass, config, directive, context): """Create a API formatted discovery response. Async friendly. @@ -942,52 +1221,36 @@ async def async_api_discovery(hass, config, request, context): continue discovery_endpoints.append(endpoint) - return api_message( - request, name='Discover.Response', namespace='Alexa.Discovery', - payload={'endpoints': discovery_endpoints}) - - -def extract_entity(funct): - """Decorate for extract entity object from request.""" - async def async_api_entity_wrapper(hass, config, request, context): - """Process a turn on request.""" - entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.') - - # extract state object - entity = hass.states.get(entity_id) - if not entity: - _LOGGER.error("Can't process %s for %s", - request[API_HEADER]['name'], entity_id) - return api_error(request, error_type='NO_SUCH_ENDPOINT') - - return await funct(hass, config, request, context, entity) - - return async_api_entity_wrapper + return directive.response( + name='Discover.Response', + namespace='Alexa.Discovery', + payload={'endpoints': discovery_endpoints}, + ) @HANDLERS.register(('Alexa.PowerController', 'TurnOn')) -@extract_entity -async def async_api_turn_on(hass, config, request, context, entity): +async def async_api_turn_on(hass, config, directive, context): """Process a turn on request.""" + entity = directive.entity domain = entity.domain - if entity.domain == group.DOMAIN: + if domain == group.DOMAIN: domain = ha.DOMAIN service = SERVICE_TURN_ON - if entity.domain == cover.DOMAIN: + if domain == cover.DOMAIN: service = cover.SERVICE_OPEN_COVER await hass.services.async_call(domain, service, { ATTR_ENTITY_ID: entity.entity_id }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PowerController', 'TurnOff')) -@extract_entity -async def async_api_turn_off(hass, config, request, context, entity): +async def async_api_turn_off(hass, config, directive, context): """Process a turn off request.""" + entity = directive.entity domain = entity.domain if entity.domain == group.DOMAIN: domain = ha.DOMAIN @@ -1000,28 +1263,28 @@ async def async_api_turn_off(hass, config, request, context, entity): ATTR_ENTITY_ID: entity.entity_id }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness')) -@extract_entity -async def async_api_set_brightness(hass, config, request, context, entity): +async def async_api_set_brightness(hass, config, directive, context): """Process a set brightness request.""" - brightness = int(request[API_PAYLOAD]['brightness']) + entity = directive.entity + brightness = int(directive.payload['brightness']) await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness')) -@extract_entity -async def async_api_adjust_brightness(hass, config, request, context, entity): +async def async_api_adjust_brightness(hass, config, directive, context): """Process an adjust brightness request.""" - brightness_delta = int(request[API_PAYLOAD]['brightnessDelta']) + entity = directive.entity + brightness_delta = int(directive.payload['brightnessDelta']) # read current state try: @@ -1037,17 +1300,17 @@ async def async_api_adjust_brightness(hass, config, request, context, entity): light.ATTR_BRIGHTNESS_PCT: brightness, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.ColorController', 'SetColor')) -@extract_entity -async def async_api_set_color(hass, config, request, context, entity): +async def async_api_set_color(hass, config, directive, context): """Process a set color request.""" + entity = directive.entity rgb = color_util.color_hsb_to_RGB( - float(request[API_PAYLOAD]['color']['hue']), - float(request[API_PAYLOAD]['color']['saturation']), - float(request[API_PAYLOAD]['color']['brightness']) + float(directive.payload['color']['hue']), + float(directive.payload['color']['saturation']), + float(directive.payload['color']['brightness']) ) await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { @@ -1055,30 +1318,28 @@ async def async_api_set_color(hass, config, request, context, entity): light.ATTR_RGB_COLOR: rgb, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature')) -@extract_entity -async def async_api_set_color_temperature(hass, config, request, context, - entity): +async def async_api_set_color_temperature(hass, config, directive, context): """Process a set color temperature request.""" - kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin']) + entity = directive.entity + kelvin = int(directive.payload['colorTemperatureInKelvin']) await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { ATTR_ENTITY_ID: entity.entity_id, light.ATTR_KELVIN: kelvin, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register( ('Alexa.ColorTemperatureController', 'DecreaseColorTemperature')) -@extract_entity -async def async_api_decrease_color_temp(hass, config, request, context, - entity): +async def async_api_decrease_color_temp(hass, config, directive, context): """Process a decrease color temperature request.""" + entity = directive.entity current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS)) @@ -1088,15 +1349,14 @@ async def async_api_decrease_color_temp(hass, config, request, context, light.ATTR_COLOR_TEMP: value, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register( ('Alexa.ColorTemperatureController', 'IncreaseColorTemperature')) -@extract_entity -async def async_api_increase_color_temp(hass, config, request, context, - entity): +async def async_api_increase_color_temp(hass, config, directive, context): """Process an increase color temperature request.""" + entity = directive.entity current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS)) @@ -1106,13 +1366,13 @@ async def async_api_increase_color_temp(hass, config, request, context, light.ATTR_COLOR_TEMP: value, }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.SceneController', 'Activate')) -@extract_entity -async def async_api_activate(hass, config, request, context, entity): +async def async_api_activate(hass, config, directive, context): """Process an activate request.""" + entity = directive.entity domain = entity.domain await hass.services.async_call(domain, SERVICE_TURN_ON, { @@ -1124,8 +1384,7 @@ async def async_api_activate(hass, config, request, context, entity): 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) } - return api_message( - request, + return directive.response( name='ActivationStarted', namespace='Alexa.SceneController', payload=payload, @@ -1133,9 +1392,9 @@ async def async_api_activate(hass, config, request, context, entity): @HANDLERS.register(('Alexa.SceneController', 'Deactivate')) -@extract_entity -async def async_api_deactivate(hass, config, request, context, entity): +async def async_api_deactivate(hass, config, directive, context): """Process a deactivate request.""" + entity = directive.entity domain = entity.domain await hass.services.async_call(domain, SERVICE_TURN_OFF, { @@ -1147,8 +1406,7 @@ async def async_api_deactivate(hass, config, request, context, entity): 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) } - return api_message( - request, + return directive.response( name='DeactivationStarted', namespace='Alexa.SceneController', payload=payload, @@ -1156,10 +1414,10 @@ async def async_api_deactivate(hass, config, request, context, entity): @HANDLERS.register(('Alexa.PercentageController', 'SetPercentage')) -@extract_entity -async def async_api_set_percentage(hass, config, request, context, entity): +async def async_api_set_percentage(hass, config, directive, context): """Process a set percentage request.""" - percentage = int(request[API_PAYLOAD]['percentage']) + entity = directive.entity + percentage = int(directive.payload['percentage']) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -1182,14 +1440,14 @@ async def async_api_set_percentage(hass, config, request, context, entity): await hass.services.async_call( entity.domain, service, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage')) -@extract_entity -async def async_api_adjust_percentage(hass, config, request, context, entity): +async def async_api_adjust_percentage(hass, config, directive, context): """Process an adjust percentage request.""" - percentage_delta = int(request[API_PAYLOAD]['percentageDelta']) + entity = directive.entity + percentage_delta = int(directive.payload['percentageDelta']) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -1229,46 +1487,43 @@ async def async_api_adjust_percentage(hass, config, request, context, entity): await hass.services.async_call( entity.domain, service, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.LockController', 'Lock')) -@extract_entity -async def async_api_lock(hass, config, request, context, entity): +async def async_api_lock(hass, config, directive, context): """Process a lock request.""" + entity = directive.entity await hass.services.async_call(entity.domain, SERVICE_LOCK, { ATTR_ENTITY_ID: entity.entity_id }, blocking=False, context=context) - # Alexa expects a lockState in the response, we don't know the actual - # lockState at this point but assume it is locked. It is reported - # correctly later when ReportState is called. The alt. to this approach - # is to implement DeferredResponse - properties = [{ + response = directive.response() + response.add_context_property({ 'name': 'lockState', 'namespace': 'Alexa.LockController', 'value': 'LOCKED' - }] - return api_message(request, context={'properties': properties}) + }) + return response # Not supported by Alexa yet @HANDLERS.register(('Alexa.LockController', 'Unlock')) -@extract_entity -async def async_api_unlock(hass, config, request, context, entity): +async def async_api_unlock(hass, config, directive, context): """Process an unlock request.""" + entity = directive.entity await hass.services.async_call(entity.domain, SERVICE_UNLOCK, { ATTR_ENTITY_ID: entity.entity_id }, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.Speaker', 'SetVolume')) -@extract_entity -async def async_api_set_volume(hass, config, request, context, entity): +async def async_api_set_volume(hass, config, directive, context): """Process a set volume request.""" - volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2) + volume = round(float(directive.payload['volume'] / 100), 2) + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1279,14 +1534,14 @@ async def async_api_set_volume(hass, config, request, context, entity): entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.InputController', 'SelectInput')) -@extract_entity -async def async_api_select_input(hass, config, request, context, entity): +async def async_api_select_input(hass, config, directive, context): """Process a set input request.""" - media_input = request[API_PAYLOAD]['input'] + media_input = directive.payload['input'] + entity = directive.entity # attempt to map the ALL UPPERCASE payload name to a source source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or [] @@ -1300,8 +1555,7 @@ async def async_api_select_input(hass, config, request, context, entity): else: msg = 'failed to map input {} to a media source on {}'.format( media_input, entity.entity_id) - return api_error( - request, error_type='INVALID_VALUE', error_message=msg) + raise _AlexaInvalidValueError(msg) data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1312,15 +1566,15 @@ async def async_api_select_input(hass, config, request, context, entity): entity.domain, media_player.SERVICE_SELECT_SOURCE, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.Speaker', 'AdjustVolume')) -@extract_entity -async def async_api_adjust_volume(hass, config, request, context, entity): +async def async_api_adjust_volume(hass, config, directive, context): """Process an adjust volume request.""" - volume_delta = int(request[API_PAYLOAD]['volume']) + volume_delta = int(directive.payload['volume']) + entity = directive.entity current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) # read current state @@ -1340,18 +1594,18 @@ async def async_api_adjust_volume(hass, config, request, context, entity): entity.domain, media_player.SERVICE_VOLUME_SET, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume')) -@extract_entity -async def async_api_adjust_volume_step(hass, config, request, context, entity): +async def async_api_adjust_volume_step(hass, config, directive, context): """Process an adjust volume step request.""" # media_player volume up/down service does not support specifying steps # each component handles it differently e.g. via config. # For now we use the volumeSteps returned to figure out if we # should step up/down - volume_step = request[API_PAYLOAD]['volumeSteps'] + volume_step = directive.payload['volumeSteps'] + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1366,15 +1620,15 @@ async def async_api_adjust_volume_step(hass, config, request, context, entity): entity.domain, media_player.SERVICE_VOLUME_DOWN, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.StepSpeaker', 'SetMute')) @HANDLERS.register(('Alexa.Speaker', 'SetMute')) -@extract_entity -async def async_api_set_mute(hass, config, request, context, entity): +async def async_api_set_mute(hass, config, directive, context): """Process a set mute request.""" - mute = bool(request[API_PAYLOAD]['mute']) + mute = bool(directive.payload['mute']) + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1385,13 +1639,13 @@ async def async_api_set_mute(hass, config, request, context, entity): entity.domain, media_player.SERVICE_VOLUME_MUTE, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PlaybackController', 'Play')) -@extract_entity -async def async_api_play(hass, config, request, context, entity): +async def async_api_play(hass, config, directive, context): """Process a play request.""" + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id } @@ -1400,13 +1654,13 @@ async def async_api_play(hass, config, request, context, entity): entity.domain, SERVICE_MEDIA_PLAY, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PlaybackController', 'Pause')) -@extract_entity -async def async_api_pause(hass, config, request, context, entity): +async def async_api_pause(hass, config, directive, context): """Process a pause request.""" + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id } @@ -1415,13 +1669,13 @@ async def async_api_pause(hass, config, request, context, entity): entity.domain, SERVICE_MEDIA_PAUSE, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PlaybackController', 'Stop')) -@extract_entity -async def async_api_stop(hass, config, request, context, entity): +async def async_api_stop(hass, config, directive, context): """Process a stop request.""" + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id } @@ -1430,13 +1684,13 @@ async def async_api_stop(hass, config, request, context, entity): entity.domain, SERVICE_MEDIA_STOP, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PlaybackController', 'Next')) -@extract_entity -async def async_api_next(hass, config, request, context, entity): +async def async_api_next(hass, config, directive, context): """Process a next request.""" + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id } @@ -1445,13 +1699,13 @@ async def async_api_next(hass, config, request, context, entity): entity.domain, SERVICE_MEDIA_NEXT_TRACK, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa.PlaybackController', 'Previous')) -@extract_entity -async def async_api_previous(hass, config, request, context, entity): +async def async_api_previous(hass, config, directive, context): """Process a previous request.""" + entity = directive.entity data = { ATTR_ENTITY_ID: entity.entity_id } @@ -1460,33 +1714,7 @@ async def async_api_previous(hass, config, request, context, entity): entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK, data, blocking=False, context=context) - return api_message(request) - - -def api_error_temp_range(hass, request, temp, min_temp, max_temp): - """Create temperature value out of range API error response. - - Async friendly. - """ - unit = hass.config.units.temperature_unit - temp_range = { - 'minimumValue': { - 'value': min_temp, - 'scale': API_TEMP_UNITS[unit], - }, - 'maximumValue': { - 'value': max_temp, - 'scale': API_TEMP_UNITS[unit], - }, - } - - msg = 'The requested temperature {} is out of range'.format(temp) - return api_error( - request, - error_type='TEMPERATURE_VALUE_OUT_OF_RANGE', - error_message=msg, - payload={'validRange': temp_range}, - ) + return directive.response() def temperature_from_object(hass, temp_obj, interval=False): @@ -1506,76 +1734,95 @@ def temperature_from_object(hass, temp_obj, interval=False): @HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature')) -@extract_entity -async def async_api_set_target_temp(hass, config, request, context, entity): +async def async_api_set_target_temp(hass, config, directive, context): """Process a set target temperature request.""" + entity = directive.entity min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP) max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP) + unit = hass.config.units.temperature_unit data = { ATTR_ENTITY_ID: entity.entity_id } - payload = request[API_PAYLOAD] + payload = directive.payload + response = directive.response() if 'targetSetpoint' in payload: temp = temperature_from_object(hass, payload['targetSetpoint']) if temp < min_temp or temp > max_temp: - return api_error_temp_range( - hass, request, temp, min_temp, max_temp) + raise _AlexaTempRangeError(hass, temp, min_temp, max_temp) data[ATTR_TEMPERATURE] = temp + response.add_context_property({ + 'name': 'targetSetpoint', + 'namespace': 'Alexa.ThermostatController', + 'value': {'value': temp, 'scale': API_TEMP_UNITS[unit]}, + }) if 'lowerSetpoint' in payload: temp_low = temperature_from_object(hass, payload['lowerSetpoint']) if temp_low < min_temp or temp_low > max_temp: - return api_error_temp_range( - hass, request, temp_low, min_temp, max_temp) + raise _AlexaTempRangeError(hass, temp_low, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_LOW] = temp_low + response.add_context_property({ + 'name': 'lowerSetpoint', + 'namespace': 'Alexa.ThermostatController', + 'value': {'value': temp_low, 'scale': API_TEMP_UNITS[unit]}, + }) if 'upperSetpoint' in payload: temp_high = temperature_from_object(hass, payload['upperSetpoint']) if temp_high < min_temp or temp_high > max_temp: - return api_error_temp_range( - hass, request, temp_high, min_temp, max_temp) + raise _AlexaTempRangeError(hass, temp_high, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high + response.add_context_property({ + 'name': 'upperSetpoint', + 'namespace': 'Alexa.ThermostatController', + 'value': {'value': temp_high, 'scale': API_TEMP_UNITS[unit]}, + }) await hass.services.async_call( entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, context=context) - return api_message(request) + return response @HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature')) -@extract_entity -async def async_api_adjust_target_temp(hass, config, request, context, entity): +async def async_api_adjust_target_temp(hass, config, directive, context): """Process an adjust target temperature request.""" + entity = directive.entity min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP) max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP) + unit = hass.config.units.temperature_unit temp_delta = temperature_from_object( - hass, request[API_PAYLOAD]['targetSetpointDelta'], interval=True) + hass, directive.payload['targetSetpointDelta'], interval=True) target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta if target_temp < min_temp or target_temp > max_temp: - return api_error_temp_range( - hass, request, target_temp, min_temp, max_temp) + raise _AlexaTempRangeError(hass, target_temp, min_temp, max_temp) data = { ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp, } + response = directive.response() await hass.services.async_call( entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, context=context) + response.add_context_property({ + 'name': 'targetSetpoint', + 'namespace': 'Alexa.ThermostatController', + 'value': {'value': target_temp, 'scale': API_TEMP_UNITS[unit]}, + }) - return api_message(request) + return response @HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode')) -@extract_entity -async def async_api_set_thermostat_mode(hass, config, request, context, - entity): +async def async_api_set_thermostat_mode(hass, config, directive, context): """Process a set thermostat mode request.""" - mode = request[API_PAYLOAD]['thermostatMode'] + entity = directive.entity + mode = directive.payload['thermostatMode'] mode = mode if isinstance(mode, str) else mode['value'] operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST) @@ -1585,12 +1832,7 @@ async def async_api_set_thermostat_mode(hass, config, request, context, ) if ha_mode not in operation_list: msg = 'The requested thermostat mode {} is not supported'.format(mode) - return api_error( - request, - namespace='Alexa.ThermostatController', - error_type='UNSUPPORTED_THERMOSTAT_MODE', - error_message=msg - ) + raise _AlexaUnsupportedThermostatModeError(msg) data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1601,25 +1843,10 @@ async def async_api_set_thermostat_mode(hass, config, request, context, entity.domain, climate.SERVICE_SET_OPERATION_MODE, data, blocking=False, context=context) - return api_message(request) + return directive.response() @HANDLERS.register(('Alexa', 'ReportState')) -@extract_entity -async def async_api_reportstate(hass, config, request, context, entity): +async def async_api_reportstate(hass, config, directive, context): """Process a ReportState request.""" - alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity) - properties = [] - for interface in alexa_entity.interfaces(): - properties.extend(interface.serialize_properties()) - - return api_message( - request, - name='StateReport', - context={'properties': properties} - ) - - -def turned_off_response(message): - """Return a device turned off response.""" - return api_error(message[API_DIRECTIVE], error_type='BRIDGE_UNREACHABLE') + return directive.response(name='StateReport') diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index fe89c263488..b4f228a630d 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -227,11 +227,9 @@ def async_handle_message(hass, cloud, handler_name, payload): @asyncio.coroutine def async_handle_alexa(hass, cloud, payload): """Handle an incoming IoT message for Alexa.""" - if not cloud.alexa_enabled: - return alexa.turned_off_response(payload) - result = yield from alexa.async_handle_message( - hass, cloud.alexa_config, payload) + hass, cloud.alexa_config, payload, + enabled=cloud.alexa_enabled) return result diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index ab268fe860f..1cf01c6092d 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -57,20 +57,21 @@ def get_new_request(namespace, name, endpoint=None): return raw_msg -def test_create_api_message_defaults(): +def test_create_api_message_defaults(hass): """Create a API message response of a request with defaults.""" request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#xy') - request = request['directive'] + directive_header = request['directive']['header'] + directive = smart_home._AlexaDirective(request) - msg = smart_home.api_message(request, payload={'test': 3}) + msg = directive.response(payload={'test': 3})._response assert 'event' in msg msg = msg['event'] assert msg['header']['messageId'] is not None - assert msg['header']['messageId'] != request['header']['messageId'] + assert msg['header']['messageId'] != directive_header['messageId'] assert msg['header']['correlationToken'] == \ - request['header']['correlationToken'] + directive_header['correlationToken'] assert msg['header']['name'] == 'Response' assert msg['header']['namespace'] == 'Alexa' assert msg['header']['payloadVersion'] == '3' @@ -78,23 +79,24 @@ def test_create_api_message_defaults(): assert 'test' in msg['payload'] assert msg['payload']['test'] == 3 - assert msg['endpoint'] == request['endpoint'] + assert msg['endpoint'] == request['directive']['endpoint'] + assert msg['endpoint'] is not request['directive']['endpoint'] def test_create_api_message_special(): """Create a API message response of a request with non defaults.""" request = get_new_request('Alexa.PowerController', 'TurnOn') - request = request['directive'] + directive_header = request['directive']['header'] + directive_header.pop('correlationToken') + directive = smart_home._AlexaDirective(request) - request['header'].pop('correlationToken') - - msg = smart_home.api_message(request, 'testName', 'testNameSpace') + msg = directive.response('testName', 'testNameSpace')._response assert 'event' in msg msg = msg['event'] assert msg['header']['messageId'] is not None - assert msg['header']['messageId'] != request['header']['messageId'] + assert msg['header']['messageId'] != directive_header['messageId'] assert 'correlationToken' not in msg['header'] assert msg['header']['name'] == 'testName' assert msg['header']['namespace'] == 'testNameSpace' @@ -785,13 +787,17 @@ async def test_thermostat(hass): 'Alexa.TemperatureSensor', 'temperature', {'value': 75.0, 'scale': 'FAHRENHEIT'}) - call, _ = await assert_request_calls_service( + call, msg = await assert_request_calls_service( 'Alexa.ThermostatController', 'SetTargetTemperature', 'climate#test_thermostat', 'climate.set_temperature', hass, payload={'targetSetpoint': {'value': 69.0, 'scale': 'FAHRENHEIT'}} ) assert call.data['temperature'] == 69.0 + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'targetSetpoint', + {'value': 69.0, 'scale': 'FAHRENHEIT'}) msg = await assert_request_fails( 'Alexa.ThermostatController', 'SetTargetTemperature', @@ -801,7 +807,7 @@ async def test_thermostat(hass): ) assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE' - call, _ = await assert_request_calls_service( + call, msg = await assert_request_calls_service( 'Alexa.ThermostatController', 'SetTargetTemperature', 'climate#test_thermostat', 'climate.set_temperature', hass, @@ -814,6 +820,16 @@ async def test_thermostat(hass): assert call.data['temperature'] == 70.0 assert call.data['target_temp_low'] == 68.0 assert call.data['target_temp_high'] == 86.0 + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'targetSetpoint', + {'value': 70.0, 'scale': 'FAHRENHEIT'}) + properties.assert_equal( + 'Alexa.ThermostatController', 'lowerSetpoint', + {'value': 68.0, 'scale': 'FAHRENHEIT'}) + properties.assert_equal( + 'Alexa.ThermostatController', 'upperSetpoint', + {'value': 86.0, 'scale': 'FAHRENHEIT'}) msg = await assert_request_fails( 'Alexa.ThermostatController', 'SetTargetTemperature', @@ -837,13 +853,17 @@ async def test_thermostat(hass): ) assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE' - call, _ = await assert_request_calls_service( + call, msg = await assert_request_calls_service( 'Alexa.ThermostatController', 'AdjustTargetTemperature', 'climate#test_thermostat', 'climate.set_temperature', hass, payload={'targetSetpointDelta': {'value': -10.0, 'scale': 'KELVIN'}} ) assert call.data['temperature'] == 52.0 + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'targetSetpoint', + {'value': 52.0, 'scale': 'FAHRENHEIT'}) msg = await assert_request_fails( 'Alexa.ThermostatController', 'AdjustTargetTemperature', @@ -1467,3 +1487,24 @@ async def test_logging_request_with_entity(hass, events): 'name': 'ErrorResponse' } assert event.context == context + + +async def test_disabled(hass): + """When enabled=False, everything fails.""" + hass.states.async_set( + 'switch.test', 'on', {'friendly_name': "Test switch"}) + request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#test') + + call_switch = async_mock_service(hass, 'switch', 'turn_on') + + msg = await smart_home.async_handle_message( + hass, DEFAULT_CONFIG, request, enabled=False) + await hass.async_block_till_done() + + assert 'event' in msg + msg = msg['event'] + + assert not call_switch + assert msg['header']['name'] == 'ErrorResponse' + assert msg['header']['namespace'] == 'Alexa' + assert msg['payload']['type'] == 'BRIDGE_UNREACHABLE' From b71dc752fa2e3be463404351b29ca2985355c5ba Mon Sep 17 00:00:00 2001 From: Luc Touraille Date: Tue, 30 Oct 2018 07:35:23 +0100 Subject: [PATCH 123/230] Upgrade aiofreepybox (#17989) - Add features to get connection status and stats - Improve error reporting - Fix issue with the authentication file (#16934) --- homeassistant/components/device_tracker/freebox.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/device_tracker/freebox.py index 2cac81fd405..b96ee710044 100644 --- a/homeassistant/components/device_tracker/freebox.py +++ b/homeassistant/components/device_tracker/freebox.py @@ -22,7 +22,7 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import ( CONF_HOST, CONF_PORT) -REQUIREMENTS = ['aiofreepybox==0.0.4'] +REQUIREMENTS = ['aiofreepybox==0.0.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 0865c388f4d..78050e51810 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -97,7 +97,7 @@ aioautomatic==0.6.5 aiodns==1.1.1 # homeassistant.components.device_tracker.freebox -aiofreepybox==0.0.4 +aiofreepybox==0.0.5 # homeassistant.components.camera.yi aioftp==0.10.1 From 5337d0b4f3ee2075fb91d940af218da681ed6ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Tue, 30 Oct 2018 10:44:07 +0100 Subject: [PATCH 124/230] fix naming bug (#17976) --- homeassistant/components/weather/smhi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weather/smhi.py b/homeassistant/components/weather/smhi.py index 3bbaab3f8ec..41ac1571339 100644 --- a/homeassistant/components/weather/smhi.py +++ b/homeassistant/components/weather/smhi.py @@ -19,7 +19,7 @@ from homeassistant.const import ( CONF_NAME, TEMP_CELSIUS) from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client -from homeassistant.util import dt, Throttle +from homeassistant.util import dt, slugify, Throttle from homeassistant.components.weather import ( WeatherEntity, ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, @@ -73,11 +73,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entries) -> bool: """Add a weather entity from map location.""" location = config_entry.data - name = location[CONF_NAME] + name = slugify(location[CONF_NAME]) session = aiohttp_client.async_get_clientsession(hass) - entity = SmhiWeather(name, location[CONF_LATITUDE], + entity = SmhiWeather(location[CONF_NAME], location[CONF_LATITUDE], location[CONF_LONGITUDE], session=session) entity.entity_id = ENTITY_ID_SENSOR_FORMAT.format(name) From 622f23abd72f15357a8019aadb6a3c43de3de1f6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 30 Oct 2018 11:35:25 +0100 Subject: [PATCH 125/230] Update frontend to 20181030.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 0e92595ae78..541af46192a 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181026.0'] +REQUIREMENTS = ['home-assistant-frontend==20181030.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 78050e51810..e46fdeb3a6e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -472,7 +472,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.0 +home-assistant-frontend==20181030.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index affbbfdf322..e2e8fc724f2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.0 +home-assistant-frontend==20181030.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 20fb7b59ef912311cff7c33ee50154409b31bd87 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 30 Oct 2018 11:35:40 +0100 Subject: [PATCH 126/230] Update translations --- .../components/auth/.translations/pt-BR.json | 7 ++++ .../dialogflow/.translations/ca.json | 18 +++++++++++ .../dialogflow/.translations/en.json | 32 +++++++++---------- .../dialogflow/.translations/ko.json | 18 +++++++++++ .../dialogflow/.translations/lb.json | 18 +++++++++++ .../dialogflow/.translations/no.json | 18 +++++++++++ .../dialogflow/.translations/ru.json | 18 +++++++++++ .../dialogflow/.translations/sl.json | 18 +++++++++++ .../components/mailgun/.translations/ca.json | 2 +- .../components/mailgun/.translations/no.json | 18 +++++++++++ .../components/mqtt/.translations/pt-BR.json | 4 +++ .../simplisafe/.translations/pt-BR.json | 17 ++++++++++ .../components/smhi/.translations/pt-BR.json | 16 ++++++++++ .../components/sonos/.translations/pt.json | 4 +-- .../components/tradfri/.translations/pt.json | 4 +-- .../components/twilio/.translations/ca.json | 2 +- .../components/twilio/.translations/lb.json | 18 +++++++++++ .../components/twilio/.translations/no.json | 13 ++++++++ .../twilio/.translations/pt-BR.json | 5 +++ .../components/unifi/.translations/no.json | 19 +++++++++-- .../components/unifi/.translations/pt-BR.json | 15 +++++++++ .../components/zwave/.translations/pt-BR.json | 5 +++ 22 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 homeassistant/components/dialogflow/.translations/ca.json create mode 100644 homeassistant/components/dialogflow/.translations/ko.json create mode 100644 homeassistant/components/dialogflow/.translations/lb.json create mode 100644 homeassistant/components/dialogflow/.translations/no.json create mode 100644 homeassistant/components/dialogflow/.translations/ru.json create mode 100644 homeassistant/components/dialogflow/.translations/sl.json create mode 100644 homeassistant/components/mailgun/.translations/no.json create mode 100644 homeassistant/components/simplisafe/.translations/pt-BR.json create mode 100644 homeassistant/components/smhi/.translations/pt-BR.json create mode 100644 homeassistant/components/twilio/.translations/lb.json create mode 100644 homeassistant/components/twilio/.translations/pt-BR.json create mode 100644 homeassistant/components/unifi/.translations/pt-BR.json create mode 100644 homeassistant/components/zwave/.translations/pt-BR.json diff --git a/homeassistant/components/auth/.translations/pt-BR.json b/homeassistant/components/auth/.translations/pt-BR.json index 58c785a5b95..faf854153b0 100644 --- a/homeassistant/components/auth/.translations/pt-BR.json +++ b/homeassistant/components/auth/.translations/pt-BR.json @@ -1,5 +1,12 @@ { "mfa_setup": { + "notify": { + "step": { + "setup": { + "title": "Verificar a configura\u00e7\u00e3o" + } + } + }, "totp": { "error": { "invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente. Se voc\u00ea obtiver este erro de forma consistente, certifique-se de que o rel\u00f3gio do sistema Home Assistant esteja correto." diff --git a/homeassistant/components/dialogflow/.translations/ca.json b/homeassistant/components/dialogflow/.translations/ca.json new file mode 100644 index 00000000000..aa81c06d750 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/ca.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "La vostra inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Dialogflow.", + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + }, + "create_entry": { + "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [integraci\u00f3 webhook de Dialogflow]({dialogflow_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/json\n\nConsulteu [la documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." + }, + "step": { + "user": { + "description": "Esteu segur que voleu configurar Dialogflow?", + "title": "Configureu el Webhook de Dialogflow" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/en.json b/homeassistant/components/dialogflow/.translations/en.json index 4a3e91a3e50..9e1cbbb636e 100644 --- a/homeassistant/components/dialogflow/.translations/en.json +++ b/homeassistant/components/dialogflow/.translations/en.json @@ -1,18 +1,18 @@ { - "config": { - "title": "Dialogflow", - "step": { - "user": { - "title": "Set up the Dialogflow Webhook", - "description": "Are you sure you want to set up Dialogflow?" - } - }, - "abort": { - "one_instance_allowed": "Only a single instance is necessary.", - "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages." - }, - "create_entry": { - "default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details." + "config": { + "abort": { + "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages.", + "one_instance_allowed": "Only a single instance is necessary." + }, + "create_entry": { + "default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details." + }, + "step": { + "user": { + "description": "Are you sure you want to set up Dialogflow?", + "title": "Set up the Dialogflow Webhook" + } + }, + "title": "Dialogflow" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/ko.json b/homeassistant/components/dialogflow/.translations/ko.json new file mode 100644 index 00000000000..f9a71747bd6 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dialogflow \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c\ud569\ub2c8\ub2e4.", + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "create_entry": { + "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow Webhook]({dialogflow_url}) \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694. \n\n - URL: `{webhook_url}`\n - Method: POST\n - Content Type: application/json\n \n \uc790\uc138\ud55c \uc815\ubcf4\ub294 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694." + }, + "step": { + "user": { + "description": "Dialogflow \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Dialogflow Webhook \uc124\uc815" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/lb.json b/homeassistant/components/dialogflow/.translations/lb.json new file mode 100644 index 00000000000..752acbdecd3 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Dialogflow Noriichten z'empf\u00e4nken.", + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + }, + "create_entry": { + "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss [Webhook Integratioun mat Dialogflow]({dialogflow_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." + }, + "step": { + "user": { + "description": "S\u00e9cher fir Dialogflowanzeriichten?", + "title": "Dialogflow Webhook ariichten" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/no.json b/homeassistant/components/dialogflow/.translations/no.json new file mode 100644 index 00000000000..e27d59a40e3 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta Dialogflow meldinger.", + "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig." + }, + "create_entry": { + "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [webhook integrasjon av Dialogflow]({dialogflow_url}). \n\nFyll ut f\u00f8lgende informasjon: \n\n- URL: `{webhook_url}` \n- Metode: POST\n- Innholdstype: application/json\n\nSe [dokumentasjonen]({docs_url}) for ytterligere detaljer." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du \u00f8nsker \u00e5 sette opp Dialogflow?", + "title": "Sett opp Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/ru.json b/homeassistant/components/dialogflow/.translations/ru.json new file mode 100644 index 00000000000..7bc785f2613 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/ru.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Dialogflow.", + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "create_entry": { + "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c [webhooks \u0434\u043b\u044f Dialogflow]({dialogflow_url}).\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + }, + "step": { + "user": { + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Dialogflow?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/sl.json b/homeassistant/components/dialogflow/.translations/sl.json new file mode 100644 index 00000000000..597e65a7658 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/sl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": " \u010ce \u017eelite prejemati sporo\u010dila dialogflow, mora biti Home Assistent dostopen prek interneta.", + "one_instance_allowed": "Potrebna je samo ena instanca." + }, + "create_entry": { + "default": "Za po\u0161iljanje dogodkov Home Assistent-u, boste morali nastaviti [webhook z dialogflow]({twilio_url}).\n\nIzpolnite naslednje informacije:\n\n- URL: `{webhook_url}`\n- Metoda: POST\n- Vrsta vsebine: application/x-www-form-urlencoded\n\nGlej [dokumentacijo]({docs_url}) za nadaljna navodila." + }, + "step": { + "user": { + "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti dialogflow?", + "title": "Nastavite Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/.translations/ca.json b/homeassistant/components/mailgun/.translations/ca.json index d644b9b8c73..f31c4838a4d 100644 --- a/homeassistant/components/mailgun/.translations/ca.json +++ b/homeassistant/components/mailgun/.translations/ca.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." }, "create_entry": { - "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Mailgun] ({mailgun_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." + "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Mailgun]({mailgun_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/.translations/no.json b/homeassistant/components/mailgun/.translations/no.json new file mode 100644 index 00000000000..e1254910542 --- /dev/null +++ b/homeassistant/components/mailgun/.translations/no.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 motta Mailgun-meldinger.", + "one_instance_allowed": "Kun \u00e9n enkelt forekomst er n\u00f8dvendig." + }, + "create_entry": { + "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [Webhooks with Mailgun]({mailgun_url}).\n\nFyll ut f\u00f8lgende informasjon:\n\n- URL: `{webhook_url}`\n- Metode: POST\n- Innholdstype: application/x-www-form-urlencoded\n\nSe [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du \u00f8nsker \u00e5 sette opp Mailgun?", + "title": "Sett opp Mailgun Webhook" + } + }, + "title": "Mailgun" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/.translations/pt-BR.json b/homeassistant/components/mqtt/.translations/pt-BR.json index e73e8b155ec..2de8003e6ed 100644 --- a/homeassistant/components/mqtt/.translations/pt-BR.json +++ b/homeassistant/components/mqtt/.translations/pt-BR.json @@ -3,9 +3,13 @@ "abort": { "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do MQTT \u00e9 permitida." }, + "error": { + "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao Broker" + }, "step": { "broker": { "data": { + "broker": "Broker", "password": "Senha", "port": "Porta", "username": "Nome de usu\u00e1rio" diff --git a/homeassistant/components/simplisafe/.translations/pt-BR.json b/homeassistant/components/simplisafe/.translations/pt-BR.json new file mode 100644 index 00000000000..800ac719ca9 --- /dev/null +++ b/homeassistant/components/simplisafe/.translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "identifier_exists": "Conta j\u00e1 cadastrada", + "invalid_credentials": "C\u00f3digo inv\u00e1lido" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Endere\u00e7o de e-mail" + }, + "title": "Preencha suas informa\u00e7\u00f5es" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/.translations/pt-BR.json b/homeassistant/components/smhi/.translations/pt-BR.json new file mode 100644 index 00000000000..771195d8152 --- /dev/null +++ b/homeassistant/components/smhi/.translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "name_exists": "O nome j\u00e1 existe" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonos/.translations/pt.json b/homeassistant/components/sonos/.translations/pt.json index a2032c76a4a..379ca965314 100644 --- a/homeassistant/components/sonos/.translations/pt.json +++ b/homeassistant/components/sonos/.translations/pt.json @@ -7,9 +7,9 @@ "step": { "confirm": { "description": "Deseja configurar o Sonos?", - "title": "" + "title": "Sonos" } }, - "title": "" + "title": "Sonos" } } \ No newline at end of file diff --git a/homeassistant/components/tradfri/.translations/pt.json b/homeassistant/components/tradfri/.translations/pt.json index e89cb6ac620..d3cb32b5d5f 100644 --- a/homeassistant/components/tradfri/.translations/pt.json +++ b/homeassistant/components/tradfri/.translations/pt.json @@ -4,8 +4,8 @@ "already_configured": "Bridge j\u00e1 est\u00e1 configurada" }, "error": { - "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel ligar ao gateway.", - "invalid_key": "Falha ao registrar-se com a chave fornecida. Se o problema persistir, tente reiniciar o gateway.", + "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel ligar \u00e0 gateway.", + "invalid_key": "Falha ao registrar-se com a chave fornecida. Se o problema persistir, tente reiniciar a gateway.", "timeout": "Tempo excedido a validar o c\u00f3digo." }, "step": { diff --git a/homeassistant/components/twilio/.translations/ca.json b/homeassistant/components/twilio/.translations/ca.json index 3179f420ede..6f63614fdb7 100644 --- a/homeassistant/components/twilio/.translations/ca.json +++ b/homeassistant/components/twilio/.translations/ca.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." }, "create_entry": { - "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Twilio] ({twilio_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." + "default": "Per enviar esdeveniments a Home Assistant, haureu de configurar [Webhooks amb Twilio]({twilio_url}).\n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - M\u00e8tode: POST \n - Tipus de contingut: application/x-www-form-urlencoded\n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants." }, "step": { "user": { diff --git a/homeassistant/components/twilio/.translations/lb.json b/homeassistant/components/twilio/.translations/lb.json new file mode 100644 index 00000000000..96b884b0c8e --- /dev/null +++ b/homeassistant/components/twilio/.translations/lb.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Twilio Noriichten z'empf\u00e4nken.", + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + }, + "create_entry": { + "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Twilio]({twilio_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." + }, + "step": { + "user": { + "description": "S\u00e9cher fir Twilio anzeriichten?", + "title": "Twilio Webhook ariichten" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/no.json b/homeassistant/components/twilio/.translations/no.json index 86e5d9051b3..0d28b094340 100644 --- a/homeassistant/components/twilio/.translations/no.json +++ b/homeassistant/components/twilio/.translations/no.json @@ -1,5 +1,18 @@ { "config": { + "abort": { + "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 motta Twilio-meldinger.", + "one_instance_allowed": "Kun \u00e9n enkelt forekomst er n\u00f8dvendig." + }, + "create_entry": { + "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [Webhooks with Twilio]({twilio_url}). \n\nFyll ut f\u00f8lgende informasjon: \n\n- URL: `{webhook_url}` \n- Metode: POST\n- Innholdstype: application/x-www-form-urlencoded \n\n Se [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data." + }, + "step": { + "user": { + "description": "Er du sikker p\u00e5 at du \u00f8nsker \u00e5 sette opp Twilio?", + "title": "Sett opp Twilio Webhook" + } + }, "title": "Twilio" } } \ No newline at end of file diff --git a/homeassistant/components/twilio/.translations/pt-BR.json b/homeassistant/components/twilio/.translations/pt-BR.json new file mode 100644 index 00000000000..86e5d9051b3 --- /dev/null +++ b/homeassistant/components/twilio/.translations/pt-BR.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/no.json b/homeassistant/components/unifi/.translations/no.json index 7e9251dc026..541b0f60d17 100644 --- a/homeassistant/components/unifi/.translations/no.json +++ b/homeassistant/components/unifi/.translations/no.json @@ -1,13 +1,26 @@ { "config": { + "abort": { + "already_configured": "Kontroller nettstedet er allerede konfigurert", + "user_privilege": "Bruker m\u00e5 v\u00e6re administrator" + }, + "error": { + "faulty_credentials": "Ugyldig brukerlegitimasjon", + "service_unavailable": "Ingen tjeneste tilgjengelig" + }, "step": { "user": { "data": { + "host": "Vert", "password": "Passord", "port": "Port", - "username": "Brukernavn" - } + "site": "Nettsted-ID", + "username": "Brukernavn", + "verify_ssl": "Kontroller bruker riktig sertifikat" + }, + "title": "Sett opp UniFi kontroller" } - } + }, + "title": "UniFi kontroller" } } \ No newline at end of file diff --git a/homeassistant/components/unifi/.translations/pt-BR.json b/homeassistant/components/unifi/.translations/pt-BR.json new file mode 100644 index 00000000000..8b00dada642 --- /dev/null +++ b/homeassistant/components/unifi/.translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "user_privilege": "O usu\u00e1rio precisa ser administrador" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "port": "Porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/.translations/pt-BR.json b/homeassistant/components/zwave/.translations/pt-BR.json new file mode 100644 index 00000000000..16c25cb7cab --- /dev/null +++ b/homeassistant/components/zwave/.translations/pt-BR.json @@ -0,0 +1,5 @@ +{ + "config": { + "title": "Z-Wave" + } +} \ No newline at end of file From 71b56363d38e5d9bd0d1d3182ac66596fdfd80bd Mon Sep 17 00:00:00 2001 From: Nick Whyte Date: Tue, 30 Oct 2018 21:56:00 +1100 Subject: [PATCH 127/230] Reverse out change #14234 BOM Weather throttle fix (#17468) * Reverse out change #14234 BOM Weather throttle fix Reverted back to original throttle code to ensure sensors are updated on time. * Fixed lint issues * Review as a commit * Use last_updated for attributes * lint * lint --- homeassistant/components/sensor/bom.py | 44 ++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/bom.py b/homeassistant/components/sensor/bom.py index 11685c7ff68..6f7bc56cca9 100644 --- a/homeassistant/components/sensor/bom.py +++ b/homeassistant/components/sensor/bom.py @@ -17,13 +17,13 @@ import zipfile import requests import voluptuous as vol +import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, CONF_NAME, ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv _RESOURCE = 'http://www.bom.gov.au/fwo/{}/{}.{}.json' _LOGGER = logging.getLogger(__name__) @@ -39,7 +39,7 @@ CONF_STATION = 'station' CONF_ZONE_ID = 'zone_id' CONF_WMO_ID = 'wmo_id' -MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=35) +MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=60) SENSOR_TYPES = { 'wmo': ['wmo', None], @@ -159,9 +159,7 @@ class BOMCurrentSensor(Entity): """Return the state attributes of the device.""" attr = { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_LAST_UPDATE: datetime.datetime.strptime( - str(self.bom_data.latest_data['local_date_time_full']), - '%Y%m%d%H%M%S'), + ATTR_LAST_UPDATE: self.bom_data.last_updated, ATTR_SENSOR_ID: self._condition, ATTR_STATION_ID: self.bom_data.latest_data['wmo'], ATTR_STATION_NAME: self.bom_data.latest_data['name'], @@ -188,6 +186,7 @@ class BOMCurrentData: self._hass = hass self._zone_id, self._wmo_id = station_id.split('.') self._data = None + self.last_updated = None def _build_url(self): """Build the URL for the requests.""" @@ -211,17 +210,50 @@ class BOMCurrentData: for the latest value that is not `-`. Iterators are used in this method to avoid iterating needlessly - iterating through the entire BOM provided dataset. + through the entire BOM provided dataset. """ condition_readings = (entry[condition] for entry in self._data) return next((x for x in condition_readings if x != '-'), None) + def should_update(self): + """Determine whether an update should occur. + + BOM provides updated data every 30 minutes. We manually define + refreshing logic here rather than a throttle to keep updates + in lock-step with BOM. + + If 35 minutes has passed since the last BOM data update, then + an update should be done. + """ + if self.last_updated is None: + # Never updated before, therefore an update should occur. + return True + + now = datetime.datetime.now() + update_due_at = self.last_updated + datetime.timedelta(minutes=35) + return now > update_due_at + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data from BOM.""" + if not self.should_update(): + _LOGGER.debug( + "BOM was updated %s minutes ago, skipping update as" + " < 35 minutes, Now: %s, LastUpdate: %s", + (datetime.datetime.now() - self.last_updated), + datetime.datetime.now(), self.last_updated) + return + try: result = requests.get(self._build_url(), timeout=10).json() self._data = result['observations']['data'] + + # set lastupdate using self._data[0] as the first element in the + # array is the latest date in the json + self.last_updated = datetime.datetime.strptime( + str(self._data[0]['local_date_time_full']), '%Y%m%d%H%M%S') + return + except ValueError as err: _LOGGER.error("Check BOM %s", err.args) self._data = None From 3de822a0e25ec4f688ff0355e21765b8cf18d333 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 30 Oct 2018 12:03:27 +0100 Subject: [PATCH 128/230] RFC: Static templates match no entities, not all (#17991) * Static templates match no entities, not all * Clean up test values --- homeassistant/helpers/template.py | 6 +++++- tests/components/automation/test_template.py | 8 ++++---- tests/helpers/test_template.py | 6 ++++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4650a4d92c2..83fe36d35c5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -31,6 +31,7 @@ _RE_GET_ENTITIES = re.compile( r"(?:(?:states\.|(?:is_state|is_state_attr|state_attr|states)" r"\((?:[\ \'\"]?))([\w]+\.[\w]+)|([\w]+))", re.I | re.M ) +_RE_JINJA_DELIMITERS = re.compile(r"\{%|\{\{") @bind_hass @@ -59,7 +60,10 @@ def render_complex(value, variables=None): def extract_entities(template, variables=None): """Extract all entities for state_changed listener from template string.""" - if template is None or _RE_NONE_ENTITIES.search(template): + if template is None or _RE_JINJA_DELIMITERS.search(template) is None: + return [] + + if _RE_NONE_ENTITIES.search(template): return MATCH_ALL extraction = _RE_GET_ENTITIES.findall(template) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index d9ad765db3f..c326c7f03f4 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -55,7 +55,7 @@ async def test_if_fires_on_change_str(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': 'true', + 'value_template': '{{ "true" }}', }, 'action': { 'service': 'test.automation' @@ -74,7 +74,7 @@ async def test_if_fires_on_change_str_crazy(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': 'TrUE', + 'value_template': '{{ "TrUE" }}', }, 'action': { 'service': 'test.automation' @@ -112,7 +112,7 @@ async def test_if_not_fires_on_change_str(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': 'False', + 'value_template': 'true', }, 'action': { 'service': 'test.automation' @@ -131,7 +131,7 @@ async def test_if_not_fires_on_change_str_crazy(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'template', - 'value_template': 'Anything other than "true" is false.', + 'value_template': '{{ "Anything other than true is false." }}', }, 'action': { 'service': 'test.automation' diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 802138898a4..3f32f444aaf 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -849,7 +849,9 @@ class TestHelpersTemplate(unittest.TestCase): def test_extract_entities_none_exclude_stuff(self): """Test extract entities function with none or exclude stuff.""" - assert MATCH_ALL == template.extract_entities(None) + assert [] == template.extract_entities(None) + + assert [] == template.extract_entities("mdi:water") assert MATCH_ALL == \ template.extract_entities( @@ -896,7 +898,7 @@ class TestHelpersTemplate(unittest.TestCase): assert ['device_tracker.phone_2'] == \ template.extract_entities(""" -is_state_attr('device_tracker.phone_2', 'battery', 40) +{{ is_state_attr('device_tracker.phone_2', 'battery', 40) }} """) assert sorted([ From f0693f6f91600561f527a4262092b7ab96401e4c Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Tue, 30 Oct 2018 04:12:41 -0700 Subject: [PATCH 129/230] Switch mailgun webhooks to the new Mailgun webhook api (#17919) * Switch mailgun webhooks to the webhook api * Change mailgun strings to indicate application/json is in use * Lint * Revert Changes to .translations. * Don't fail if the API key isn't set --- homeassistant/components/mailgun/__init__.py | 46 +++- homeassistant/components/mailgun/strings.json | 2 +- tests/components/mailgun/test_init.py | 230 ++++++++++++++++-- 3 files changed, 252 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index e52bc663c9a..e78dc0aa479 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -4,6 +4,10 @@ Support for Mailgun. For more details about this component, please refer to the documentation at https://home-assistant.io/components/mailgun/ """ +import hashlib +import hmac +import json +import logging import voluptuous as vol @@ -12,7 +16,7 @@ from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID from homeassistant.helpers import config_entry_flow DOMAIN = 'mailgun' -API_PATH = '/api/{}'.format(DOMAIN) +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['webhook'] MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) CONF_SANDBOX = 'sandbox' @@ -38,9 +42,40 @@ async def async_setup(hass, config): async def handle_webhook(hass, webhook_id, request): """Handle incoming webhook with Mailgun inbound messages.""" - data = dict(await request.post()) - data['webhook_id'] = webhook_id - hass.bus.async_fire(MESSAGE_RECEIVED, data) + body = await request.text() + try: + data = json.loads(body) if body else {} + except ValueError: + return None + + if isinstance(data, dict) and 'signature' in data.keys(): + if await verify_webhook(hass, **data['signature']): + data['webhook_id'] = webhook_id + hass.bus.async_fire(MESSAGE_RECEIVED, data) + return + + _LOGGER.warning( + 'Mailgun webhook received an unauthenticated message - webhook_id: %s', + webhook_id + ) + + +async def verify_webhook(hass, token=None, timestamp=None, signature=None): + """Verify webhook was signed by Mailgun.""" + if DOMAIN not in hass.data: + _LOGGER.warning('Cannot validate Mailgun webhook, missing API Key') + return True + + if not (token and timestamp and signature): + return False + + hmac_digest = hmac.new( + key=bytes(hass.data[DOMAIN][CONF_API_KEY], 'utf-8'), + msg=bytes('{}{}'.format(timestamp, token), 'utf-8'), + digestmod=hashlib.sha256 + ).hexdigest() + + return hmac.compare_digest(signature, hmac_digest) async def async_setup_entry(hass, entry): @@ -59,8 +94,7 @@ config_entry_flow.register_webhook_flow( DOMAIN, 'Mailgun Webhook', { - 'mailgun_url': - 'https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks', + 'mailgun_url': 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', # noqa: E501 pylint: disable=line-too-long 'docs_url': 'https://www.home-assistant.io/components/mailgun/' } ) diff --git a/homeassistant/components/mailgun/strings.json b/homeassistant/components/mailgun/strings.json index 0e993bef5d4..c72ec747b30 100644 --- a/homeassistant/components/mailgun/strings.json +++ b/homeassistant/components/mailgun/strings.json @@ -12,7 +12,7 @@ "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages." }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." } } } diff --git a/tests/components/mailgun/test_init.py b/tests/components/mailgun/test_init.py index 312e3e22bfd..6e84c68e980 100644 --- a/tests/components/mailgun/test_init.py +++ b/tests/components/mailgun/test_init.py @@ -1,39 +1,231 @@ """Test the init file of Mailgun.""" -from unittest.mock import patch +import hashlib +import hmac +from unittest.mock import Mock + +import pytest from homeassistant import data_entry_flow -from homeassistant.components import mailgun - +from homeassistant.components import mailgun, webhook +from homeassistant.const import CONF_API_KEY, CONF_DOMAIN from homeassistant.core import callback +from homeassistant.setup import async_setup_component + +API_KEY = 'abc123' -async def test_config_flow_registers_webhook(hass, aiohttp_client): - """Test setting up Mailgun and sending webhook.""" - with patch('homeassistant.util.get_local_ip', return_value='example.com'): - result = await hass.config_entries.flow.async_init('mailgun', context={ - 'source': 'user' - }) +@pytest.fixture +async def http_client(hass, aiohttp_client): + """Initialize a Home Assistant Server for testing this module.""" + await async_setup_component(hass, webhook.DOMAIN, {}) + return await aiohttp_client(hass.http.app) + + +@pytest.fixture +async def webhook_id_with_api_key(hass): + """Initialize the Mailgun component and get the webhook_id.""" + await async_setup_component(hass, mailgun.DOMAIN, { + mailgun.DOMAIN: { + CONF_API_KEY: API_KEY, + CONF_DOMAIN: 'example.com' + }, + }) + + hass.config.api = Mock(base_url='http://example.com') + result = await hass.config_entries.flow.async_init('mailgun', context={ + 'source': 'user' + }) assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result result = await hass.config_entries.flow.async_configure( result['flow_id'], {}) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - webhook_id = result['result'].data['webhook_id'] - mailgun_events = [] + return result['result'].data['webhook_id'] + + +@pytest.fixture +async def webhook_id_without_api_key(hass): + """Initialize the Mailgun component and get the webhook_id w/o API key.""" + await async_setup_component(hass, mailgun.DOMAIN, {}) + + hass.config.api = Mock(base_url='http://example.com') + result = await hass.config_entries.flow.async_init('mailgun', context={ + 'source': 'user' + }) + assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result + + result = await hass.config_entries.flow.async_configure( + result['flow_id'], {}) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + return result['result'].data['webhook_id'] + + +@pytest.fixture +async def mailgun_events(hass): + """Return a list of mailgun_events triggered.""" + events = [] @callback def handle_event(event): """Handle Mailgun event.""" - mailgun_events.append(event) + events.append(event) hass.bus.async_listen(mailgun.MESSAGE_RECEIVED, handle_event) - client = await aiohttp_client(hass.http.app) - await client.post('/api/webhook/{}'.format(webhook_id), data={ - 'hello': 'mailgun' - }) + return events - assert len(mailgun_events) == 1 - assert mailgun_events[0].data['webhook_id'] == webhook_id - assert mailgun_events[0].data['hello'] == 'mailgun' + +async def test_mailgun_webhook_with_missing_signature( + http_client, + webhook_id_with_api_key, + mailgun_events +): + """Test that webhook doesn't trigger an event without a signature.""" + event_count = len(mailgun_events) + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_with_api_key), + json={ + 'hello': 'mailgun', + 'signature': {} + } + ) + + assert len(mailgun_events) == event_count + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_with_api_key), + json={ + 'hello': 'mailgun', + } + ) + + assert len(mailgun_events) == event_count + + +async def test_mailgun_webhook_with_different_api_key( + http_client, + webhook_id_with_api_key, + mailgun_events +): + """Test that webhook doesn't trigger an event with a wrong signature.""" + timestamp = '1529006854' + token = 'a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0' + + event_count = len(mailgun_events) + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_with_api_key), + json={ + 'hello': 'mailgun', + 'signature': { + 'signature': hmac.new( + key=b'random_api_key', + msg=bytes('{}{}'.format(timestamp, token), 'utf-8'), + digestmod=hashlib.sha256 + ).hexdigest(), + 'timestamp': timestamp, + 'token': token + } + } + ) + + assert len(mailgun_events) == event_count + + +async def test_mailgun_webhook_event_with_correct_api_key( + http_client, + webhook_id_with_api_key, + mailgun_events +): + """Test that webhook triggers an event after validating a signature.""" + timestamp = '1529006854' + token = 'a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0' + + event_count = len(mailgun_events) + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_with_api_key), + json={ + 'hello': 'mailgun', + 'signature': { + 'signature': hmac.new( + key=bytes(API_KEY, 'utf-8'), + msg=bytes('{}{}'.format(timestamp, token), 'utf-8'), + digestmod=hashlib.sha256 + ).hexdigest(), + 'timestamp': timestamp, + 'token': token + } + } + ) + + assert len(mailgun_events) == event_count + 1 + assert mailgun_events[-1].data['webhook_id'] == webhook_id_with_api_key + assert mailgun_events[-1].data['hello'] == 'mailgun' + + +async def test_mailgun_webhook_with_missing_signature_without_api_key( + http_client, + webhook_id_without_api_key, + mailgun_events +): + """Test that webhook triggers an event without a signature w/o API key.""" + event_count = len(mailgun_events) + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_without_api_key), + json={ + 'hello': 'mailgun', + 'signature': {} + } + ) + + assert len(mailgun_events) == event_count + 1 + assert mailgun_events[-1].data['webhook_id'] == webhook_id_without_api_key + assert mailgun_events[-1].data['hello'] == 'mailgun' + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_without_api_key), + json={ + 'hello': 'mailgun', + } + ) + + assert len(mailgun_events) == event_count + 1 + assert mailgun_events[-1].data['webhook_id'] == webhook_id_without_api_key + assert mailgun_events[-1].data['hello'] == 'mailgun' + + +async def test_mailgun_webhook_event_without_an_api_key( + http_client, + webhook_id_without_api_key, + mailgun_events +): + """Test that webhook triggers an event if there is no api key.""" + timestamp = '1529006854' + token = 'a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0' + + event_count = len(mailgun_events) + + await http_client.post( + '/api/webhook/{}'.format(webhook_id_without_api_key), + json={ + 'hello': 'mailgun', + 'signature': { + 'signature': hmac.new( + key=bytes(API_KEY, 'utf-8'), + msg=bytes('{}{}'.format(timestamp, token), 'utf-8'), + digestmod=hashlib.sha256 + ).hexdigest(), + 'timestamp': timestamp, + 'token': token + } + } + ) + + assert len(mailgun_events) == event_count + 1 + assert mailgun_events[-1].data['webhook_id'] == webhook_id_without_api_key + assert mailgun_events[-1].data['hello'] == 'mailgun' From 87bd2a32e4b159a9836f24ee05fb31c3c59751eb Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz Date: Tue, 30 Oct 2018 16:21:58 +0200 Subject: [PATCH 130/230] Change hebrew date at sunset (#17449) * Change date at sunset * Fix tests to actually run and add fix to component * Make tests pass * Use get_astral_event_next instead of get_astral_event_date * Revert to using get_astral_event_date * Make tox happy: reset state on tearDown --- .../components/sensor/jewish_calendar.py | 13 ++- .../components/sensor/test_jewish_calendar.py | 91 +++++++++++++++++-- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py index e1225b8f25d..6c867f02fce 100644 --- a/homeassistant/components/sensor/jewish_calendar.py +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -13,6 +13,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.helpers.sun import get_astral_event_date import homeassistant.util.dt as dt_util REQUIREMENTS = ['hdate==0.6.5'] @@ -107,8 +108,18 @@ class JewishCalSensor(Entity): """Update the state of the sensor.""" import hdate - today = dt_util.now().date() + now = dt_util.as_local(dt_util.now()) + _LOGGER.debug("Now: %s Timezone = %s", now, now.tzinfo) + + today = now.date() upcoming_saturday = today + timedelta((12 - today.weekday()) % 7) + sunset = dt_util.as_local(get_astral_event_date( + self.hass, 'sunset', today)) + + _LOGGER.debug("Now: %s Sunset: %s", now, sunset) + + if now > sunset: + today += timedelta(1) date = hdate.HDate( today, diaspora=self.diaspora, hebrew=self._hebrew) diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py index 9274ab678a9..02539f8503e 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -5,7 +5,7 @@ from datetime import datetime as dt from unittest.mock import patch from homeassistant.util.async_ import run_coroutine_threadsafe -from homeassistant.util.dt import get_time_zone +from homeassistant.util.dt import get_time_zone, set_default_time_zone from homeassistant.setup import setup_component from homeassistant.components.sensor.jewish_calendar import JewishCalSensor from tests.common import get_test_home_assistant @@ -24,6 +24,8 @@ class TestJewishCalenderSensor(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" self.hass.stop() + # Reset the default timezone, so we don't affect other tests + set_default_time_zone(get_time_zone('UTC')) def test_jewish_calendar_min_config(self): """Test minimum jewish calendar configuration.""" @@ -63,10 +65,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_date_output(self): """Test Jewish calendar sensor date output.""" test_time = dt(2018, 9, 3) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='english', sensor_type='date', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), @@ -76,10 +82,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_date_output_hebrew(self): """Test Jewish calendar sensor date output in hebrew.""" test_time = dt(2018, 9, 3) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='date', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -88,10 +98,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_holiday_name(self): """Test Jewish calendar sensor holiday name output in hebrew.""" test_time = dt(2018, 9, 10) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='holiday_name', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -100,10 +114,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_holiday_name_english(self): """Test Jewish calendar sensor holiday name output in english.""" test_time = dt(2018, 9, 10) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='english', sensor_type='holiday_name', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -112,10 +130,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_holyness(self): """Test Jewish calendar sensor holyness value.""" test_time = dt(2018, 9, 10) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='holyness', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -124,10 +146,14 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_torah_reading(self): """Test Jewish calendar sensor torah reading in hebrew.""" test_time = dt(2018, 9, 8) + set_default_time_zone(get_time_zone('UTC')) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='weekly_portion', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) + timezone=get_time_zone("UTC"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -136,10 +162,15 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_first_stars_ny(self): """Test Jewish calendar sensor first stars time in NY, US.""" test_time = dt(2018, 9, 8) + set_default_time_zone(get_time_zone('America/New_York')) + self.hass.config.latitude = 40.7128 + self.hass.config.longitude = -74.0060 + # self.hass.config.time_zone = get_time_zone("America/New_York") sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='first_stars', latitude=40.7128, longitude=-74.0060, timezone=get_time_zone("America/New_York"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -147,11 +178,15 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_first_stars_jerusalem(self): """Test Jewish calendar sensor first stars time in Jerusalem, IL.""" + set_default_time_zone(get_time_zone('Asia/Jerusalem')) test_time = dt(2018, 9, 8) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='first_stars', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="Asia/Jerusalem", diaspora=False) + timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() @@ -159,12 +194,50 @@ class TestJewishCalenderSensor(unittest.TestCase): def test_jewish_calendar_sensor_torah_reading_weekday(self): """Test the sensor showing torah reading also on weekdays.""" + set_default_time_zone(get_time_zone('Asia/Jerusalem')) test_time = dt(2018, 10, 14) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE sensor = JewishCalSensor( name='test', language='hebrew', sensor_type='weekly_portion', latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="Asia/Jerusalem", diaspora=False) + timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) + sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() assert sensor.state == "פרשת לך לך" + + def test_jewish_calendar_sensor_date_before_sunset(self): + """Test the sensor showing the correct date before sunset.""" + tz = get_time_zone('Asia/Jerusalem') + set_default_time_zone(tz) + test_time = tz.localize(dt(2018, 10, 14, 17, 0, 0)) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='date', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) + sensor.hass = self.hass + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), self.hass.loop).result() + assert sensor.state == "ה\' בחשון ה\' תשע\"ט" + + def test_jewish_calendar_sensor_date_after_sunset(self): + """Test the sensor showing the correct date after sunset.""" + tz = get_time_zone('Asia/Jerusalem') + set_default_time_zone(tz) + test_time = tz.localize(dt(2018, 10, 14, 19, 0, 0)) + self.hass.config.latitude = self.TEST_LATITUDE + self.hass.config.longitude = self.TEST_LONGITUDE + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='date', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) + sensor.hass = self.hass + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), self.hass.loop).result() + assert sensor.state == "ו\' בחשון ה\' תשע\"ט" From 4d9ef9e795b4bf5999b0ad6880b8d75ede5c74f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 30 Oct 2018 17:38:09 +0200 Subject: [PATCH 131/230] Import homeassistant domain instead of hardcoding it (#17985) --- .../components/websocket_api/commands.py | 4 +- homeassistant/config.py | 2 +- homeassistant/helpers/state.py | 3 +- .../climate/test_generic_thermostat.py | 43 ++++++++++--------- tests/components/test_conversation.py | 17 ++++---- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 8e1dac4af8e..771a6a57f4f 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -2,7 +2,7 @@ import voluptuous as vol from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED -from homeassistant.core import callback +from homeassistant.core import callback, DOMAIN as HASS_DOMAIN from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service import async_get_all_descriptions @@ -134,7 +134,7 @@ async def handle_call_service(hass, connection, msg): Async friendly. """ blocking = True - if (msg['domain'] == 'homeassistant' and + if (msg['domain'] == HASS_DOMAIN and msg['service'] in ['restart', 'stop']): blocking = False await hass.services.async_call( diff --git a/homeassistant/config.py b/homeassistant/config.py index 8f0690a02c0..5f7107f95ae 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -445,7 +445,7 @@ def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str: getattr(domain_config, '__config_file__', '?'), getattr(domain_config, '__line__', '?')) - if domain != 'homeassistant': + if domain != CONF_CORE: message += ('Please check the docs at ' 'https://home-assistant.io/components/{}/'.format(domain)) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index fa728026eeb..61dc49ce2ec 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -44,14 +44,13 @@ from homeassistant.const import ( STATE_CLOSED, STATE_HOME, STATE_LOCKED, STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_UNLOCKED, SERVICE_SELECT_OPTION) -from homeassistant.core import State +from homeassistant.core import State, DOMAIN as HASS_DOMAIN from homeassistant.util.async_ import run_coroutine_threadsafe from .typing import HomeAssistantType _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. diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 71654afd5b8..f87a5371773 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -6,7 +6,8 @@ from unittest import mock import pytz import homeassistant.core as ha -from homeassistant.core import callback, CoreState, State +from homeassistant.core import ( + callback, DOMAIN as HASS_DOMAIN, CoreState, State) from homeassistant.setup import setup_component, async_setup_component from homeassistant.const import ( SERVICE_TURN_OFF, @@ -257,7 +258,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -270,7 +271,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 2 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -292,7 +293,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -314,7 +315,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -327,7 +328,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -363,7 +364,7 @@ class TestClimateGenericThermostat(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -416,7 +417,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 2 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -445,7 +446,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -458,7 +459,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -480,7 +481,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -502,7 +503,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -515,7 +516,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -592,7 +593,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -618,7 +619,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -692,7 +693,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -709,7 +710,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -773,7 +774,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -796,7 +797,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -863,7 +864,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_ON == call.service assert ENT_SWITCH == call.data['entity_id'] @@ -886,7 +887,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): self.hass.block_till_done() assert 1 == len(self.calls) call = self.calls[0] - assert 'homeassistant' == call.domain + assert HASS_DOMAIN == call.domain assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] diff --git a/tests/components/test_conversation.py b/tests/components/test_conversation.py index 61247b5bdde..7934e016281 100644 --- a/tests/components/test_conversation.py +++ b/tests/components/test_conversation.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access import pytest +from homeassistant.core import DOMAIN as HASS_DOMAIN from homeassistant.setup import async_setup_component from homeassistant.components import conversation import homeassistant.components as component @@ -152,7 +153,7 @@ async def test_turn_on_intent(hass, sentence): assert result hass.states.async_set('light.kitchen', 'off') - calls = async_mock_service(hass, 'homeassistant', 'turn_on') + calls = async_mock_service(hass, HASS_DOMAIN, 'turn_on') await hass.services.async_call( 'conversation', 'process', { @@ -162,7 +163,7 @@ async def test_turn_on_intent(hass, sentence): assert len(calls) == 1 call = calls[0] - assert call.domain == 'homeassistant' + assert call.domain == HASS_DOMAIN assert call.service == 'turn_on' assert call.data == {'entity_id': 'light.kitchen'} @@ -203,7 +204,7 @@ async def test_turn_off_intent(hass, sentence): assert result hass.states.async_set('light.kitchen', 'on') - calls = async_mock_service(hass, 'homeassistant', 'turn_off') + calls = async_mock_service(hass, HASS_DOMAIN, 'turn_off') await hass.services.async_call( 'conversation', 'process', { @@ -213,7 +214,7 @@ async def test_turn_off_intent(hass, sentence): assert len(calls) == 1 call = calls[0] - assert call.domain == 'homeassistant' + assert call.domain == HASS_DOMAIN assert call.service == 'turn_off' assert call.data == {'entity_id': 'light.kitchen'} @@ -228,7 +229,7 @@ async def test_toggle_intent(hass, sentence): assert result hass.states.async_set('light.kitchen', 'on') - calls = async_mock_service(hass, 'homeassistant', 'toggle') + calls = async_mock_service(hass, HASS_DOMAIN, 'toggle') await hass.services.async_call( 'conversation', 'process', { @@ -238,7 +239,7 @@ async def test_toggle_intent(hass, sentence): assert len(calls) == 1 call = calls[0] - assert call.domain == 'homeassistant' + assert call.domain == HASS_DOMAIN assert call.service == 'toggle' assert call.data == {'entity_id': 'light.kitchen'} @@ -253,7 +254,7 @@ async def test_http_api(hass, aiohttp_client): client = await aiohttp_client(hass.http.app) hass.states.async_set('light.kitchen', 'off') - calls = async_mock_service(hass, 'homeassistant', 'turn_on') + calls = async_mock_service(hass, HASS_DOMAIN, 'turn_on') resp = await client.post('/api/conversation/process', json={ 'text': 'Turn the kitchen on' @@ -262,7 +263,7 @@ async def test_http_api(hass, aiohttp_client): assert len(calls) == 1 call = calls[0] - assert call.domain == 'homeassistant' + assert call.domain == HASS_DOMAIN assert call.service == 'turn_on' assert call.data == {'entity_id': 'light.kitchen'} From 865ea82432d27c2b2d5ef187717a1bbff5b46b9f Mon Sep 17 00:00:00 2001 From: Neil Crosby Date: Tue, 30 Oct 2018 18:13:20 +0000 Subject: [PATCH 132/230] Allow jinja namespace command to work. (#18011) --- homeassistant/helpers/template.py | 6 ++++++ tests/helpers/test_template.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 83fe36d35c5..66f289724be 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -9,6 +9,7 @@ import re import jinja2 from jinja2 import contextfilter from jinja2.sandbox import ImmutableSandboxedEnvironment +from jinja2.utils import Namespace from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_UNIT_OF_MEASUREMENT, MATCH_ALL, @@ -618,6 +619,11 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): """Test if callback is safe.""" return isinstance(obj, AllStates) or super().is_safe_callable(obj) + def is_safe_attribute(self, obj, attr, value): + """Test if attribute is safe.""" + return isinstance(obj, Namespace) or \ + super().is_safe_attribute(obj, attr, value) + ENV = TemplateEnvironment() ENV.filters['round'] = forgiving_round diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 3f32f444aaf..573a9f78b72 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -963,6 +963,23 @@ class TestHelpersTemplate(unittest.TestCase): "{{ is_state('media_player.' ~ where , 'playing') }}", {'where': 'livingroom'}) + def test_jinja_namespace(self): + """Test Jinja's namespace command can be used.""" + test_template = template.Template( + ( + "{% set ns = namespace(a_key='') %}" + "{% set ns.a_key = states.sensor.dummy.state %}" + "{{ ns.a_key }}" + ), + self.hass + ) + + self.hass.states.set('sensor.dummy', 'a value') + assert 'a value' == test_template.render() + + self.hass.states.set('sensor.dummy', 'another value') + assert 'another value' == test_template.render() + @asyncio.coroutine def test_state_with_unit(hass): From eef9246db18a22e270f56ea684523082bc032804 Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Tue, 30 Oct 2018 16:19:49 -0400 Subject: [PATCH 133/230] Support for WeMo Humidifier (#17996) * Fix Vera climate component to use correct states Changed the Vera climate component so it uses the STATE_* states from the base climate component. This will allow it to work with Google Assistant. * Wemo Humidifier - Initial Commit * WeMo Humidifier - First draft of component * WeMo Humidifier - Removed direct IO from property * WeMo Humidifier - Trivial comment change * Added myself as codeowner for WeMo * WeMo Humidifier - Fixed various syntax & lint issue * WeMo Humidifier - Small comment addition * WeMo Humidifier - Fix TypeError: 'WemoHumidifier' object is not iterable * WeMo Humidifier - Rename set humidity service * WeMo Humidifier - Add to .coveragerc * WeMo Humidifier - Fixed lint/pylint issues * WeMo Humidifier - First round of requested changes * WeMo Humidifier - Round two of requested changes * WeMo Humidifier - Third round of requested changes * WeMo Humidifier - Fixed whitespace issue on dict comprehension * WeMo Humidifier - Fourth round of requested changes * WeMo Humidifier - Corrected typo in async_add_executor_job call * WeMo Humidifier - Fixed spacing before inline comments --- .coveragerc | 1 + CODEOWNERS | 4 + homeassistant/components/fan/wemo.py | 305 +++++++++++++++++++++++++++ homeassistant/components/wemo.py | 1 + 4 files changed, 311 insertions(+) create mode 100644 homeassistant/components/fan/wemo.py diff --git a/.coveragerc b/.coveragerc index dea02d21f56..1838b1cb3f2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -499,6 +499,7 @@ omit = homeassistant/components/emoncms_history.py homeassistant/components/emulated_hue/upnp.py homeassistant/components/fan/mqtt.py + homeassistant/components/fan/wemo.py homeassistant/components/folder_watcher.py homeassistant/components/foursquare.py homeassistant/components/goalfeed.py diff --git a/CODEOWNERS b/CODEOWNERS index edec0cf0345..bf4c342b474 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -239,6 +239,10 @@ homeassistant/components/*/upcloud.py @scop homeassistant/components/velux.py @Julius2342 homeassistant/components/*/velux.py @Julius2342 +# W +homeassistant/components/wemo.py @sqldiablo +homeassistant/components/*/wemo.py @sqldiablo + # X homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi diff --git a/homeassistant/components/fan/wemo.py b/homeassistant/components/fan/wemo.py new file mode 100644 index 00000000000..902868e4f43 --- /dev/null +++ b/homeassistant/components/fan/wemo.py @@ -0,0 +1,305 @@ +""" +Support for WeMo humidifier. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fan.wemo/ +""" +import asyncio +import logging +from datetime import timedelta + +import requests +import async_timeout +import voluptuous as vol +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.fan import ( + DOMAIN, SUPPORT_SET_SPEED, FanEntity, + SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH) +from homeassistant.exceptions import PlatformNotReady +from homeassistant.const import ATTR_ENTITY_ID + +DEPENDENCIES = ['wemo'] +SCAN_INTERVAL = timedelta(seconds=10) +DATA_KEY = 'fan.wemo' + +_LOGGER = logging.getLogger(__name__) + +ATTR_CURRENT_HUMIDITY = 'current_humidity' +ATTR_TARGET_HUMIDITY = 'target_humidity' +ATTR_FAN_MODE = 'fan_mode' +ATTR_FILTER_LIFE = 'filter_life' +ATTR_FILTER_EXPIRED = 'filter_expired' +ATTR_WATER_LEVEL = 'water_level' + +# The WEMO_ constants below come from pywemo itself +WEMO_ON = 1 +WEMO_OFF = 0 + +WEMO_HUMIDITY_45 = 0 +WEMO_HUMIDITY_50 = 1 +WEMO_HUMIDITY_55 = 2 +WEMO_HUMIDITY_60 = 3 +WEMO_HUMIDITY_100 = 4 + +WEMO_FAN_OFF = 0 +WEMO_FAN_MINIMUM = 1 +WEMO_FAN_LOW = 2 # Not used due to limitations of the base fan implementation +WEMO_FAN_MEDIUM = 3 +WEMO_FAN_HIGH = 4 # Not used due to limitations of the base fan implementation +WEMO_FAN_MAXIMUM = 5 + +WEMO_WATER_EMPTY = 0 +WEMO_WATER_LOW = 1 +WEMO_WATER_GOOD = 2 + +SUPPORTED_SPEEDS = [ + SPEED_OFF, SPEED_LOW, + SPEED_MEDIUM, SPEED_HIGH] + +SUPPORTED_FEATURES = SUPPORT_SET_SPEED + +# Since the base fan object supports a set list of fan speeds, +# we have to reuse some of them when mapping to the 5 WeMo speeds +WEMO_FAN_SPEED_TO_HASS = { + WEMO_FAN_OFF: SPEED_OFF, + WEMO_FAN_MINIMUM: SPEED_LOW, + WEMO_FAN_LOW: SPEED_LOW, # Reusing SPEED_LOW + WEMO_FAN_MEDIUM: SPEED_MEDIUM, + WEMO_FAN_HIGH: SPEED_HIGH, # Reusing SPEED_HIGH + WEMO_FAN_MAXIMUM: SPEED_HIGH +} + +# Because we reused mappings in the previous dict, we have to filter them +# back out in this dict, or else we would have duplicate keys +HASS_FAN_SPEED_TO_WEMO = {v: k for (k, v) in WEMO_FAN_SPEED_TO_HASS.items() + if k not in [WEMO_FAN_LOW, WEMO_FAN_HIGH]} + +SERVICE_SET_HUMIDITY = 'wemo_set_humidity' + +SET_HUMIDITY_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_TARGET_HUMIDITY): + vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up discovered WeMo humidifiers.""" + from pywemo import discovery + + if DATA_KEY not in hass.data: + hass.data[DATA_KEY] = {} + + if discovery_info is None: + return + + location = discovery_info['ssdp_description'] + mac = discovery_info['mac_address'] + + try: + device = WemoHumidifier( + discovery.device_from_description(location, mac)) + except (requests.exceptions.ConnectionError, + requests.exceptions.Timeout) as err: + _LOGGER.error('Unable to access %s (%s)', location, err) + raise PlatformNotReady + + hass.data[DATA_KEY][device.entity_id] = device + add_entities([device]) + + def service_handle(service): + """Handle the WeMo humidifier services.""" + entity_ids = service.data.get(ATTR_ENTITY_ID) + target_humidity = service.data.get(ATTR_TARGET_HUMIDITY) + + if entity_ids: + humidifiers = [device for device in hass.data[DATA_KEY].values() if + device.entity_id in entity_ids] + else: + humidifiers = hass.data[DATA_KEY].values() + + for humidifier in humidifiers: + humidifier.set_humidity(target_humidity) + + # Register service(s) + hass.services.register( + DOMAIN, SERVICE_SET_HUMIDITY, service_handle, + schema=SET_HUMIDITY_SCHEMA) + + +class WemoHumidifier(FanEntity): + """Representation of a WeMo humidifier.""" + + def __init__(self, device): + """Initialize the WeMo switch.""" + self.wemo = device + self._state = None + self._available = True + self._update_lock = None + + self._fan_mode = None + self._target_humidity = None + self._current_humidity = None + self._water_level = None + self._filter_life = None + self._filter_expired = None + self._last_fan_on_mode = WEMO_FAN_MEDIUM + + # look up model name, name, and serial number + # once as it incurs network traffic + self._model_name = self.wemo.model_name + self._name = self.wemo.name + self._serialnumber = self.wemo.serialnumber + + def _subscription_callback(self, _device, _type, _params): + """Update the state by the Wemo device.""" + _LOGGER.info("Subscription update for %s", self.name) + updated = self.wemo.subscription_update(_type, _params) + self.hass.add_job( + self._async_locked_subscription_callback(not updated)) + + async def _async_locked_subscription_callback(self, force_update): + """Handle an update from a subscription.""" + # If an update is in progress, we don't do anything + if self._update_lock.locked(): + return + + await self._async_locked_update(force_update) + self.async_schedule_update_ha_state() + + @property + def unique_id(self): + """Return the ID of this WeMo humidifier.""" + return self._serialnumber + + @property + def name(self): + """Return the name of the humidifier if any.""" + return self._name + + @property + def is_on(self): + """Return true if switch is on. Standby is on.""" + return self._state + + @property + def available(self): + """Return true if switch is available.""" + return self._available + + @property + def icon(self): + """Return the icon of device based on its type.""" + return 'mdi:water-percent' + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + ATTR_CURRENT_HUMIDITY: self._current_humidity, + ATTR_TARGET_HUMIDITY: self._target_humidity, + ATTR_FAN_MODE: self._fan_mode, + ATTR_WATER_LEVEL: self._water_level, + ATTR_FILTER_LIFE: self._filter_life, + ATTR_FILTER_EXPIRED: self._filter_expired + } + + @property + def speed(self) -> str: + """Return the current speed.""" + return WEMO_FAN_SPEED_TO_HASS.get(self._fan_mode) + + @property + def speed_list(self: FanEntity) -> list: + """Get the list of available speeds.""" + return SUPPORTED_SPEEDS + + @property + def supported_features(self: FanEntity) -> int: + """Flag supported features.""" + return SUPPORTED_FEATURES + + async def async_added_to_hass(self): + """Wemo humidifier added to HASS.""" + # Define inside async context so we know our event loop + self._update_lock = asyncio.Lock() + + registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY + await self.hass.async_add_executor_job(registry.register, self.wemo) + registry.on(self.wemo, None, self._subscription_callback) + + async def async_update(self): + """Update WeMo state. + + Wemo has an aggressive retry logic that sometimes can take over a + minute to return. If we don't get a state after 5 seconds, assume the + Wemo humidifier is unreachable. If update goes through, it will be made + available again. + """ + # If an update is in progress, we don't do anything + if self._update_lock.locked(): + return + + try: + with async_timeout.timeout(5): + await asyncio.shield(self._async_locked_update(True)) + except asyncio.TimeoutError: + _LOGGER.warning('Lost connection to %s', self.name) + self._available = False + + async def _async_locked_update(self, force_update): + """Try updating within an async lock.""" + async with self._update_lock: + await self.hass.asyn_add_executor_job(self._update, force_update) + + def _update(self, force_update=True): + """Update the device state.""" + try: + self._state = self.wemo.get_state(force_update) + + self._fan_mode = self.wemo.fan_mode_string + self._target_humidity = self.wemo.desired_humidity_percent + self._current_humidity = self.wemo.current_humidity_percent + self._water_level = self.wemo.water_level_string + self._filter_life = self.wemo.filter_life_percent + self._filter_expired = self.wemo.filter_expired + + if self.wemo.fan_mode != WEMO_FAN_OFF: + self._last_fan_on_mode = self.wemo.fan_mode + + if not self._available: + _LOGGER.info('Reconnected to %s', self.name) + self._available = True + except AttributeError as err: + _LOGGER.warning("Could not update status for %s (%s)", + self.name, err) + self._available = False + + def turn_on(self: FanEntity, speed: str = None, **kwargs) -> None: + """Turn the switch on.""" + if speed is None: + self.wemo.set_state(self._last_fan_on_mode) + else: + self.set_speed(speed) + + def turn_off(self: FanEntity, **kwargs) -> None: + """Turn the switch off.""" + self.wemo.set_state(WEMO_FAN_OFF) + + def set_speed(self: FanEntity, speed: str) -> None: + """Set the fan_mode of the Humidifier.""" + self.wemo.set_state(HASS_FAN_SPEED_TO_WEMO.get(speed)) + + def set_humidity(self: FanEntity, humidity: float) -> None: + """Set the target humidity level for the Humidifier.""" + if humidity < 50: + self.wemo.set_humidity(WEMO_HUMIDITY_45) + elif 50 <= humidity < 55: + self.wemo.set_humidity(WEMO_HUMIDITY_50) + elif 55 <= humidity < 60: + self.wemo.set_humidity(WEMO_HUMIDITY_55) + elif 60 <= humidity < 100: + self.wemo.set_humidity(WEMO_HUMIDITY_60) + elif humidity >= 100: + self.wemo.set_humidity(WEMO_HUMIDITY_100) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 9664ca9419a..1da37ac38b0 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -24,6 +24,7 @@ WEMO_MODEL_DISPATCH = { 'Bridge': 'light', 'CoffeeMaker': 'switch', 'Dimmer': 'light', + 'Humidifier': 'fan', 'Insight': 'switch', 'LightSwitch': 'switch', 'Maker': 'switch', From 9cb5ea20af3ab81c03741da22708da2b1c520c12 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 30 Oct 2018 13:23:44 -0700 Subject: [PATCH 134/230] Fix Bloomsky api call (#18016) --- homeassistant/components/bloomsky.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index 00377b3f12b..55fb15c686d 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -54,7 +54,7 @@ class BloomSky: """Handle all communication with the BloomSky API.""" # API documentation at http://weatherlution.com/bloomsky-api/ - API_URL = 'https://api.bloomsky.com/api/skydata' + API_URL = 'http://api.bloomsky.com/api/skydata' def __init__(self, api_key): """Initialize the BookSky.""" From 9565c0bd1d5d616e956be65ef6bfbbce774d5a13 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 30 Oct 2018 20:25:12 +0000 Subject: [PATCH 135/230] Upgrade pyipma (#17992) * bump dependency version * Add more context to debug message Co-Authored-By: dgomes * shorten debug messages --- homeassistant/components/weather/ipma.py | 8 +++++--- requirements_all.txt | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/weather/ipma.py b/homeassistant/components/weather/ipma.py index 02d94e47558..7fecfbcd074 100644 --- a/homeassistant/components/weather/ipma.py +++ b/homeassistant/components/weather/ipma.py @@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['pyipma==1.1.3'] +REQUIREMENTS = ['pyipma==1.1.4'] _LOGGER = logging.getLogger(__name__) @@ -71,8 +71,8 @@ async def async_setup_platform(hass, config, async_add_entities, station = await Station.get(websession, float(latitude), float(longitude)) - _LOGGER.debug("Initializing ipma weather: coordinates %s, %s", - latitude, longitude) + _LOGGER.debug("Initializing for coordinates %s, %s -> station %s", + latitude, longitude, station.local) async_add_entities([IPMAWeather(station, config)], True) @@ -93,6 +93,8 @@ class IPMAWeather(WeatherEntity): """Update Condition and Forecast.""" with async_timeout.timeout(10, loop=self.hass.loop): self._condition = await self._station.observation() + _LOGGER.debug("Updating station %s, condition %s", + self._station.local, self._condition) self._forecast = await self._station.forecast() self._description = self._forecast[0].description diff --git a/requirements_all.txt b/requirements_all.txt index e46fdeb3a6e..77d58cac617 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -937,7 +937,7 @@ pyialarm==0.2 pyicloud==0.9.1 # homeassistant.components.weather.ipma -pyipma==1.1.3 +pyipma==1.1.4 # homeassistant.components.sensor.irish_rail_transport pyirishrail==0.0.2 From 4073f63256a3c67e05fd8fa142c83a6a5649a787 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Tue, 30 Oct 2018 21:29:11 +0100 Subject: [PATCH 136/230] Async version of melissa (#17721) * rebase upstream * Fixed tests * Fixing lint --- homeassistant/components/climate/melissa.py | 43 +- homeassistant/components/melissa.py | 16 +- homeassistant/components/sensor/melissa.py | 20 +- requirements_all.txt | 2 +- tests/components/climate/test_melissa.py | 535 +++++++++++++------- tests/components/sensor/test_melissa.py | 149 +++--- tests/components/test_melissa.py | 33 +- 7 files changed, 481 insertions(+), 317 deletions(-) diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py index c8e67c14835..bfb18fa0a4c 100644 --- a/homeassistant/components/climate/melissa.py +++ b/homeassistant/components/climate/melissa.py @@ -34,10 +34,11 @@ FAN_MODES = [ ] -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Iterate through and add all Melissa devices.""" api = hass.data[DATA_MELISSA] - devices = api.fetch_devices().values() + devices = (await api.async_fetch_devices()).values() all_devices = [] @@ -46,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): all_devices.append(MelissaClimate( api, device['serial_number'], device)) - add_entities(all_devices) + async_add_entities(all_devices) class MelissaClimate(ClimateDevice): @@ -142,48 +143,48 @@ class MelissaClimate(ClimateDevice): """Return the list of supported features.""" return SUPPORT_FLAGS - def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) - self.send({self._api.TEMP: temp}) + await self.async_send({self._api.TEMP: temp}) - def set_fan_mode(self, fan_mode): + async def async_set_fan_mode(self, fan_mode): """Set fan mode.""" melissa_fan_mode = self.hass_fan_to_melissa(fan_mode) - self.send({self._api.FAN: melissa_fan_mode}) + await self.async_send({self._api.FAN: melissa_fan_mode}) - def set_operation_mode(self, operation_mode): + async def async_set_operation_mode(self, operation_mode): """Set operation mode.""" mode = self.hass_mode_to_melissa(operation_mode) - self.send({self._api.MODE: mode}) + await self.async_send({self._api.MODE: mode}) - def turn_on(self): + async def async_turn_on(self): """Turn on device.""" - self.send({self._api.STATE: self._api.STATE_ON}) + await self.async_send({self._api.STATE: self._api.STATE_ON}) - def turn_off(self): + async def async_turn_off(self): """Turn off device.""" - self.send({self._api.STATE: self._api.STATE_OFF}) + await self.async_send({self._api.STATE: self._api.STATE_OFF}) - def send(self, value): + async def async_send(self, value): """Send action to service.""" try: old_value = self._cur_settings.copy() self._cur_settings.update(value) except AttributeError: old_value = None - if not self._api.send(self._serial_number, self._cur_settings): + if not await self._api.async_send( + self._serial_number, self._cur_settings): self._cur_settings = old_value - return False - return True - def update(self): + async def async_update(self): """Get latest data from Melissa.""" try: - self._data = self._api.status(cached=True)[self._serial_number] - self._cur_settings = self._api.cur_settings( + self._data = (await self._api.async_status(cached=True))[ + self._serial_number] + self._cur_settings = (await self._api.async_cur_settings( self._serial_number - )['controller']['_relation']['command_log'] + ))['controller']['_relation']['command_log'] except KeyError: _LOGGER.warning( 'Unable to update entity %s', self.entity_id) diff --git a/homeassistant/components/melissa.py b/homeassistant/components/melissa.py index 49e7d62f5cb..da2ec49d11f 100644 --- a/homeassistant/components/melissa.py +++ b/homeassistant/components/melissa.py @@ -10,9 +10,9 @@ import voluptuous as vol from homeassistant.const import CONF_USERNAME, CONF_PASSWORD from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.discovery import async_load_platform -REQUIREMENTS = ["py-melissa-climate==1.0.6"] +REQUIREMENTS = ["py-melissa-climate==2.0.0"] _LOGGER = logging.getLogger(__name__) @@ -28,17 +28,19 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -def setup(hass, config): +async def async_setup(hass, config): """Set up the Melissa Climate component.""" import melissa conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) - - api = melissa.Melissa(username=username, password=password) + api = melissa.AsyncMelissa(username=username, password=password) + await api.async_connect() hass.data[DATA_MELISSA] = api - load_platform(hass, 'sensor', DOMAIN, {}, config) - load_platform(hass, 'climate', DOMAIN, {}, config) + hass.async_create_task( + async_load_platform(hass, 'sensor', DOMAIN, {}, config)) + hass.async_create_task( + async_load_platform(hass, 'climate', DOMAIN, {}, config)) return True diff --git a/homeassistant/components/sensor/melissa.py b/homeassistant/components/sensor/melissa.py index df4800cbbd0..c11c5c76740 100644 --- a/homeassistant/components/sensor/melissa.py +++ b/homeassistant/components/sensor/melissa.py @@ -15,17 +15,19 @@ DEPENDENCIES = ['melissa'] _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): """Set up the melissa sensor platform.""" sensors = [] api = hass.data[DATA_MELISSA] - devices = api.fetch_devices().values() + + devices = (await api.async_fetch_devices()).values() for device in devices: if device['type'] == 'melissa': sensors.append(MelissaTemperatureSensor(device, api)) sensors.append(MelissaHumiditySensor(device, api)) - add_entities(sensors) + async_add_entities(sensors) class MelissaSensor(Entity): @@ -54,9 +56,9 @@ class MelissaSensor(Entity): """Return the state of the sensor.""" return self._state - def update(self): + async def async_update(self): """Fetch status from melissa.""" - self._data = self._api.status(cached=True) + self._data = await self._api.async_status(cached=True) class MelissaTemperatureSensor(MelissaSensor): @@ -70,9 +72,9 @@ class MelissaTemperatureSensor(MelissaSensor): """Return the unit of measurement.""" return self._unit - def update(self): + async def async_update(self): """Fetch new state data for the sensor.""" - super().update() + await super().async_update() try: self._state = self._data[self._serial]['temp'] except KeyError: @@ -90,9 +92,9 @@ class MelissaHumiditySensor(MelissaSensor): """Return the unit of measurement.""" return self._unit - def update(self): + async def async_update(self): """Fetch new state data for the sensor.""" - super().update() + await super().async_update() try: self._state = self._data[self._serial]['humidity'] except KeyError: diff --git a/requirements_all.txt b/requirements_all.txt index 77d58cac617..e99e1a34665 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -768,7 +768,7 @@ py-canary==0.5.0 py-cpuinfo==4.0.0 # homeassistant.components.melissa -py-melissa-climate==1.0.6 +py-melissa-climate==2.0.0 # homeassistant.components.camera.synology py-synology==0.2.0 diff --git a/tests/components/climate/test_melissa.py b/tests/components/climate/test_melissa.py index 538e642cd82..2c135bfc09d 100644 --- a/tests/components/climate/test_melissa.py +++ b/tests/components/climate/test_melissa.py @@ -1,9 +1,8 @@ """Test for Melissa climate component.""" -import unittest from unittest.mock import Mock, patch import json -from asynctest import mock +from homeassistant.components.climate.melissa import MelissaClimate from homeassistant.components.climate import ( melissa, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, @@ -15,244 +14,392 @@ from homeassistant.components.melissa import DATA_MELISSA from homeassistant.const import ( TEMP_CELSIUS, STATE_ON, ATTR_TEMPERATURE, STATE_OFF, STATE_IDLE ) -from tests.common import get_test_home_assistant, load_fixture +from tests.common import load_fixture, mock_coro_func + +_SERIAL = "12345678" -class TestMelissa(unittest.TestCase): - """Tests for Melissa climate.""" +def melissa_mock(): + """Use this to mock the melissa api.""" + api = Mock() + api.async_fetch_devices = mock_coro_func( + return_value=json.loads(load_fixture('melissa_fetch_devices.json'))) + api.async_status = mock_coro_func(return_value=json.loads(load_fixture( + 'melissa_status.json'))) + api.async_cur_settings = mock_coro_func( + return_value=json.loads(load_fixture('melissa_cur_settings.json'))) - def setUp(self): # pylint: disable=invalid-name - """Set up test variables.""" - self.hass = get_test_home_assistant() - self._serial = '12345678' + api.async_send = mock_coro_func(return_value=True) - self.api = Mock() - self.api.fetch_devices.return_value = json.loads(load_fixture( - 'melissa_fetch_devices.json' - )) - self.api.cur_settings.return_value = json.loads(load_fixture( - 'melissa_cur_settings.json' - )) - self.api.status.return_value = json.loads(load_fixture( - 'melissa_status.json' - )) - self.api.STATE_OFF = 0 - self.api.STATE_ON = 1 - self.api.STATE_IDLE = 2 + api.STATE_OFF = 0 + api.STATE_ON = 1 + api.STATE_IDLE = 2 - self.api.MODE_AUTO = 0 - self.api.MODE_FAN = 1 - self.api.MODE_HEAT = 2 - self.api.MODE_COOL = 3 - self.api.MODE_DRY = 4 + api.MODE_AUTO = 0 + api.MODE_FAN = 1 + api.MODE_HEAT = 2 + api.MODE_COOL = 3 + api.MODE_DRY = 4 - self.api.FAN_AUTO = 0 - self.api.FAN_LOW = 1 - self.api.FAN_MEDIUM = 2 - self.api.FAN_HIGH = 3 + api.FAN_AUTO = 0 + api.FAN_LOW = 1 + api.FAN_MEDIUM = 2 + api.FAN_HIGH = 3 - self.api.STATE = 'state' - self.api.MODE = 'mode' - self.api.FAN = 'fan' - self.api.TEMP = 'temp' + api.STATE = 'state' + api.MODE = 'mode' + api.FAN = 'fan' + api.TEMP = 'temp' + return api - device = self.api.fetch_devices()[self._serial] - self.thermostat = melissa.MelissaClimate( - self.api, device['serial_number'], device) - self.thermostat.update() - def tearDown(self): # pylint: disable=invalid-name - """Teardown this test class. Stop hass.""" - self.hass.stop() - - @patch("homeassistant.components.climate.melissa.MelissaClimate") - def test_setup_platform(self, mocked_thermostat): - """Test setup_platform.""" - device = self.api.fetch_devices()[self._serial] - thermostat = mocked_thermostat(self.api, device['serial_number'], +async def test_setup_platform(hass): + """Test setup_platform.""" + with patch("homeassistant.components.climate.melissa.MelissaClimate" + ) as mocked_thermostat: + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = mocked_thermostat(api, device['serial_number'], device) thermostats = [thermostat] - self.hass.data[DATA_MELISSA] = self.api + hass.data[DATA_MELISSA] = api config = {} add_entities = Mock() discovery_info = {} - melissa.setup_platform(self.hass, config, add_entities, discovery_info) + await melissa.async_setup_platform( + hass, config, add_entities, discovery_info) add_entities.assert_called_once_with(thermostats) - def test_get_name(self): - """Test name property.""" - assert "Melissa 12345678" == self.thermostat.name - def test_is_on(self): - """Test name property.""" - assert self.thermostat.is_on - self.thermostat._cur_settings = None - assert not self.thermostat.is_on +async def test_get_name(hass): + """Test name property.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert "Melissa 12345678" == thermostat.name - def test_current_fan_mode(self): - """Test current_fan_mode property.""" - self.thermostat.update() - assert SPEED_LOW == self.thermostat.current_fan_mode - self.thermostat._cur_settings = None - assert self.thermostat.current_fan_mode is None - def test_current_temperature(self): - """Test current temperature.""" - assert 27.4 == self.thermostat.current_temperature +async def test_is_on(hass): + """Test name property.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert thermostat.is_on - def test_current_temperature_no_data(self): - """Test current temperature without data.""" - self.thermostat._data = None - assert self.thermostat.current_temperature is None + thermostat._cur_settings = None + assert not thermostat.is_on - def test_target_temperature_step(self): - """Test current target_temperature_step.""" - assert 1 == self.thermostat.target_temperature_step - def test_current_operation(self): - """Test current operation.""" - self.thermostat.update() - assert self.thermostat.current_operation == STATE_HEAT - self.thermostat._cur_settings = None - assert self.thermostat.current_operation is None +async def test_current_fan_mode(hass): + """Test current_fan_mode property.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert SPEED_LOW == thermostat.current_fan_mode - def test_operation_list(self): - """Test the operation list.""" + thermostat._cur_settings = None + assert thermostat.current_fan_mode is None + + +async def test_current_temperature(hass): + """Test current temperature.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 27.4 == thermostat.current_temperature + + +async def test_current_temperature_no_data(hass): + """Test current temperature without data.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + thermostat._data = None + assert thermostat.current_temperature is None + + +async def test_target_temperature_step(hass): + """Test current target_temperature_step.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 1 == thermostat.target_temperature_step + + +async def test_current_operation(hass): + """Test current operation.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert thermostat.current_operation == STATE_HEAT + + thermostat._cur_settings = None + assert thermostat.current_operation is None + + +async def test_operation_list(hass): + """Test the operation list.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) assert [STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT] == \ - self.thermostat.operation_list + thermostat.operation_list - def test_fan_list(self): - """Test the fan list.""" + +async def test_fan_list(hass): + """Test the fan list.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) assert [STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM] == \ - self.thermostat.fan_list + thermostat.fan_list - def test_target_temperature(self): - """Test target temperature.""" - assert 16 == self.thermostat.target_temperature - self.thermostat._cur_settings = None - assert self.thermostat.target_temperature is None - def test_state(self): - """Test state.""" - assert STATE_ON == self.thermostat.state - self.thermostat._cur_settings = None - assert self.thermostat.state is None +async def test_target_temperature(hass): + """Test target temperature.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert 16 == thermostat.target_temperature - def test_temperature_unit(self): - """Test temperature unit.""" - assert TEMP_CELSIUS == self.thermostat.temperature_unit + thermostat._cur_settings = None + assert thermostat.target_temperature is None - def test_min_temp(self): - """Test min temp.""" - assert 16 == self.thermostat.min_temp - def test_max_temp(self): - """Test max temp.""" - assert 30 == self.thermostat.max_temp +async def test_state(hass): + """Test state.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert STATE_ON == thermostat.state - def test_supported_features(self): - """Test supported_features property.""" + thermostat._cur_settings = None + assert thermostat.state is None + + +async def test_temperature_unit(hass): + """Test temperature unit.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert TEMP_CELSIUS == thermostat.temperature_unit + + +async def test_min_temp(hass): + """Test min temp.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 16 == thermostat.min_temp + + +async def test_max_temp(hass): + """Test max temp.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 30 == thermostat.max_temp + + +async def test_supported_features(hass): + """Test supported_features property.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF | SUPPORT_FAN_MODE) - assert features == self.thermostat.supported_features + assert features == thermostat.supported_features - def test_set_temperature(self): - """Test set_temperature.""" - self.api.send.return_value = True - self.thermostat.update() - self.thermostat.set_temperature(**{ATTR_TEMPERATURE: 25}) - assert 25 == self.thermostat.target_temperature - def test_fan_mode(self): - """Test set_fan_mode.""" - self.api.send.return_value = True - self.thermostat.set_fan_mode(SPEED_HIGH) - assert SPEED_HIGH == self.thermostat.current_fan_mode +async def test_set_temperature(hass): + """Test set_temperature.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await thermostat.async_set_temperature(**{ATTR_TEMPERATURE: 25}) + assert 25 == thermostat.target_temperature - def test_set_operation_mode(self): - """Test set_operation_mode.""" - self.api.send.return_value = True - self.thermostat.set_operation_mode(STATE_COOL) - assert STATE_COOL == self.thermostat.current_operation - def test_turn_on(self): - """Test turn_on.""" - self.thermostat.turn_on() - assert self.thermostat.state +async def test_fan_mode(hass): + """Test set_fan_mode.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await hass.async_block_till_done() + await thermostat.async_set_fan_mode(SPEED_HIGH) + await hass.async_block_till_done() + assert SPEED_HIGH == thermostat.current_fan_mode - def test_turn_off(self): - """Test turn_off.""" - self.thermostat.turn_off() - assert STATE_OFF == self.thermostat.state - def test_send(self): - """Test send.""" - self.thermostat.update() - assert self.thermostat.send( - {'fan': self.api.FAN_MEDIUM}) - assert SPEED_MEDIUM == self.thermostat.current_fan_mode - self.api.send.return_value = False - self.thermostat._cur_settings = None - assert not self.thermostat.send({ - 'fan': self.api.FAN_LOW}) - assert SPEED_LOW != self.thermostat.current_fan_mode - assert self.thermostat._cur_settings is None +async def test_set_operation_mode(hass): + """Test set_operation_mode.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await hass.async_block_till_done() + await thermostat.async_set_operation_mode(STATE_COOL) + await hass.async_block_till_done() + assert STATE_COOL == thermostat.current_operation - @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') - def test_update(self, mocked_warning): - """Test update.""" - self.thermostat.update() - assert SPEED_LOW == self.thermostat.current_fan_mode - assert STATE_HEAT == self.thermostat.current_operation - self.thermostat._api.status.side_effect = KeyError('boom') - self.thermostat.update() - mocked_warning.assert_called_once_with( - 'Unable to update entity %s', self.thermostat.entity_id) - def test_melissa_state_to_hass(self): - """Test for translate melissa states to hass.""" - assert STATE_OFF == self.thermostat.melissa_state_to_hass(0) - assert STATE_ON == self.thermostat.melissa_state_to_hass(1) - assert STATE_IDLE == self.thermostat.melissa_state_to_hass(2) - assert self.thermostat.melissa_state_to_hass(3) is None +async def test_turn_on(hass): + """Test turn_on.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await hass.async_block_till_done() + await thermostat.async_turn_on() + await hass.async_block_till_done() + assert thermostat.state - def test_melissa_op_to_hass(self): - """Test for translate melissa operations to hass.""" - assert STATE_FAN_ONLY == self.thermostat.melissa_op_to_hass(1) - assert STATE_HEAT == self.thermostat.melissa_op_to_hass(2) - assert STATE_COOL == self.thermostat.melissa_op_to_hass(3) - assert STATE_DRY == self.thermostat.melissa_op_to_hass(4) - assert self.thermostat.melissa_op_to_hass(5) is None - def test_melissa_fan_to_hass(self): - """Test for translate melissa fan state to hass.""" - assert STATE_AUTO == self.thermostat.melissa_fan_to_hass(0) - assert SPEED_LOW == self.thermostat.melissa_fan_to_hass(1) - assert SPEED_MEDIUM == self.thermostat.melissa_fan_to_hass(2) - assert SPEED_HIGH == self.thermostat.melissa_fan_to_hass(3) - assert self.thermostat.melissa_fan_to_hass(4) is None +async def test_turn_off(hass): + """Test turn_off.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await hass.async_block_till_done() + await thermostat.async_turn_off() + await hass.async_block_till_done() + assert STATE_OFF == thermostat.state - @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') - def test_hass_mode_to_melissa(self, mocked_warning): - """Test for hass operations to melssa.""" - assert 1 == self.thermostat.hass_mode_to_melissa(STATE_FAN_ONLY) - assert 2 == self.thermostat.hass_mode_to_melissa(STATE_HEAT) - assert 3 == self.thermostat.hass_mode_to_melissa(STATE_COOL) - assert 4 == self.thermostat.hass_mode_to_melissa(STATE_DRY) - self.thermostat.hass_mode_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s mode", "test") - @mock.patch('homeassistant.components.climate.melissa._LOGGER.warning') - def test_hass_fan_to_melissa(self, mocked_warning): - """Test for translate melissa states to hass.""" - assert 0 == self.thermostat.hass_fan_to_melissa(STATE_AUTO) - assert 1 == self.thermostat.hass_fan_to_melissa(SPEED_LOW) - assert 2 == self.thermostat.hass_fan_to_melissa(SPEED_MEDIUM) - assert 3 == self.thermostat.hass_fan_to_melissa(SPEED_HIGH) - self.thermostat.hass_fan_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s fan mode", "test") +async def test_send(hass): + """Test send.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + await hass.async_block_till_done() + await thermostat.async_send({'fan': api.FAN_MEDIUM}) + await hass.async_block_till_done() + assert SPEED_MEDIUM == thermostat.current_fan_mode + api.async_send.return_value = mock_coro_func(return_value=False) + thermostat._cur_settings = None + await thermostat.async_send({'fan': api.FAN_LOW}) + await hass.async_block_till_done() + assert SPEED_LOW != thermostat.current_fan_mode + assert thermostat._cur_settings is None + + +async def test_update(hass): + """Test update.""" + with patch('homeassistant.components.melissa'): + with patch('homeassistant.components.climate.melissa._LOGGER.warning' + ) as mocked_warning: + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + await thermostat.async_update() + assert SPEED_LOW == thermostat.current_fan_mode + assert STATE_HEAT == thermostat.current_operation + api.async_status = mock_coro_func(exception=KeyError('boom')) + await thermostat.async_update() + mocked_warning.assert_called_once_with( + 'Unable to update entity %s', thermostat.entity_id) + + +async def test_melissa_state_to_hass(hass): + """Test for translate melissa states to hass.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert STATE_OFF == thermostat.melissa_state_to_hass(0) + assert STATE_ON == thermostat.melissa_state_to_hass(1) + assert STATE_IDLE == thermostat.melissa_state_to_hass(2) + assert thermostat.melissa_state_to_hass(3) is None + + +async def test_melissa_op_to_hass(hass): + """Test for translate melissa operations to hass.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert STATE_FAN_ONLY == thermostat.melissa_op_to_hass(1) + assert STATE_HEAT == thermostat.melissa_op_to_hass(2) + assert STATE_COOL == thermostat.melissa_op_to_hass(3) + assert STATE_DRY == thermostat.melissa_op_to_hass(4) + assert thermostat.melissa_op_to_hass(5) is None + + +async def test_melissa_fan_to_hass(hass): + """Test for translate melissa fan state to hass.""" + with patch('homeassistant.components.melissa'): + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert STATE_AUTO == thermostat.melissa_fan_to_hass(0) + assert SPEED_LOW == thermostat.melissa_fan_to_hass(1) + assert SPEED_MEDIUM == thermostat.melissa_fan_to_hass(2) + assert SPEED_HIGH == thermostat.melissa_fan_to_hass(3) + assert thermostat.melissa_fan_to_hass(4) is None + + +async def test_hass_mode_to_melissa(hass): + """Test for hass operations to melssa.""" + with patch('homeassistant.components.melissa'): + with patch('homeassistant.components.climate.melissa._LOGGER.warning' + ) as mocked_warning: + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 1 == thermostat.hass_mode_to_melissa(STATE_FAN_ONLY) + assert 2 == thermostat.hass_mode_to_melissa(STATE_HEAT) + assert 3 == thermostat.hass_mode_to_melissa(STATE_COOL) + assert 4 == thermostat.hass_mode_to_melissa(STATE_DRY) + thermostat.hass_mode_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s mode", "test") + + +async def test_hass_fan_to_melissa(hass): + """Test for translate melissa states to hass.""" + with patch('homeassistant.components.melissa'): + with patch('homeassistant.components.climate.melissa._LOGGER.warning' + ) as mocked_warning: + api = melissa_mock() + device = (await api.async_fetch_devices())[_SERIAL] + thermostat = MelissaClimate(api, _SERIAL, device) + assert 0 == thermostat.hass_fan_to_melissa(STATE_AUTO) + assert 1 == thermostat.hass_fan_to_melissa(SPEED_LOW) + assert 2 == thermostat.hass_fan_to_melissa(SPEED_MEDIUM) + assert 3 == thermostat.hass_fan_to_melissa(SPEED_HIGH) + thermostat.hass_fan_to_melissa("test") + mocked_warning.assert_called_once_with( + "Melissa have no setting for %s fan mode", "test") diff --git a/tests/components/sensor/test_melissa.py b/tests/components/sensor/test_melissa.py index aff3df83c3d..024e2e564eb 100644 --- a/tests/components/sensor/test_melissa.py +++ b/tests/components/sensor/test_melissa.py @@ -1,89 +1,112 @@ """Test for Melissa climate component.""" -import unittest import json -from unittest.mock import Mock +from unittest.mock import Mock, patch + +from homeassistant.components.sensor.melissa import MelissaTemperatureSensor, \ + MelissaHumiditySensor + +from tests.common import load_fixture, mock_coro_func from homeassistant.components.melissa import DATA_MELISSA from homeassistant.components.sensor import melissa -from homeassistant.components.sensor.melissa import MelissaTemperatureSensor, \ - MelissaHumiditySensor from homeassistant.const import TEMP_CELSIUS -from tests.common import get_test_home_assistant, load_fixture -class TestMelissa(unittest.TestCase): - """Tests for Melissa climate.""" +_SERIAL = "12345678" - def setUp(self): # pylint: disable=invalid-name - """Set up test variables.""" - self.hass = get_test_home_assistant() - self._serial = '12345678' - self.api = Mock() - self.api.fetch_devices.return_value = json.loads(load_fixture( - 'melissa_fetch_devices.json' - )) - self.api.status.return_value = json.loads(load_fixture( - 'melissa_status.json' - )) +def melissa_mock(): + """Use this to mock the melissa api.""" + api = Mock() + api.async_fetch_devices = mock_coro_func( + return_value=json.loads(load_fixture('melissa_fetch_devices.json'))) + api.async_status = mock_coro_func(return_value=json.loads(load_fixture( + 'melissa_status.json' + ))) - self.api.TEMP = 'temp' - self.api.HUMIDITY = 'humidity' - device = self.api.fetch_devices()[self._serial] - self.temp = MelissaTemperatureSensor(device, self.api) - self.hum = MelissaHumiditySensor(device, self.api) + api.TEMP = 'temp' + api.HUMIDITY = 'humidity' + return api - def tearDown(self): # pylint: disable=invalid-name - """Teardown this test class. Stop hass.""" - self.hass.stop() - def test_setup_platform(self): - """Test setup_platform.""" - self.hass.data[DATA_MELISSA] = self.api +async def test_setup_platform(hass): + """Test setup_platform.""" + with patch('homeassistant.components.melissa'): + hass.data[DATA_MELISSA] = melissa_mock() config = {} - add_entities = Mock() + async_add_entities = mock_coro_func() discovery_info = {} - melissa.setup_platform(self.hass, config, add_entities, discovery_info) + await melissa.async_setup_platform( + hass, config, async_add_entities, discovery_info) - def test_name(self): - """Test name property.""" - device = self.api.fetch_devices()[self._serial] - assert self.temp.name == '{0} {1}'.format( + +async def test_name(hass): + """Test name property.""" + with patch('homeassistant.components.melissa'): + mocked_melissa = melissa_mock() + device = (await mocked_melissa.async_fetch_devices())[_SERIAL] + temp = MelissaTemperatureSensor(device, mocked_melissa) + hum = MelissaHumiditySensor(device, mocked_melissa) + + assert temp.name == '{0} {1}'.format( device['name'], - self.temp._type + temp._type ) - assert self.hum.name == '{0} {1}'.format( + assert hum.name == '{0} {1}'.format( device['name'], - self.hum._type + hum._type ) - def test_state(self): - """Test state property.""" - device = self.api.status()[self._serial] - self.temp.update() - assert self.temp.state == device[self.api.TEMP] - self.hum.update() - assert self.hum.state == device[self.api.HUMIDITY] - def test_unit_of_measurement(self): - """Test unit of measurement property.""" - assert self.temp.unit_of_measurement == TEMP_CELSIUS - assert self.hum.unit_of_measurement == '%' +async def test_state(hass): + """Test state property.""" + with patch('homeassistant.components.melissa'): + mocked_melissa = melissa_mock() + device = (await mocked_melissa.async_fetch_devices())[_SERIAL] + status = (await mocked_melissa.async_status())[_SERIAL] + temp = MelissaTemperatureSensor(device, mocked_melissa) + hum = MelissaHumiditySensor(device, mocked_melissa) + await temp.async_update() + assert temp.state == status[mocked_melissa.TEMP] + await hum.async_update() + assert hum.state == status[mocked_melissa.HUMIDITY] - def test_update(self): - """Test for update.""" - self.temp.update() - assert self.temp.state == 27.4 - self.hum.update() - assert self.hum.state == 18.7 - def test_update_keyerror(self): - """Test for faulty update.""" - self.temp._api.status.return_value = {} - self.temp.update() - assert self.temp.state is None - self.hum._api.status.return_value = {} - self.hum.update() - assert self.hum.state is None +async def test_unit_of_measurement(hass): + """Test unit of measurement property.""" + with patch('homeassistant.components.melissa'): + mocked_melissa = melissa_mock() + device = (await mocked_melissa.async_fetch_devices())[_SERIAL] + temp = MelissaTemperatureSensor(device, mocked_melissa) + hum = MelissaHumiditySensor(device, mocked_melissa) + assert temp.unit_of_measurement == TEMP_CELSIUS + assert hum.unit_of_measurement == '%' + + +async def test_update(hass): + """Test for update.""" + with patch('homeassistant.components.melissa'): + mocked_melissa = melissa_mock() + device = (await mocked_melissa.async_fetch_devices())[_SERIAL] + temp = MelissaTemperatureSensor(device, mocked_melissa) + hum = MelissaHumiditySensor(device, mocked_melissa) + await temp.async_update() + assert temp.state == 27.4 + await hum.async_update() + assert hum.state == 18.7 + + +async def test_update_keyerror(hass): + """Test for faulty update.""" + with patch('homeassistant.components.melissa'): + mocked_melissa = melissa_mock() + device = (await mocked_melissa.async_fetch_devices())[_SERIAL] + temp = MelissaTemperatureSensor(device, mocked_melissa) + hum = MelissaHumiditySensor(device, mocked_melissa) + mocked_melissa.async_status = mock_coro_func(return_value={}) + await temp.async_update() + assert temp.state is None + await hum.async_update() + assert hum.state is None diff --git a/tests/components/test_melissa.py b/tests/components/test_melissa.py index 020d4f7db1e..d7bb9b3c1e4 100644 --- a/tests/components/test_melissa.py +++ b/tests/components/test_melissa.py @@ -1,6 +1,5 @@ """The test for the Melissa Climate component.""" -import unittest -from tests.common import get_test_home_assistant, MockDependency +from tests.common import MockDependency, mock_coro_func from homeassistant.components import melissa @@ -12,25 +11,15 @@ VALID_CONFIG = { } -class TestMelissa(unittest.TestCase): - """Test the Melissa component.""" +async def test_setup(hass): + """Test setting up the Melissa component.""" + with MockDependency('melissa') as mocked_melissa: + mocked_melissa.AsyncMelissa().async_connect = mock_coro_func() + await melissa.async_setup(hass, VALID_CONFIG) - def setUp(self): # pylint: disable=invalid-name - """Initialize the values for this test class.""" - self.hass = get_test_home_assistant() - self.config = VALID_CONFIG - - def tearDown(self): # pylint: disable=invalid-name - """Teardown this test class. Stop hass.""" - self.hass.stop() - - @MockDependency("melissa") - def test_setup(self, mocked_melissa): - """Test setting up the Melissa component.""" - melissa.setup(self.hass, self.config) - - mocked_melissa.Melissa.assert_called_with( + mocked_melissa.AsyncMelissa.assert_called_with( username="********", password="********") - assert melissa.DATA_MELISSA in self.hass.data - assert isinstance(self.hass.data[melissa.DATA_MELISSA], type( - mocked_melissa.Melissa())) + + assert melissa.DATA_MELISSA in hass.data + assert isinstance(hass.data[melissa.DATA_MELISSA], type( + mocked_melissa.AsyncMelissa())) From 9c77465c0e8be1ec9d33828730ba7518936b15f2 Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Tue, 30 Oct 2018 22:33:04 -0700 Subject: [PATCH 137/230] Upgrade to asuswrt 1.1.1 to better handle mac addresses with letters in them (#18030) * Upgrade to asuswrt 1.1.1 to better handle mac addresses with letters in them Signed-off-by: Gavin Mogan * Update requirements_all as well Signed-off-by: Gavin Mogan --- homeassistant/components/device_tracker/asuswrt.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 7f79bea0d2e..5d97ed219a9 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, CONF_PROTOCOL) -REQUIREMENTS = ['aioasuswrt==1.1.0'] +REQUIREMENTS = ['aioasuswrt==1.1.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index e99e1a34665..d52a0cdd092 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -88,7 +88,7 @@ abodepy==0.14.0 afsapi==0.0.4 # homeassistant.components.device_tracker.asuswrt -aioasuswrt==1.1.0 +aioasuswrt==1.1.1 # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 From 314e5ac29636471aa06c11a410320a4016d8c065 Mon Sep 17 00:00:00 2001 From: Philip Rosenberg-Watt Date: Wed, 31 Oct 2018 00:22:55 -0600 Subject: [PATCH 138/230] Add SMA sensor SSL verification option (#18033) --- homeassistant/components/sensor/sma.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index 5290b2018bf..dc4b73c6950 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -13,7 +13,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL, - EVENT_HOMEASSISTANT_STOP) + CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -66,6 +66,7 @@ CUSTOM_SCHEMA = vol.Any({ PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_GROUP, default=GROUPS[0]): vol.In(GROUPS), vol.Required(CONF_SENSORS): vol.Schema({cv.slug: cv.ensure_list}), @@ -107,7 +108,7 @@ async def async_setup_platform( async_add_entities(hass_sensors) # Init the SMA interface - session = async_get_clientsession(hass) + session = async_get_clientsession(hass, verify_ssl=config[CONF_VERIFY_SSL]) grp = {GROUP_INSTALLER: pysma.GROUP_INSTALLER, GROUP_USER: pysma.GROUP_USER}[config[CONF_GROUP]] From dcc46226ee75320591367773c2339aedd811c555 Mon Sep 17 00:00:00 2001 From: Nate Clark Date: Wed, 31 Oct 2018 02:42:33 -0400 Subject: [PATCH 139/230] Konnected: Pass hass_config to load_platform (#18027) --- homeassistant/components/konnected.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/konnected.py b/homeassistant/components/konnected.py index 21e2fbba4c7..a6fe4be65fa 100644 --- a/homeassistant/components/konnected.py +++ b/homeassistant/components/konnected.py @@ -156,7 +156,7 @@ async def async_setup(hass, config): # Initialize devices specified in the configuration on boot for device in cfg.get(CONF_DEVICES): - ConfiguredDevice(hass, device).save_data() + ConfiguredDevice(hass, device, config).save_data() discovery.async_listen( hass, @@ -172,10 +172,11 @@ async def async_setup(hass, config): class ConfiguredDevice: """A representation of a configured Konnected device.""" - def __init__(self, hass, config): + def __init__(self, hass, config, hass_config): """Initialize the Konnected device.""" self.hass = hass self.config = config + self.hass_config = hass_config @property def device_id(self): @@ -237,11 +238,11 @@ class ConfiguredDevice: self.hass.data[DOMAIN][CONF_DEVICES][self.device_id] = device_data discovery.load_platform( - self.hass, 'binary_sensor', - DOMAIN, {'device_id': self.device_id}) + self.hass, 'binary_sensor', DOMAIN, + {'device_id': self.device_id}, self.hass_config) discovery.load_platform( self.hass, 'switch', DOMAIN, - {'device_id': self.device_id}) + {'device_id': self.device_id}, self.hass_config) class DiscoveredDevice: From 9c840f93f04dae24442bfb284254c10f4787ea2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 31 Oct 2018 10:10:28 +0200 Subject: [PATCH 140/230] Use const.SUN_EVENT_* more (#18039) --- .../components/device_sun_light_trigger.py | 10 +++-- .../components/sensor/jewish_calendar.py | 5 ++- homeassistant/components/sun.py | 7 ++-- homeassistant/components/switch/flux.py | 7 ++-- homeassistant/helpers/condition.py | 4 +- homeassistant/helpers/config_validation.py | 3 +- homeassistant/helpers/event.py | 11 +++--- homeassistant/helpers/sun.py | 7 +++- tests/components/automation/test_sun.py | 21 +++++----- tests/components/switch/test_flux.py | 35 +++++++++-------- tests/helpers/test_sun.py | 39 ++++++++++--------- 11 files changed, 82 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 40a602056bf..2b047e92c1e 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( ATTR_PROFILE, ATTR_TRANSITION, DOMAIN as DOMAIN_LIGHT) from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_HOME, - STATE_NOT_HOME) + STATE_NOT_HOME, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) from homeassistant.helpers.event import ( async_track_point_in_utc_time, async_track_state_change) from homeassistant.helpers.sun import is_up, get_astral_event_next @@ -79,7 +79,7 @@ async def async_setup(hass, config): Async friendly. """ - next_setting = get_astral_event_next(hass, 'sunset') + next_setting = get_astral_event_next(hass, SUN_EVENT_SUNSET) if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) @@ -123,7 +123,8 @@ async def async_setup(hass, config): start_point + index * LIGHT_TRANSITION_TIME) async_track_point_in_utc_time(hass, schedule_light_turn_on, - get_astral_event_next(hass, 'sunrise')) + get_astral_event_next(hass, + SUN_EVENT_SUNRISE)) # If the sun is already above horizon schedule the time-based pre-sun set # event. @@ -153,7 +154,8 @@ async def async_setup(hass, config): # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. elif (start_point and - start_point < now < get_astral_event_next(hass, 'sunset')): + start_point < now < get_astral_event_next(hass, + SUN_EVENT_SUNSET)): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py index 6c867f02fce..2c917ba0f3b 100644 --- a/homeassistant/components/sensor/jewish_calendar.py +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -10,7 +10,8 @@ import logging import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.const import ( + CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, SUN_EVENT_SUNSET) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.sun import get_astral_event_date @@ -114,7 +115,7 @@ class JewishCalSensor(Entity): today = now.date() upcoming_saturday = today + timedelta((12 - today.weekday()) % 7) sunset = dt_util.as_local(get_astral_event_date( - self.hass, 'sunset', today)) + self.hass, SUN_EVENT_SUNSET, today)) _LOGGER.debug("Now: %s Sunset: %s", now, sunset) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index e2717047b0a..250c6a2ed2f 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -7,7 +7,8 @@ https://home-assistant.io/components/sun/ import logging from datetime import timedelta -from homeassistant.const import CONF_ELEVATION +from homeassistant.const import ( + CONF_ELEVATION, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import ( @@ -109,9 +110,9 @@ class Sun(Entity): self.next_noon = get_astral_event_next( self.hass, 'solar_noon', utc_point_in_time) self.next_rising = get_astral_event_next( - self.hass, 'sunrise', utc_point_in_time) + self.hass, SUN_EVENT_SUNRISE, utc_point_in_time) self.next_setting = get_astral_event_next( - self.hass, 'sunset', utc_point_in_time) + self.hass, SUN_EVENT_SUNSET, utc_point_in_time) @callback def update_sun_position(self, utc_point_in_time): diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index 00388822be1..01c85dce6cf 100644 --- a/homeassistant/components/switch/flux.py +++ b/homeassistant/components/switch/flux.py @@ -18,7 +18,7 @@ from homeassistant.components.light import ( from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, CONF_PLATFORM, CONF_LIGHTS, CONF_MODE, - SERVICE_TURN_ON) + SERVICE_TURN_ON, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.sun import get_astral_event_date from homeassistant.util import slugify @@ -200,7 +200,7 @@ class FluxSwitch(SwitchDevice): if now is None: now = dt_now() - sunset = get_astral_event_date(self.hass, 'sunset', now.date()) + sunset = get_astral_event_date(self.hass, SUN_EVENT_SUNSET, now.date()) start_time = self.find_start_time(now) stop_time = self.find_stop_time(now) @@ -283,7 +283,8 @@ class FluxSwitch(SwitchDevice): hour=self._start_time.hour, minute=self._start_time.minute, second=0) else: - sunrise = get_astral_event_date(self.hass, 'sunrise', now.date()) + sunrise = get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, + now.date()) return sunrise def find_stop_time(self, now): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 930f68c3da4..86112e2aea2 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -243,8 +243,8 @@ def sun(hass, before=None, after=None, before_offset=None, after_offset=None): before_offset = before_offset or timedelta(0) after_offset = after_offset or timedelta(0) - sunrise = get_astral_event_date(hass, 'sunrise', today) - sunset = get_astral_event_date(hass, 'sunset', today) + sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today) + sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, today) if sunrise is None and SUN_EVENT_SUNRISE in (before, after): # There is no sunrise today diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 9ce4b6b166d..5c49a1b50e1 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -535,7 +535,8 @@ SUN_CONDITION_SCHEMA = vol.All(vol.Schema({ vol.Required(CONF_CONDITION): 'sun', vol.Optional('before'): sun_event, vol.Optional('before_offset'): time_period, - vol.Optional('after'): vol.All(vol.Lower, vol.Any('sunset', 'sunrise')), + vol.Optional('after'): vol.All(vol.Lower, vol.Any( + SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)), vol.Optional('after_offset'): time_period, }), has_at_least_one_key('before', 'after')) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 1c28e2878e9..c1dae00bed5 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -6,7 +6,8 @@ from homeassistant.loader import bind_hass from homeassistant.helpers.sun import get_astral_event_next from ..core import HomeAssistant, callback from ..const import ( - ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) + ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, + SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) from ..util import dt as dt_util from ..util.async_ import run_callback_threadsafe @@ -274,12 +275,12 @@ def async_track_sunrise(hass, action, offset=None): nonlocal remove remove = async_track_point_in_utc_time( hass, sunrise_automation_listener, get_astral_event_next( - hass, 'sunrise', offset=offset)) + hass, SUN_EVENT_SUNRISE, offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( hass, sunrise_automation_listener, get_astral_event_next( - hass, 'sunrise', offset=offset)) + hass, SUN_EVENT_SUNRISE, offset=offset)) def remove_listener(): """Remove sunset listener.""" @@ -303,12 +304,12 @@ def async_track_sunset(hass, action, offset=None): nonlocal remove remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next( - hass, 'sunset', offset=offset)) + hass, SUN_EVENT_SUNSET, offset=offset)) hass.async_run_job(action) remove = async_track_point_in_utc_time( hass, sunset_automation_listener, get_astral_event_next( - hass, 'sunset', offset=offset)) + hass, SUN_EVENT_SUNSET, offset=offset)) def remove_listener(): """Remove sunset listener.""" diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index 59c2160a180..99ea1bad11f 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -1,6 +1,7 @@ """Helpers for sun events.""" import datetime +from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.core import callback from homeassistant.util import dt as dt_util from homeassistant.loader import bind_hass @@ -86,7 +87,9 @@ def is_up(hass, utc_point_in_time=None): if utc_point_in_time is None: utc_point_in_time = dt_util.utcnow() - next_sunrise = get_astral_event_next(hass, 'sunrise', utc_point_in_time) - next_sunset = get_astral_event_next(hass, 'sunset', utc_point_in_time) + next_sunrise = get_astral_event_next(hass, SUN_EVENT_SUNRISE, + utc_point_in_time) + next_sunset = get_astral_event_next(hass, SUN_EVENT_SUNSET, + utc_point_in_time) return next_sunrise > next_sunset diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index dce7933a759..030662da05c 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -4,6 +4,7 @@ from datetime import datetime import pytest from unittest.mock import patch +from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.setup import async_setup_component from homeassistant.components import sun import homeassistant.components.automation as automation @@ -39,7 +40,7 @@ async def test_sunset_trigger(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunset', + 'event': SUN_EVENT_SUNSET, }, 'action': { 'service': 'test.automation', @@ -75,7 +76,7 @@ async def test_sunrise_trigger(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunrise', + 'event': SUN_EVENT_SUNRISE, }, 'action': { 'service': 'test.automation', @@ -99,7 +100,7 @@ async def test_sunset_trigger_with_offset(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunset', + 'event': SUN_EVENT_SUNSET, 'offset': '0:30:00' }, 'action': { @@ -130,7 +131,7 @@ async def test_sunrise_trigger_with_offset(hass, calls): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunrise', + 'event': SUN_EVENT_SUNRISE, 'offset': '-0:30:00' }, 'action': { @@ -154,7 +155,7 @@ async def test_if_action_before(hass, calls): }, 'condition': { 'condition': 'sun', - 'before': 'sunrise', + 'before': SUN_EVENT_SUNRISE, }, 'action': { 'service': 'test.automation' @@ -187,7 +188,7 @@ async def test_if_action_after(hass, calls): }, 'condition': { 'condition': 'sun', - 'after': 'sunrise', + 'after': SUN_EVENT_SUNRISE, }, 'action': { 'service': 'test.automation' @@ -220,7 +221,7 @@ async def test_if_action_before_with_offset(hass, calls): }, 'condition': { 'condition': 'sun', - 'before': 'sunrise', + 'before': SUN_EVENT_SUNRISE, 'before_offset': '+1:00:00' }, 'action': { @@ -254,7 +255,7 @@ async def test_if_action_after_with_offset(hass, calls): }, 'condition': { 'condition': 'sun', - 'after': 'sunrise', + 'after': SUN_EVENT_SUNRISE, 'after_offset': '+1:00:00' }, 'action': { @@ -288,8 +289,8 @@ async def test_if_action_before_and_after_during(hass, calls): }, 'condition': { 'condition': 'sun', - 'after': 'sunrise', - 'before': 'sunset' + 'after': SUN_EVENT_SUNRISE, + 'before': SUN_EVENT_SUNSET }, 'action': { 'service': 'test.automation' diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index dbe31e8f47b..56e9a4434c0 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -4,7 +4,8 @@ from unittest.mock import patch from homeassistant.setup import setup_component from homeassistant.components import switch, light -from homeassistant.const import CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON +from homeassistant.const import ( + CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SUN_EVENT_SUNRISE) import homeassistant.loader as loader import homeassistant.util.dt as dt_util @@ -91,7 +92,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -131,7 +132,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -176,7 +177,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -222,7 +223,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -269,7 +270,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -314,7 +315,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -364,7 +365,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -415,7 +416,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -465,7 +466,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -514,7 +515,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -564,7 +565,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -611,7 +612,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -660,7 +661,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -720,7 +721,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: print('sunrise {}'.format(sunrise_time)) return sunrise_time print('sunset {}'.format(sunset_time)) @@ -774,7 +775,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -818,7 +819,7 @@ class TestSwitchFlux(unittest.TestCase): sunrise_time = test_time.replace(hour=5, minute=0, second=0) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py index 8fb4e44b0ed..af639e69432 100644 --- a/tests/helpers/test_sun.py +++ b/tests/helpers/test_sun.py @@ -4,6 +4,7 @@ import unittest from unittest.mock import patch from datetime import timedelta, datetime +from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET import homeassistant.util.dt as dt_util import homeassistant.helpers.sun as sun @@ -92,9 +93,9 @@ class TestSun(unittest.TestCase): assert next_noon == sun.get_astral_event_next( self.hass, 'solar_noon') assert next_rising == sun.get_astral_event_next( - self.hass, 'sunrise') + self.hass, SUN_EVENT_SUNRISE) assert next_setting == sun.get_astral_event_next( - self.hass, 'sunset') + self.hass, SUN_EVENT_SUNSET) def test_date_events(self): """Test retrieving next sun events.""" @@ -123,9 +124,9 @@ class TestSun(unittest.TestCase): assert noon == sun.get_astral_event_date( self.hass, 'solar_noon', utc_today) assert sunrise == sun.get_astral_event_date( - self.hass, 'sunrise', utc_today) + self.hass, SUN_EVENT_SUNRISE, utc_today) assert sunset == sun.get_astral_event_date( - self.hass, 'sunset', utc_today) + self.hass, SUN_EVENT_SUNSET, utc_today) def test_date_events_default_date(self): """Test retrieving next sun events.""" @@ -155,9 +156,9 @@ class TestSun(unittest.TestCase): assert noon == sun.get_astral_event_date( self.hass, 'solar_noon', utc_today) assert sunrise == sun.get_astral_event_date( - self.hass, 'sunrise', utc_today) + self.hass, SUN_EVENT_SUNRISE, utc_today) assert sunset == sun.get_astral_event_date( - self.hass, 'sunset', utc_today) + self.hass, SUN_EVENT_SUNSET, utc_today) def test_date_events_accepts_datetime(self): """Test retrieving next sun events.""" @@ -186,9 +187,9 @@ class TestSun(unittest.TestCase): assert noon == sun.get_astral_event_date( self.hass, 'solar_noon', utc_now) assert sunrise == sun.get_astral_event_date( - self.hass, 'sunrise', utc_now) + self.hass, SUN_EVENT_SUNRISE, utc_now) assert sunset == sun.get_astral_event_date( - self.hass, 'sunset', utc_now) + self.hass, SUN_EVENT_SUNSET, utc_now) def test_is_up(self): """Test retrieving next sun events.""" @@ -209,19 +210,21 @@ class TestSun(unittest.TestCase): june = datetime(2016, 6, 1, tzinfo=dt_util.UTC) - print(sun.get_astral_event_date(self.hass, 'sunrise', + print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, datetime(2017, 7, 25))) - print(sun.get_astral_event_date(self.hass, 'sunset', + print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, datetime(2017, 7, 25))) - print(sun.get_astral_event_date(self.hass, 'sunrise', + print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, datetime(2017, 7, 26))) - print(sun.get_astral_event_date(self.hass, 'sunset', + print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, datetime(2017, 7, 26))) - assert sun.get_astral_event_next(self.hass, 'sunrise', june) == \ - datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) - assert sun.get_astral_event_next(self.hass, 'sunset', june) == \ - datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) - assert sun.get_astral_event_date(self.hass, 'sunrise', june) is None - assert sun.get_astral_event_date(self.hass, 'sunset', june) is None + assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNRISE, june) \ + == datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) + assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNSET, june) \ + == datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) + assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, june) \ + is None + assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, june) \ + is None From e85e5789a275516d40ccda7a2fbfa97e5323b8e6 Mon Sep 17 00:00:00 2001 From: kennedyshead Date: Wed, 31 Oct 2018 09:44:41 +0100 Subject: [PATCH 141/230] Bumping aioasuswrt to 1.1.2 (#18042) --- homeassistant/components/device_tracker/asuswrt.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 5d97ed219a9..561d41562de 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, CONF_PROTOCOL) -REQUIREMENTS = ['aioasuswrt==1.1.1'] +REQUIREMENTS = ['aioasuswrt==1.1.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index d52a0cdd092..6dd7488a2e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -88,7 +88,7 @@ abodepy==0.14.0 afsapi==0.0.4 # homeassistant.components.device_tracker.asuswrt -aioasuswrt==1.1.1 +aioasuswrt==1.1.2 # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 From 239e314dc15016c7fbfa9b273391e39b2dc353d0 Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Wed, 31 Oct 2018 05:54:15 -0400 Subject: [PATCH 142/230] Add services.yaml entry for new WeMo Humidifier platform service (#18032) * WeMo Humidifier - Add entry to services.yaml * WeMo Humidifier - Fix typo in wemo.py * WeMo Humidifier - Fixed incorrect parameter name --- homeassistant/components/fan/services.yaml | 10 ++++++++++ homeassistant/components/fan/wemo.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fan/services.yaml b/homeassistant/components/fan/services.yaml index 039cc33f748..b183c6049ee 100644 --- a/homeassistant/components/fan/services.yaml +++ b/homeassistant/components/fan/services.yaml @@ -204,3 +204,13 @@ xiaomi_miio_set_dry_off: entity_id: description: Name of the xiaomi miio entity. example: 'fan.xiaomi_miio_device' + +wemo_set_humidity: + description: Set the target humidity of WeMo humidifier devices. + fields: + entity_id: + description: Names of the WeMo humidifier entities (0 or more entities, if no entity_id is provided, all WeMo humidifiers will have the target humidity set). + example: 'fan.wemo_humidifier' + target_humidity: + description: Target humidity. This is a float value between 0 and 100, but will be mapped to the humidity levels that WeMo humidifiers support (45, 50, 55, 60, and 100/Max) by rounding the value down to the nearest supported value. + example: 56.5 diff --git a/homeassistant/components/fan/wemo.py b/homeassistant/components/fan/wemo.py index 902868e4f43..0c570465f4d 100644 --- a/homeassistant/components/fan/wemo.py +++ b/homeassistant/components/fan/wemo.py @@ -251,7 +251,7 @@ class WemoHumidifier(FanEntity): async def _async_locked_update(self, force_update): """Try updating within an async lock.""" async with self._update_lock: - await self.hass.asyn_add_executor_job(self._update, force_update) + await self.hass.async_add_executor_job(self._update, force_update) def _update(self, force_update=True): """Update the device state.""" From b12e79e5cf7613789cc7a8b2a05e389e296b8434 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Wed, 31 Oct 2018 12:33:43 +0100 Subject: [PATCH 143/230] Add opentherm_gw services (#17762) * Move components/opentherm_gw.py to components/opentherm_gw/__init__.py * Update requirements-all.txt * Await set_clock coroutine rather than scheduling it. * Create task for async_load_platform --- .coveragerc | 2 +- .../__init__.py} | 168 +++++++++++++++++- .../components/opentherm_gw/services.yaml | 81 +++++++++ requirements_all.txt | 2 +- 4 files changed, 246 insertions(+), 7 deletions(-) rename homeassistant/components/{opentherm_gw.py => opentherm_gw/__init__.py} (50%) create mode 100644 homeassistant/components/opentherm_gw/services.yaml diff --git a/.coveragerc b/.coveragerc index 1838b1cb3f2..72451dab531 100644 --- a/.coveragerc +++ b/.coveragerc @@ -247,7 +247,7 @@ omit = homeassistant/components/opencv.py homeassistant/components/*/opencv.py - homeassistant/components/opentherm_gw.py + homeassistant/components/opentherm_gw/* homeassistant/components/*/opentherm_gw.py homeassistant/components/openuv/__init__.py diff --git a/homeassistant/components/opentherm_gw.py b/homeassistant/components/opentherm_gw/__init__.py similarity index 50% rename from homeassistant/components/opentherm_gw.py rename to homeassistant/components/opentherm_gw/__init__.py index 7152a58afcc..3cf66c72a3a 100644 --- a/homeassistant/components/opentherm_gw.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -5,14 +5,16 @@ For more details about this component, please refer to the documentation at http://home-assistant.io/components/opentherm_gw/ """ import logging +from datetime import datetime, date import voluptuous as vol from homeassistant.components.binary_sensor import DOMAIN as COMP_BINARY_SENSOR from homeassistant.components.sensor import DOMAIN as COMP_SENSOR -from homeassistant.const import (CONF_DEVICE, CONF_MONITORED_VARIABLES, - CONF_NAME, PRECISION_HALVES, PRECISION_TENTHS, - PRECISION_WHOLE) +from homeassistant.const import ( + ATTR_DATE, ATTR_ID, ATTR_TEMPERATURE, ATTR_TIME, CONF_DEVICE, + CONF_MONITORED_VARIABLES, CONF_NAME, EVENT_HOMEASSISTANT_STOP, + PRECISION_HALVES, PRECISION_TENTHS, PRECISION_WHOLE) from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -20,16 +22,72 @@ import homeassistant.helpers.config_validation as cv DOMAIN = 'opentherm_gw' +ATTR_MODE = 'mode' +ATTR_LEVEL = 'level' + CONF_CLIMATE = 'climate' CONF_FLOOR_TEMP = 'floor_temperature' CONF_PRECISION = 'precision' DATA_DEVICE = 'device' DATA_GW_VARS = 'gw_vars' +DATA_LATEST_STATUS = 'latest_status' DATA_OPENTHERM_GW = 'opentherm_gw' SIGNAL_OPENTHERM_GW_UPDATE = 'opentherm_gw_update' +SERVICE_RESET_GATEWAY = 'reset_gateway' + +SERVICE_SET_CLOCK = 'set_clock' +SERVICE_SET_CLOCK_SCHEMA = vol.Schema({ + vol.Optional(ATTR_DATE, default=date.today()): cv.date, + vol.Optional(ATTR_TIME, default=datetime.now().time()): cv.time, +}) + +SERVICE_SET_CONTROL_SETPOINT = 'set_control_setpoint' +SERVICE_SET_CONTROL_SETPOINT_SCHEMA = vol.Schema({ + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=0, max=90)), +}) + +SERVICE_SET_GPIO_MODE = 'set_gpio_mode' +SERVICE_SET_GPIO_MODE_SCHEMA = vol.Schema(vol.Any( + vol.Schema({ + vol.Required(ATTR_ID): vol.Equal('A'), + vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), + vol.Range(min=0, max=6)), + }), + vol.Schema({ + vol.Required(ATTR_ID): vol.Equal('B'), + vol.Required(ATTR_MODE): vol.All(vol.Coerce(int), + vol.Range(min=0, max=7)), + }), +)) + +SERVICE_SET_LED_MODE = 'set_led_mode' +SERVICE_SET_LED_MODE_SCHEMA = vol.Schema({ + vol.Required(ATTR_ID): vol.In('ABCDEF'), + vol.Required(ATTR_MODE): vol.In('RXTBOFHWCEMP'), +}) + +SERVICE_SET_MAX_MOD = 'set_max_modulation' +SERVICE_SET_MAX_MOD_SCHEMA = vol.Schema({ + vol.Required(ATTR_LEVEL): vol.All(vol.Coerce(int), + vol.Range(min=-1, max=100)) +}) + +SERVICE_SET_OAT = 'set_outside_temperature' +SERVICE_SET_OAT_SCHEMA = vol.Schema({ + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=-40, max=99)), +}) + +SERVICE_SET_SB_TEMP = 'set_setback_temperature' +SERVICE_SET_SB_TEMP_SCHEMA = vol.Schema({ + vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), + vol.Range(min=0, max=30)), +}) + CLIMATE_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string, vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES, @@ -46,7 +104,7 @@ CONFIG_SCHEMA = vol.Schema({ }), }, extra=vol.ALLOW_EXTRA) -REQUIREMENTS = ['pyotgw==0.2b1'] +REQUIREMENTS = ['pyotgw==0.3b0'] _LOGGER = logging.getLogger(__name__) @@ -60,9 +118,11 @@ async def async_setup(hass, config): hass.data[DATA_OPENTHERM_GW] = { DATA_DEVICE: gateway, DATA_GW_VARS: pyotgw.vars, + DATA_LATEST_STATUS: {} } hass.async_create_task(connect_and_subscribe( hass, conf[CONF_DEVICE], gateway)) + hass.async_create_task(register_services(hass, gateway)) hass.async_create_task(async_load_platform( hass, 'climate', DOMAIN, conf.get(CONF_CLIMATE), config)) if monitored_vars: @@ -76,13 +136,110 @@ async def connect_and_subscribe(hass, device_path, gateway): await gateway.connect(hass.loop, device_path) _LOGGER.debug("Connected to OpenTherm Gateway at %s", device_path) + async def cleanup(event): + """Reset overrides on the gateway.""" + await gateway.set_control_setpoint(0) + await gateway.set_max_relative_mod('-') + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, cleanup) + async def handle_report(status): """Handle reports from the OpenTherm Gateway.""" _LOGGER.debug("Received report: %s", status) + hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] = status async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) gateway.subscribe(handle_report) +async def register_services(hass, gateway): + """Register services for the component.""" + gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] + + async def reset_gateway(call): + """Reset the OpenTherm Gateway.""" + mode_rst = gw_vars.OTGW_MODE_RESET + status = await gateway.set_mode(mode_rst) + hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] = status + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_RESET_GATEWAY, reset_gateway) + + async def set_control_setpoint(call): + """Set the control setpoint on the OpenTherm Gateway.""" + gw_var = gw_vars.DATA_CONTROL_SETPOINT + value = await gateway.set_control_setpoint(call.data[ATTR_TEMPERATURE]) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({gw_var: value}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_CONTROL_SETPOINT, + set_control_setpoint, + SERVICE_SET_CONTROL_SETPOINT_SCHEMA) + + async def set_device_clock(call): + """Set the clock on the OpenTherm Gateway.""" + attr_date = call.data[ATTR_DATE] + attr_time = call.data[ATTR_TIME] + await gateway.set_clock(datetime.combine(attr_date, attr_time)) + hass.services.async_register(DOMAIN, SERVICE_SET_CLOCK, set_device_clock, + SERVICE_SET_CLOCK_SCHEMA) + + async def set_gpio_mode(call): + """Set the OpenTherm Gateway GPIO modes.""" + gpio_id = call.data[ATTR_ID] + gpio_mode = call.data[ATTR_MODE] + mode = await gateway.set_gpio_mode(gpio_id, gpio_mode) + gpio_var = getattr(gw_vars, 'OTGW_GPIO_{}'.format(gpio_id)) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({gpio_var: mode}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_GPIO_MODE, set_gpio_mode, + SERVICE_SET_GPIO_MODE_SCHEMA) + + async def set_led_mode(call): + """Set the OpenTherm Gateway LED modes.""" + led_id = call.data[ATTR_ID] + led_mode = call.data[ATTR_MODE] + mode = await gateway.set_led_mode(led_id, led_mode) + led_var = getattr(gw_vars, 'OTGW_LED_{}'.format(led_id)) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({led_var: mode}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_LED_MODE, set_led_mode, + SERVICE_SET_LED_MODE_SCHEMA) + + async def set_max_mod(call): + """Set the max modulation level.""" + gw_var = gw_vars.DATA_SLAVE_MAX_RELATIVE_MOD + level = call.data[ATTR_LEVEL] + if level == -1: + # Backend only clears setting on non-numeric values. + level = '-' + value = await gateway.set_max_relative_mod(level) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({gw_var: value}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_MAX_MOD, set_max_mod, + SERVICE_SET_MAX_MOD_SCHEMA) + + async def set_outside_temp(call): + """Provide the outside temperature to the OpenTherm Gateway.""" + gw_var = gw_vars.DATA_OUTSIDE_TEMP + value = await gateway.set_outside_temp(call.data[ATTR_TEMPERATURE]) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({gw_var: value}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_OAT, set_outside_temp, + SERVICE_SET_OAT_SCHEMA) + + async def set_setback_temp(call): + """Set the OpenTherm Gateway SetBack temperature.""" + gw_var = gw_vars.OTGW_SB_TEMP + value = await gateway.set_setback_temp(call.data[ATTR_TEMPERATURE]) + status = hass.data[DATA_OPENTHERM_GW][DATA_LATEST_STATUS] + status.update({gw_var: value}) + async_dispatcher_send(hass, SIGNAL_OPENTHERM_GW_UPDATE, status) + hass.services.async_register(DOMAIN, SERVICE_SET_SB_TEMP, set_setback_temp, + SERVICE_SET_SB_TEMP_SCHEMA) + + async def setup_monitored_vars(hass, config, monitored_vars): """Set up requested sensors.""" gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS] @@ -203,4 +360,5 @@ async def setup_monitored_vars(hass, config, monitored_vars): hass.async_create_task(async_load_platform( hass, COMP_BINARY_SENSOR, DOMAIN, binary_sensors, config)) if sensors: - await async_load_platform(hass, COMP_SENSOR, DOMAIN, sensors, config) + hass.async_create_task(async_load_platform( + hass, COMP_SENSOR, DOMAIN, sensors, config)) diff --git a/homeassistant/components/opentherm_gw/services.yaml b/homeassistant/components/opentherm_gw/services.yaml new file mode 100644 index 00000000000..df08ccaa4f9 --- /dev/null +++ b/homeassistant/components/opentherm_gw/services.yaml @@ -0,0 +1,81 @@ +# Describes the format for available opentherm_gw services + +reset_gateway: + description: Reset the OpenTherm Gateway. + +set_clock: + description: Set the clock and day of the week on the connected thermostat. + fields: + date: + description: Optional date from which the day of the week will be extracted. Defaults to today. + example: '2018-10-23' + time: + description: Optional time in 24h format which will be provided to the thermostat. Defaults to the current time. + example: '19:34' + +set_control_setpoint: + description: > + Set the central heating control setpoint override on the gateway. + You will only need this if you are writing your own software thermostat. + fields: + temperature: + description: > + The central heating setpoint to set on the gateway. + Values between 0 and 90 are accepted, but not all boilers support this range. + A value of 0 disables the central heating setpoint override. + example: '37.5' + +set_gpio_mode: + description: Change the function of the GPIO pins of the gateway. + fields: + id: + description: The ID of the GPIO pin. Either "A" or "B". + example: 'B' + mode: + description: > + Mode to set on the GPIO pin. Values 0 through 6 are accepted for both GPIOs, 7 is only accepted for GPIO "B". + See https://www.home-assistant.io/components/opentherm_gw/#gpio-modes for an explanation of the values. + example: '5' + +set_led_mode: + description: Change the function of the LEDs of the gateway. + fields: + id: + description: The ID of the LED. Possible values are "A" through "F". + example: 'C' + mode: + description: > + The function to assign to the LED. One of "R", "X", "T", "B", "O", "F", "H", "W", "C", "E", "M" or "P". + See https://www.home-assistant.io/components/opentherm_gw/#led-modes for an explanation of the values. + example: 'F' + +set_max_modulation: + description: > + Override the maximum relative modulation level. + You will only need this if you are writing your own software thermostat. + fields: + level: + description: > + The modulation level to provide to the gateway. + Values between 0 and 100 will set the modulation level. + Provide a value of -1 to clear the override and forward the value from the thermostat again. + example: '42' + +set_outside_temperature: + description: > + Provide an outside temperature to the thermostat. + If your thermostat is unable to display an outside temperature and does not support OTC (Outside Temperature Correction), this has no effect. + fields: + temperature: + description: > + The temperature to provide to the thermostat. + Values between -40.0 and 64.0 will be accepted, but not all thermostats can display the full range. + Any value above 64.0 will clear a previously configured value (suggestion: 99) + example: '-2.3' + +set_setback_temperature: + description: Configure the setback temperature to be used with the GPIO away mode function. + fields: + temperature: + description: The setback temperature to configure on the gateway. Values between 0.0 and 30.0 are accepted. + example: '16.0' diff --git a/requirements_all.txt b/requirements_all.txt index 6dd7488a2e9..3a029503935 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1038,7 +1038,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==0.2b1 +pyotgw==0.3b0 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From 1578187376bde6c83824b6b9ee821967f937935a Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Wed, 31 Oct 2018 04:52:21 -0700 Subject: [PATCH 144/230] Change cv.string to [cv.string] (#18050) --- homeassistant/components/nest/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index c66abf1a8bd..bb0e6247de3 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -55,7 +55,7 @@ SENSOR_SCHEMA = vol.Schema({ AWAY_SCHEMA = vol.Schema({ vol.Required(ATTR_HOME_MODE): vol.In([HOME_MODE_AWAY, HOME_MODE_HOME]), - vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, cv.string), + vol.Optional(ATTR_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_TRIP_ID): cv.string, vol.Optional(ATTR_ETA): cv.time_period, vol.Optional(ATTR_ETA_WINDOW): cv.time_period @@ -65,7 +65,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_SECRET): cv.string, - vol.Optional(CONF_STRUCTURE): vol.All(cv.ensure_list, cv.string), + vol.Optional(CONF_STRUCTURE): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_SENSORS): SENSOR_SCHEMA, vol.Optional(CONF_BINARY_SENSORS): SENSOR_SCHEMA }) From b763c0f902a020e476a94c6cea1c0cabc144d545 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 31 Oct 2018 13:49:54 +0100 Subject: [PATCH 145/230] Extract ruamel.yaml to util with secrets, lovelace ws decorators (#17958) * Extract ruamel.yaml to util, ws decorators, secrets * lint * Extend SafeConstructor Somehow my last commit is gone after rebase... * lint * Woof... * Woof woof... * Cleanup type hints * Update homeassistant/scripts/check_config.py * lint * typing --- homeassistant/components/lovelace/__init__.py | 393 +++++------------- homeassistant/package_constraints.txt | 1 + homeassistant/scripts/check_config.py | 6 +- homeassistant/util/ruamel_yaml.py | 134 ++++++ homeassistant/util/yaml.py | 6 +- requirements_all.txt | 4 +- requirements_test_all.txt | 3 - setup.py | 1 + tests/components/lovelace/test_init.py | 131 +++--- tests/util/test_ruamel_yaml.py | 158 +++++++ 10 files changed, 452 insertions(+), 385 deletions(-) create mode 100644 homeassistant/util/ruamel_yaml.py create mode 100644 tests/util/test_ruamel_yaml.py diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index ec131c8a4d9..d21dc3867d8 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,19 +1,17 @@ """Lovelace UI.""" import logging import uuid -import os -from os import O_CREAT, O_TRUNC, O_WRONLY -from collections import OrderedDict +from functools import wraps from typing import Dict, List, Union import voluptuous as vol +import homeassistant.util.ruamel_yaml as yaml from homeassistant.components import websocket_api from homeassistant.exceptions import HomeAssistantError _LOGGER = logging.getLogger(__name__) DOMAIN = 'lovelace' -REQUIREMENTS = ['ruamel.yaml==0.15.72'] LOVELACE_CONFIG_FILE = 'ui-lovelace.yaml' JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name @@ -77,10 +75,6 @@ SCHEMA_DELETE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ }) -class WriteError(HomeAssistantError): - """Error writing the data.""" - - class CardNotFoundError(HomeAssistantError): """Card not found in data.""" @@ -89,87 +83,25 @@ class ViewNotFoundError(HomeAssistantError): """View not found in data.""" -class UnsupportedYamlError(HomeAssistantError): - """Unsupported YAML.""" - - class DuplicateIdError(HomeAssistantError): """Duplicate ID's.""" -def save_yaml(fname: str, data: JSON_TYPE): - """Save a YAML file.""" - from ruamel.yaml import YAML - from ruamel.yaml.error import YAMLError - yaml = YAML(typ='rt') - yaml.indent(sequence=4, offset=2) - tmp_fname = fname + "__TEMP__" - try: - with open(os.open(tmp_fname, O_WRONLY | O_CREAT | O_TRUNC, 0o644), - 'w', encoding='utf-8') as temp_file: - yaml.dump(data, temp_file) - os.replace(tmp_fname, fname) - except YAMLError as exc: - _LOGGER.error(str(exc)) - raise HomeAssistantError(exc) - except OSError as exc: - _LOGGER.exception('Saving YAML file %s failed: %s', fname, exc) - raise WriteError(exc) - finally: - if os.path.exists(tmp_fname): - try: - os.remove(tmp_fname) - except OSError as exc: - # If we are cleaning up then something else went wrong, so - # we should suppress likely follow-on errors in the cleanup - _LOGGER.error("YAML replacement cleanup failed: %s", exc) - - -def _yaml_unsupported(loader, node): - raise UnsupportedYamlError( - 'Unsupported YAML, you can not use {} in ui-lovelace.yaml' - .format(node.tag)) - - -def load_yaml(fname: str) -> JSON_TYPE: - """Load a YAML file.""" - from ruamel.yaml import YAML - from ruamel.yaml.constructor import RoundTripConstructor - from ruamel.yaml.error import YAMLError - - RoundTripConstructor.add_constructor(None, _yaml_unsupported) - - yaml = YAML(typ='rt') - - try: - with open(fname, encoding='utf-8') as conf_file: - # If configuration file is empty YAML returns None - # We convert that to an empty dict - return yaml.load(conf_file) or OrderedDict() - except YAMLError as exc: - _LOGGER.error("YAML error in %s: %s", fname, exc) - raise HomeAssistantError(exc) - except UnicodeDecodeError as exc: - _LOGGER.error("Unable to read file %s: %s", fname, exc) - raise HomeAssistantError(exc) - - def load_config(fname: str) -> JSON_TYPE: """Load a YAML file.""" - return load_yaml(fname) + return yaml.load_yaml(fname, False) -def migrate_config(fname: str) -> JSON_TYPE: - """Load a YAML file and adds id to views and cards if not present.""" - config = load_yaml(fname) - # Check if all views and cards have a unique id or else add one +def migrate_config(fname: str) -> None: + """Add id to views and cards if not present and check duplicates.""" + config = yaml.load_yaml(fname, True) updated = False seen_card_ids = set() seen_view_ids = set() index = 0 for view in config.get('views', []): - view_id = view.get('id') - if view_id is None: + view_id = str(view.get('id', '')) + if not view_id: updated = True view.insert(0, 'id', index, comment="Automatically created id") @@ -179,8 +111,8 @@ def migrate_config(fname: str) -> JSON_TYPE: 'ID `{}` has multiple occurances in views'.format(view_id)) seen_view_ids.add(view_id) for card in view.get('cards', []): - card_id = card.get('id') - if card_id is None: + card_id = str(card.get('id', '')) + if not card_id: updated = True card.insert(0, 'id', uuid.uuid4().hex, comment="Automatically created id") @@ -192,48 +124,22 @@ def migrate_config(fname: str) -> JSON_TYPE: seen_card_ids.add(card_id) index += 1 if updated: - save_yaml(fname, config) - return config - - -def object_to_yaml(data: JSON_TYPE) -> str: - """Create yaml string from object.""" - from ruamel.yaml import YAML - from ruamel.yaml.error import YAMLError - from ruamel.yaml.compat import StringIO - yaml = YAML(typ='rt') - yaml.indent(sequence=4, offset=2) - stream = StringIO() - try: - yaml.dump(data, stream) - return stream.getvalue() - except YAMLError as exc: - _LOGGER.error("YAML error: %s", exc) - raise HomeAssistantError(exc) - - -def yaml_to_object(data: str) -> JSON_TYPE: - """Create object from yaml string.""" - from ruamel.yaml import YAML - from ruamel.yaml.error import YAMLError - yaml = YAML(typ='rt') - try: - return yaml.load(data) - except YAMLError as exc: - _LOGGER.error("YAML error: %s", exc) - raise HomeAssistantError(exc) + yaml.save_yaml(fname, config) def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\ -> JSON_TYPE: """Load a specific card config for id.""" - config = load_yaml(fname) + round_trip = data_format == FORMAT_YAML + + config = yaml.load_yaml(fname, round_trip) + for view in config.get('views', []): for card in view.get('cards', []): - if str(card.get('id')) != card_id: + if str(card.get('id', '')) != card_id: continue if data_format == FORMAT_YAML: - return object_to_yaml(card) + return yaml.object_to_yaml(card) return card raise CardNotFoundError( @@ -241,17 +147,17 @@ def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\ def update_card(fname: str, card_id: str, card_config: str, - data_format: str = FORMAT_YAML): + data_format: str = FORMAT_YAML) -> None: """Save a specific card config for id.""" - config = load_yaml(fname) + config = yaml.load_yaml(fname, True) for view in config.get('views', []): for card in view.get('cards', []): - if str(card.get('id')) != card_id: + if str(card.get('id', '')) != card_id: continue if data_format == FORMAT_YAML: - card_config = yaml_to_object(card_config) + card_config = yaml.yaml_to_object(card_config) card.update(card_config) - save_yaml(fname, config) + yaml.save_yaml(fname, config) return raise CardNotFoundError( @@ -259,39 +165,39 @@ def update_card(fname: str, card_id: str, card_config: str, def add_card(fname: str, view_id: str, card_config: str, - position: int = None, data_format: str = FORMAT_YAML): + position: int = None, data_format: str = FORMAT_YAML) -> None: """Add a card to a view.""" - config = load_yaml(fname) + config = yaml.load_yaml(fname, True) for view in config.get('views', []): - if str(view.get('id')) != view_id: + if str(view.get('id', '')) != view_id: continue cards = view.get('cards', []) if data_format == FORMAT_YAML: - card_config = yaml_to_object(card_config) + card_config = yaml.yaml_to_object(card_config) if position is None: cards.append(card_config) else: cards.insert(position, card_config) - save_yaml(fname, config) + yaml.save_yaml(fname, config) return raise ViewNotFoundError( "View with ID: {} was not found in {}.".format(view_id, fname)) -def move_card(fname: str, card_id: str, position: int = None): +def move_card(fname: str, card_id: str, position: int = None) -> None: """Move a card to a different position.""" if position is None: raise HomeAssistantError('Position is required if view is not\ specified.') - config = load_yaml(fname) + config = yaml.load_yaml(fname, True) for view in config.get('views', []): for card in view.get('cards', []): - if str(card.get('id')) != card_id: + if str(card.get('id', '')) != card_id: continue cards = view.get('cards') cards.insert(position, cards.pop(cards.index(card))) - save_yaml(fname, config) + yaml.save_yaml(fname, config) return raise CardNotFoundError( @@ -299,14 +205,14 @@ def move_card(fname: str, card_id: str, position: int = None): def move_card_view(fname: str, card_id: str, view_id: str, - position: int = None): + position: int = None) -> None: """Move a card to a different view.""" - config = load_yaml(fname) + config = yaml.load_yaml(fname, True) for view in config.get('views', []): - if str(view.get('id')) == view_id: + if str(view.get('id', '')) == view_id: destination = view.get('cards') for card in view.get('cards'): - if str(card.get('id')) != card_id: + if str(card.get('id', '')) != card_id: continue origin = view.get('cards') card_to_move = card @@ -325,19 +231,19 @@ def move_card_view(fname: str, card_id: str, view_id: str, else: destination.insert(position, card_to_move) - save_yaml(fname, config) + yaml.save_yaml(fname, config) -def delete_card(fname: str, card_id: str, position: int = None): +def delete_card(fname: str, card_id: str, position: int = None) -> None: """Delete a card from view.""" - config = load_yaml(fname) + config = yaml.load_yaml(fname, True) for view in config.get('views', []): for card in view.get('cards', []): - if str(card.get('id')) != card_id: + if str(card.get('id', '')) != card_id: continue cards = view.get('cards') cards.pop(cards.index(card)) - save_yaml(fname, config) + yaml.save_yaml(fname, config) return raise CardNotFoundError( @@ -382,193 +288,100 @@ async def async_setup(hass, config): return True +def handle_yaml_errors(func): + """Handle error with websocket calls.""" + @wraps(func) + async def send_with_error_handling(hass, connection, msg): + error = None + try: + result = await func(hass, connection, msg) + message = websocket_api.result_message( + msg['id'], result + ) + except FileNotFoundError: + error = ('file_not_found', + 'Could not find ui-lovelace.yaml in your config dir.') + except yaml.UnsupportedYamlError as err: + error = 'unsupported_error', str(err) + except yaml.WriteError as err: + error = 'write_error', str(err) + except CardNotFoundError as err: + error = 'card_not_found', str(err) + except ViewNotFoundError as err: + error = 'view_not_found', str(err) + except HomeAssistantError as err: + error = 'error', str(err) + + if error is not None: + message = websocket_api.error_message(msg['id'], *error) + + connection.send_message(message) + + return send_with_error_handling + + @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_config(hass, connection, msg): """Send lovelace UI config over websocket config.""" - error = None - try: - config = await hass.async_add_executor_job( - load_config, hass.config.path(LOVELACE_CONFIG_FILE)) - message = websocket_api.result_message( - msg['id'], config - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except HomeAssistantError as err: - error = 'load_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + load_config, hass.config.path(LOVELACE_CONFIG_FILE)) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_migrate_config(hass, connection, msg): """Migrate lovelace UI config.""" - error = None - try: - config = await hass.async_add_executor_job( - migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) - message = websocket_api.result_message( - msg['id'], config - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except HomeAssistantError as err: - error = 'load_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_get_card(hass, connection, msg): """Send lovelace card config over websocket config.""" - error = None - try: - card = await hass.async_add_executor_job( - get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], - msg.get('format', FORMAT_YAML)) - message = websocket_api.result_message( - msg['id'], card - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except CardNotFoundError as err: - error = 'card_not_found', str(err) - except HomeAssistantError as err: - error = 'load_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], + msg.get('format', FORMAT_YAML)) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_update_card(hass, connection, msg): """Receive lovelace card config over websocket and save.""" - error = None - try: - await hass.async_add_executor_job( - update_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML)) - message = websocket_api.result_message( - msg['id'] - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except CardNotFoundError as err: - error = 'card_not_found', str(err) - except HomeAssistantError as err: - error = 'save_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + update_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML)) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_add_card(hass, connection, msg): """Add new card to view over websocket and save.""" - error = None - try: - await hass.async_add_executor_job( - add_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['view_id'], msg['card_config'], msg.get('position'), - msg.get('format', FORMAT_YAML)) - message = websocket_api.result_message( - msg['id'] - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except ViewNotFoundError as err: - error = 'view_not_found', str(err) - except HomeAssistantError as err: - error = 'save_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + add_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_id'], msg['card_config'], msg.get('position'), + msg.get('format', FORMAT_YAML)) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_move_card(hass, connection, msg): """Move card to different position over websocket and save.""" - error = None - try: - if 'new_view_id' in msg: - await hass.async_add_executor_job( - move_card_view, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id'], msg['new_view_id'], msg.get('new_position')) - else: - await hass.async_add_executor_job( - move_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id'], msg.get('new_position')) + if 'new_view_id' in msg: + return await hass.async_add_executor_job( + move_card_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg['new_view_id'], msg.get('new_position')) - message = websocket_api.result_message( - msg['id'] - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except ViewNotFoundError as err: - error = 'view_not_found', str(err) - except CardNotFoundError as err: - error = 'card_not_found', str(err) - except HomeAssistantError as err: - error = 'save_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + move_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id'], msg.get('new_position')) @websocket_api.async_response +@handle_yaml_errors async def websocket_lovelace_delete_card(hass, connection, msg): """Delete card from lovelace over websocket and save.""" - error = None - try: - await hass.async_add_executor_job( - delete_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id']) - message = websocket_api.result_message( - msg['id'] - ) - except FileNotFoundError: - error = ('file_not_found', - 'Could not find ui-lovelace.yaml in your config dir.') - except UnsupportedYamlError as err: - error = 'unsupported_error', str(err) - except CardNotFoundError as err: - error = 'card_not_found', str(err) - except HomeAssistantError as err: - error = 'save_error', str(err) - - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - - connection.send_message(message) + return await hass.async_add_executor_job( + delete_card, hass.config.path(LOVELACE_CONFIG_FILE), + msg['card_id']) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 193bb42dba0..531c4c2e8a6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,6 +11,7 @@ pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 requests==2.20.0 +ruamel.yaml==0.15.72 voluptuous==0.11.5 voluptuous-serialize==2.0.0 diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 94add794651..1e77454a8d5 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__) MOCKS = { 'load': ("homeassistant.util.yaml.load_yaml", yaml.load_yaml), 'load*': ("homeassistant.config.load_yaml", yaml.load_yaml), - 'secrets': ("homeassistant.util.yaml._secret_yaml", yaml._secret_yaml), + 'secrets': ("homeassistant.util.yaml.secret_yaml", yaml.secret_yaml), } SILENCE = ( 'homeassistant.scripts.check_config.yaml.clear_secret_cache', @@ -198,7 +198,7 @@ def check(config_dir, secrets=False): if secrets: # Ensure !secrets point to the patched function - yaml.yaml.SafeLoader.add_constructor('!secret', yaml._secret_yaml) + yaml.yaml.SafeLoader.add_constructor('!secret', yaml.secret_yaml) try: hass = core.HomeAssistant() @@ -223,7 +223,7 @@ def check(config_dir, secrets=False): pat.stop() if secrets: # Ensure !secrets point to the original function - yaml.yaml.SafeLoader.add_constructor('!secret', yaml._secret_yaml) + yaml.yaml.SafeLoader.add_constructor('!secret', yaml.secret_yaml) bootstrap.clear_secret_cache() return res diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py new file mode 100644 index 00000000000..c3035811344 --- /dev/null +++ b/homeassistant/util/ruamel_yaml.py @@ -0,0 +1,134 @@ +"""ruamel.yaml utility functions.""" +import logging +import os +from os import O_CREAT, O_TRUNC, O_WRONLY +from collections import OrderedDict +from typing import Union, List, Dict + +import ruamel.yaml +from ruamel.yaml import YAML +from ruamel.yaml.constructor import SafeConstructor +from ruamel.yaml.error import YAMLError +from ruamel.yaml.compat import StringIO + +from homeassistant.util.yaml import secret_yaml +from homeassistant.exceptions import HomeAssistantError + +_LOGGER = logging.getLogger(__name__) + +JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name + + +class ExtSafeConstructor(SafeConstructor): + """Extended SafeConstructor.""" + + +class UnsupportedYamlError(HomeAssistantError): + """Unsupported YAML.""" + + +class WriteError(HomeAssistantError): + """Error writing the data.""" + + +def _include_yaml(constructor: SafeConstructor, node: ruamel.yaml.nodes.Node) \ + -> JSON_TYPE: + """Load another YAML file and embeds it using the !include tag. + + Example: + device_tracker: !include device_tracker.yaml + """ + fname = os.path.join(os.path.dirname(constructor.name), node.value) + return load_yaml(fname, False) + + +def _yaml_unsupported(constructor: SafeConstructor, node: + ruamel.yaml.nodes.Node) -> None: + raise UnsupportedYamlError( + 'Unsupported YAML, you can not use {} in {}' + .format(node.tag, os.path.basename(constructor.name))) + + +def object_to_yaml(data: JSON_TYPE) -> str: + """Create yaml string from object.""" + yaml = YAML(typ='rt') + yaml.indent(sequence=4, offset=2) + stream = StringIO() + try: + yaml.dump(data, stream) + result = stream.getvalue() # type: str + return result + except YAMLError as exc: + _LOGGER.error("YAML error: %s", exc) + raise HomeAssistantError(exc) + + +def yaml_to_object(data: str) -> JSON_TYPE: + """Create object from yaml string.""" + yaml = YAML(typ='rt') + try: + result = yaml.load(data) # type: Union[List, Dict, str] + return result + except YAMLError as exc: + _LOGGER.error("YAML error: %s", exc) + raise HomeAssistantError(exc) + + +def load_yaml(fname: str, round_trip: bool = False) -> JSON_TYPE: + """Load a YAML file.""" + if round_trip: + yaml = YAML(typ='rt') + yaml.preserve_quotes = True + else: + ExtSafeConstructor.name = fname + yaml = YAML(typ='safe') + yaml.Constructor = ExtSafeConstructor + + try: + with open(fname, encoding='utf-8') as conf_file: + # If configuration file is empty YAML returns None + # We convert that to an empty dict + return yaml.load(conf_file) or OrderedDict() + except YAMLError as exc: + _LOGGER.error("YAML error in %s: %s", fname, exc) + raise HomeAssistantError(exc) + except UnicodeDecodeError as exc: + _LOGGER.error("Unable to read file %s: %s", fname, exc) + raise HomeAssistantError(exc) + + +def save_yaml(fname: str, data: JSON_TYPE) -> None: + """Save a YAML file.""" + yaml = YAML(typ='rt') + yaml.indent(sequence=4, offset=2) + tmp_fname = fname + "__TEMP__" + try: + file_stat = os.stat(fname) + with open(os.open(tmp_fname, O_WRONLY | O_CREAT | O_TRUNC, + file_stat.st_mode), 'w', encoding='utf-8') \ + as temp_file: + yaml.dump(data, temp_file) + os.replace(tmp_fname, fname) + try: + os.chown(fname, file_stat.st_uid, file_stat.st_gid) + except OSError: + pass + except YAMLError as exc: + _LOGGER.error(str(exc)) + raise HomeAssistantError(exc) + except OSError as exc: + _LOGGER.exception('Saving YAML file %s failed: %s', fname, exc) + raise WriteError(exc) + finally: + if os.path.exists(tmp_fname): + try: + os.remove(tmp_fname) + except OSError as exc: + # If we are cleaning up then something else went wrong, so + # we should suppress likely follow-on errors in the cleanup + _LOGGER.error("YAML replacement cleanup failed: %s", exc) + + +ExtSafeConstructor.add_constructor(u'!secret', secret_yaml) +ExtSafeConstructor.add_constructor(u'!include', _include_yaml) +ExtSafeConstructor.add_constructor(None, _yaml_unsupported) diff --git a/homeassistant/util/yaml.py b/homeassistant/util/yaml.py index 69f83aefad7..c988cb811b2 100644 --- a/homeassistant/util/yaml.py +++ b/homeassistant/util/yaml.py @@ -272,8 +272,8 @@ def _load_secret_yaml(secret_path: str) -> JSON_TYPE: return secrets -def _secret_yaml(loader: SafeLineLoader, - node: yaml.nodes.Node) -> JSON_TYPE: +def secret_yaml(loader: SafeLineLoader, + node: yaml.nodes.Node) -> JSON_TYPE: """Load secrets and embed it into the configuration YAML.""" secret_path = os.path.dirname(loader.name) while True: @@ -322,7 +322,7 @@ yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, yaml.SafeLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG, _construct_seq) yaml.SafeLoader.add_constructor('!env_var', _env_var_yaml) -yaml.SafeLoader.add_constructor('!secret', _secret_yaml) +yaml.SafeLoader.add_constructor('!secret', secret_yaml) yaml.SafeLoader.add_constructor('!include_dir_list', _include_dir_list_yaml) yaml.SafeLoader.add_constructor('!include_dir_merge_list', _include_dir_merge_list_yaml) diff --git a/requirements_all.txt b/requirements_all.txt index 3a029503935..cfc514c72fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -12,6 +12,7 @@ pip>=8.0.3 pytz>=2018.04 pyyaml>=3.13,<4 requests==2.20.0 +ruamel.yaml==0.15.72 voluptuous==0.11.5 voluptuous-serialize==2.0.0 @@ -1313,9 +1314,6 @@ roombapy==1.3.1 # homeassistant.components.switch.rpi_rf # rpi-rf==0.9.6 -# homeassistant.components.lovelace -ruamel.yaml==0.15.72 - # homeassistant.components.media_player.russound_rnet russound==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2e8fc724f2..f8b363b1a9e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -214,9 +214,6 @@ rflink==0.0.37 # homeassistant.components.ring ring_doorbell==0.2.2 -# homeassistant.components.lovelace -ruamel.yaml==0.15.72 - # homeassistant.components.media_player.yamaha rxv==0.5.1 diff --git a/setup.py b/setup.py index 5bca2cc43db..4ade305c590 100755 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ REQUIRES = [ 'pytz>=2018.04', 'pyyaml>=3.13,<4', 'requests==2.20.0', + 'ruamel.yaml==0.15.72', 'voluptuous==0.11.5', 'voluptuous-serialize==2.0.0', ] diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 212bd9e2722..690c4976565 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -1,17 +1,12 @@ """Test the Lovelace initialization.""" -import os -import unittest from unittest.mock import patch -from tempfile import mkdtemp -import pytest from ruamel.yaml import YAML from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.components.websocket_api.const import TYPE_RESULT -from homeassistant.components.lovelace import (load_yaml, migrate_config, - save_yaml, - UnsupportedYamlError) +from homeassistant.components.lovelace import migrate_config +from homeassistant.util.ruamel_yaml import UnsupportedYamlError TEST_YAML_A = """\ title: My Awesome Home @@ -118,63 +113,33 @@ views: """ -class TestYAML(unittest.TestCase): - """Test lovelace.yaml save and load.""" +def test_add_id(): + """Test if id is added.""" + yaml = YAML(typ='rt') - def setUp(self): - """Set up for tests.""" - self.tmp_dir = mkdtemp() - self.yaml = YAML(typ='rt') + fname = "dummy.yaml" + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + migrate_config(fname) - def tearDown(self): - """Clean up after tests.""" - for fname in os.listdir(self.tmp_dir): - os.remove(os.path.join(self.tmp_dir, fname)) - os.rmdir(self.tmp_dir) + result = save_yaml_mock.call_args_list[0][0][1] + assert 'id' in result['views'][0]['cards'][0] + assert 'id' in result['views'][1] - def _path_for(self, leaf_name): - return os.path.join(self.tmp_dir, leaf_name+".yaml") - def test_save_and_load(self): - """Test saving and loading back.""" - fname = self._path_for("test1") - save_yaml(fname, self.yaml.load(TEST_YAML_A)) - data = load_yaml(fname) - assert data == self.yaml.load(TEST_YAML_A) +def test_id_not_changed(): + """Test if id is not changed if already exists.""" + yaml = YAML(typ='rt') - def test_overwrite_and_reload(self): - """Test that we can overwrite an existing file and read back.""" - fname = self._path_for("test3") - save_yaml(fname, self.yaml.load(TEST_YAML_A)) - save_yaml(fname, self.yaml.load(TEST_YAML_B)) - data = load_yaml(fname) - assert data == self.yaml.load(TEST_YAML_B) - - def test_load_bad_data(self): - """Test error from trying to load unserialisable data.""" - fname = self._path_for("test5") - with open(fname, "w") as fh: - fh.write(TEST_BAD_YAML) - with pytest.raises(HomeAssistantError): - load_yaml(fname) - - def test_add_id(self): - """Test if id is added.""" - fname = self._path_for("test6") - with patch('homeassistant.components.lovelace.load_yaml', - return_value=self.yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml'): - data = migrate_config(fname) - assert 'id' in data['views'][0]['cards'][0] - assert 'id' in data['views'][1] - - def test_id_not_changed(self): - """Test if id is not changed if already exists.""" - fname = self._path_for("test7") - with patch('homeassistant.components.lovelace.load_yaml', - return_value=self.yaml.load(TEST_YAML_B)): - data = migrate_config(fname) - assert data == self.yaml.load(TEST_YAML_B) + fname = "dummy.yaml" + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_B)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + migrate_config(fname) + assert save_yaml_mock.call_count == 0 async def test_deprecated_lovelace_ui(hass, hass_ws_client): @@ -231,7 +196,7 @@ async def test_deprecated_lovelace_ui_load_err(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] is False - assert msg['error']['code'] == 'load_error' + assert msg['error']['code'] == 'error' async def test_lovelace_ui(hass, hass_ws_client): @@ -288,7 +253,7 @@ async def test_lovelace_ui_load_err(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] is False - assert msg['error']['code'] == 'load_error' + assert msg['error']['code'] == 'error' async def test_lovelace_ui_load_json_err(hass, hass_ws_client): @@ -316,7 +281,7 @@ async def test_lovelace_get_card(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)): await client.send_json({ 'id': 5, @@ -337,7 +302,7 @@ async def test_lovelace_get_card_not_found(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)): await client.send_json({ 'id': 5, @@ -357,7 +322,7 @@ async def test_lovelace_get_card_bad_yaml(hass, hass_ws_client): await async_setup_component(hass, 'lovelace') client = await hass_ws_client(hass) - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', side_effect=HomeAssistantError): await client.send_json({ 'id': 5, @@ -369,7 +334,7 @@ async def test_lovelace_get_card_bad_yaml(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] is False - assert msg['error']['code'] == 'load_error' + assert msg['error']['code'] == 'error' async def test_lovelace_update_card(hass, hass_ws_client): @@ -378,9 +343,9 @@ async def test_lovelace_update_card(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -404,7 +369,7 @@ async def test_lovelace_update_card_not_found(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)): await client.send_json({ 'id': 5, @@ -426,9 +391,9 @@ async def test_lovelace_update_card_bad_yaml(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.yaml_to_object', + patch('homeassistant.util.ruamel_yaml.yaml_to_object', side_effect=HomeAssistantError): await client.send_json({ 'id': 5, @@ -441,7 +406,7 @@ async def test_lovelace_update_card_bad_yaml(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] is False - assert msg['error']['code'] == 'save_error' + assert msg['error']['code'] == 'error' async def test_lovelace_add_card(hass, hass_ws_client): @@ -450,9 +415,9 @@ async def test_lovelace_add_card(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -476,9 +441,9 @@ async def test_lovelace_add_card_position(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -503,9 +468,9 @@ async def test_lovelace_move_card_position(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -529,9 +494,9 @@ async def test_lovelace_move_card_view(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -555,9 +520,9 @@ async def test_lovelace_move_card_view_position(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, @@ -582,9 +547,9 @@ async def test_lovelace_delete_card(hass, hass_ws_client): client = await hass_ws_client(hass) yaml = YAML(typ='rt') - with patch('homeassistant.components.lovelace.load_yaml', + with patch('homeassistant.util.ruamel_yaml.load_yaml', return_value=yaml.load(TEST_YAML_A)), \ - patch('homeassistant.components.lovelace.save_yaml') \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ as save_yaml_mock: await client.send_json({ 'id': 5, diff --git a/tests/util/test_ruamel_yaml.py b/tests/util/test_ruamel_yaml.py new file mode 100644 index 00000000000..61006c98642 --- /dev/null +++ b/tests/util/test_ruamel_yaml.py @@ -0,0 +1,158 @@ +"""Test Home Assistant ruamel.yaml loader.""" +import os +import unittest +from tempfile import mkdtemp +import pytest + +from ruamel.yaml import YAML + +from homeassistant.exceptions import HomeAssistantError +import homeassistant.util.ruamel_yaml as util_yaml + + +TEST_YAML_A = """\ +title: My Awesome Home +# Include external resources +resources: + - url: /local/my-custom-card.js + type: js + - url: /local/my-webfont.css + type: css + +# Exclude entities from "Unused entities" view +excluded_entities: + - weblink.router +views: + # View tab title. + - title: Example + # Optional unique id for direct access /lovelace/${id} + id: example + # Optional background (overwrites the global background). + background: radial-gradient(crimson, skyblue) + # Each view can have a different theme applied. + theme: dark-mode + # The cards to show on this view. + cards: + # The filter card will filter entities for their state + - type: entity-filter + entities: + - device_tracker.paulus + - device_tracker.anne_there + state_filter: + - 'home' + card: + type: glance + title: People that are home + + # The picture entity card will represent an entity with a picture + - type: picture-entity + image: https://www.home-assistant.io/images/default-social.png + entity: light.bed_light + + # Specify a tab icon if you want the view tab to be an icon. + - icon: mdi:home-assistant + # Title of the view. Will be used as the tooltip for tab icon + title: Second view + cards: + - id: test + type: entities + title: Test card + # Entities card will take a list of entities and show their state. + - type: entities + # Title of the entities card + title: Example + # The entities here will be shown in the same order as specified. + # Each entry is an entity ID or a map with extra options. + entities: + - light.kitchen + - switch.ac + - entity: light.living_room + # Override the name to use + name: LR Lights + + # The markdown card will render markdown text. + - type: markdown + title: Lovelace + content: > + Welcome to your **Lovelace UI**. +""" + +TEST_YAML_B = """\ +title: Home +views: + - title: Dashboard + id: dashboard + icon: mdi:home + cards: + - id: testid + type: vertical-stack + cards: + - type: picture-entity + entity: group.sample + name: Sample + image: /local/images/sample.jpg + tap_action: toggle +""" + +# Test data that can not be loaded as YAML +TEST_BAD_YAML = """\ +title: Home +views: + - title: Dashboard + icon: mdi:home + cards: + - id: testid + type: vertical-stack +""" + +# Test unsupported YAML +TEST_UNSUP_YAML = """\ +title: Home +views: + - title: Dashboard + icon: mdi:home + cards: !include cards.yaml +""" + + +class TestYAML(unittest.TestCase): + """Test lovelace.yaml save and load.""" + + def setUp(self): + """Set up for tests.""" + self.tmp_dir = mkdtemp() + self.yaml = YAML(typ='rt') + + def tearDown(self): + """Clean up after tests.""" + for fname in os.listdir(self.tmp_dir): + os.remove(os.path.join(self.tmp_dir, fname)) + os.rmdir(self.tmp_dir) + + def _path_for(self, leaf_name): + return os.path.join(self.tmp_dir, leaf_name+".yaml") + + def test_save_and_load(self): + """Test saving and loading back.""" + fname = self._path_for("test1") + open(fname, "w+") + util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_A)) + data = util_yaml.load_yaml(fname, True) + assert data == self.yaml.load(TEST_YAML_A) + + def test_overwrite_and_reload(self): + """Test that we can overwrite an existing file and read back.""" + fname = self._path_for("test2") + open(fname, "w+") + util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_A)) + util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_B)) + data = util_yaml.load_yaml(fname, True) + assert data == self.yaml.load(TEST_YAML_B) + + def test_load_bad_data(self): + """Test error from trying to load unserialisable data.""" + fname = self._path_for("test3") + with open(fname, "w") as fh: + fh.write(TEST_BAD_YAML) + with pytest.raises(HomeAssistantError): + util_yaml.load_yaml(fname, True) From 93706fa568dc2cb458397d0b7fee21b17a0a5110 Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Wed, 31 Oct 2018 11:09:13 -0400 Subject: [PATCH 146/230] Report correct thermostat mode to Alexa (#18053) We were erroneously reporting the _previous_ mode. So if the thermostat was off and the user asks, "Alexa, set the thermostat to heat", the thermostat would be set to heat but Alexa would respond, "The thermostat is off." Bug caught by @Thunderbird2086 at https://github.com/home-assistant/home-assistant/pull/17969#issuecomment-434654345 --- homeassistant/components/alexa/smart_home.py | 8 ++++++- tests/components/alexa/test_smart_home.py | 25 +++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 475f507439c..6b747689057 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1839,11 +1839,17 @@ async def async_api_set_thermostat_mode(hass, config, directive, context): climate.ATTR_OPERATION_MODE: ha_mode, } + response = directive.response() await hass.services.async_call( entity.domain, climate.SERVICE_SET_OPERATION_MODE, data, blocking=False, context=context) + response.add_context_property({ + 'name': 'thermostatMode', + 'namespace': 'Alexa.ThermostatController', + 'value': mode, + }) - return directive.response() + return response @HANDLERS.register(('Alexa', 'ReportState')) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 1cf01c6092d..186a35c19ec 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -873,22 +873,41 @@ async def test_thermostat(hass): ) assert msg['event']['payload']['type'] == 'TEMPERATURE_VALUE_OUT_OF_RANGE' - call, _ = await assert_request_calls_service( + # Setting mode, the payload can be an object with a value attribute... + call, msg = await assert_request_calls_service( 'Alexa.ThermostatController', 'SetThermostatMode', 'climate#test_thermostat', 'climate.set_operation_mode', hass, payload={'thermostatMode': {'value': 'HEAT'}} ) assert call.data['operation_mode'] == 'heat' + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'thermostatMode', 'HEAT') - call, _ = await assert_request_calls_service( + call, msg = await assert_request_calls_service( + 'Alexa.ThermostatController', 'SetThermostatMode', + 'climate#test_thermostat', 'climate.set_operation_mode', + hass, + payload={'thermostatMode': {'value': 'COOL'}} + ) + assert call.data['operation_mode'] == 'cool' + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'thermostatMode', 'COOL') + + # ...it can also be just the mode. + call, msg = await assert_request_calls_service( 'Alexa.ThermostatController', 'SetThermostatMode', 'climate#test_thermostat', 'climate.set_operation_mode', hass, payload={'thermostatMode': 'HEAT'} ) - assert call.data['operation_mode'] == 'heat' + properties = _ReportedProperties(msg['context']['properties']) + properties.assert_equal( + 'Alexa.ThermostatController', 'thermostatMode', 'HEAT') + msg = await assert_request_fails( 'Alexa.ThermostatController', 'SetThermostatMode', 'climate#test_thermostat', 'climate.set_operation_mode', From 7363378ac497215e7c09df1d18841d1d33328cb5 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Wed, 31 Oct 2018 21:09:00 +0200 Subject: [PATCH 147/230] Update SMA sensor to pysma 0.2.2 (#17988) --- homeassistant/components/sensor/sma.py | 102 +++++++++---------------- requirements_all.txt | 2 +- 2 files changed, 39 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index dc4b73c6950..4b0c33191dc 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -12,14 +12,14 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL, - CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_SSL, CONF_VERIFY_SSL, + EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval -REQUIREMENTS = ['pysma==0.2'] +REQUIREMENTS = ['pysma==0.2.2'] _LOGGER = logging.getLogger(__name__) @@ -30,35 +30,28 @@ CONF_KEY = 'key' CONF_SENSORS = 'sensors' CONF_UNIT = 'unit' -GROUP_INSTALLER = 'installer' -GROUP_USER = 'user' -GROUPS = [GROUP_USER, GROUP_INSTALLER] - -SENSOR_OPTIONS = [ - 'current_consumption', - 'current_power', - 'total_consumption', - 'total_yield', -] +GROUPS = ['user', 'installer'] def _check_sensor_schema(conf): """Check sensors and attributes are valid.""" + import pysma + valid = list(conf[CONF_CUSTOM].keys()) - valid.extend(SENSOR_OPTIONS) - for sensor, attrs in conf[CONF_SENSORS].items(): - if sensor not in valid: - raise vol.Invalid("{} does not exist".format(sensor)) + valid.extend([s.name for s in pysma.SENSORS]) + for sname, attrs in conf[CONF_SENSORS].items(): + if sname not in valid: + raise vol.Invalid("{} does not exist".format(sname)) for attr in attrs: if attr in valid: continue - raise vol.Invalid("{} does not exist [{}]".format(attr, sensor)) + raise vol.Invalid("{} does not exist [{}]".format(attr, sname)) return conf CUSTOM_SCHEMA = vol.Any({ vol.Required(CONF_KEY): - vol.All(cv.string, vol.Length(min=13, max=13)), + vol.All(cv.string, vol.Length(min=13, max=15)), vol.Required(CONF_UNIT): cv.string, vol.Optional(CONF_FACTOR, default=1): vol.Coerce(float), }) @@ -80,37 +73,26 @@ async def async_setup_platform( """Set up SMA WebConnect sensor.""" import pysma - # Sensor_defs from the library - sensor_defs = dict(zip(SENSOR_OPTIONS, [ - (pysma.KEY_CURRENT_CONSUMPTION_W, 'W', 1), - (pysma.KEY_CURRENT_POWER_W, 'W', 1), - (pysma.KEY_TOTAL_CONSUMPTION_KWH, 'kWh', 1000), - (pysma.KEY_TOTAL_YIELD_KWH, 'kWh', 1000)])) - # Sensor_defs from the custom config for name, prop in config[CONF_CUSTOM].items(): - if name in sensor_defs: - _LOGGER.warning("Custom sensor %s replace built-in sensor", name) - sensor_defs[name] = (prop['key'], prop['unit'], prop['factor']) + n_s = pysma.Sensor(name, prop['key'], prop['unit'], prop['factor']) + pysma.add_sensor(n_s) # Prepare all HASS sensor entities hass_sensors = [] used_sensors = [] for name, attr in config[CONF_SENSORS].items(): - hass_sensors.append(SMAsensor(name, attr, sensor_defs)) + sub_sensors = [pysma.get_sensor(s) for s in attr] + hass_sensors.append(SMAsensor(pysma.get_sensor(name), sub_sensors)) used_sensors.append(name) used_sensors.extend(attr) - # Remove sensor_defs not in use - sensor_defs = {name: val for name, val in sensor_defs.items() - if name in used_sensors} - async_add_entities(hass_sensors) + used_sensors = [pysma.get_sensor(s) for s in set(used_sensors)] # Init the SMA interface session = async_get_clientsession(hass, verify_ssl=config[CONF_VERIFY_SSL]) - grp = {GROUP_INSTALLER: pysma.GROUP_INSTALLER, - GROUP_USER: pysma.GROUP_USER}[config[CONF_GROUP]] + grp = config[CONF_GROUP] url = "http{}://{}".format( "s" if config[CONF_SSL] else "", config[CONF_HOST]) @@ -124,10 +106,6 @@ async def async_setup_platform( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session) - # Read SMA values periodically & update sensors - names_to_query = list(sensor_defs.keys()) - keys_to_query = [sensor_defs[name][0] for name in names_to_query] - backoff = 0 async def async_sma(event): @@ -137,17 +115,14 @@ async def async_setup_platform( backoff -= 1 return - values = await sma.read(keys_to_query) + values = await sma.read(used_sensors) if values is None: - backoff = 3 + backoff = 10 return - values = [0 if val is None else val for val in values] - res = dict(zip(names_to_query, values)) - res = {key: val // sensor_defs[key][2] for key, val in res.items()} - _LOGGER.debug("Update sensors %s %s %s", keys_to_query, values, res) + tasks = [] for sensor in hass_sensors: - task = sensor.async_update_values(res) + task = sensor.async_update_values() if task: tasks.append(task) if tasks: @@ -160,18 +135,18 @@ async def async_setup_platform( class SMAsensor(Entity): """Representation of a SMA sensor.""" - def __init__(self, sensor_name, attr, sensor_defs): + def __init__(self, pysma_sensor, sub_sensors): """Initialize the sensor.""" - self._name = sensor_name - self._key, self._unit_of_measurement, _ = sensor_defs[sensor_name] - self._state = None - self._sensor_defs = sensor_defs - self._attr = {att: "" for att in attr} + self._sensor = pysma_sensor + self._sub_sensors = sub_sensors + + self._attr = {s.name: "" for s in sub_sensors} + self._state = self._sensor.value @property def name(self): """Return the name of the sensor.""" - return self._name + return self._sensor.name @property def state(self): @@ -181,7 +156,7 @@ class SMAsensor(Entity): @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return self._unit_of_measurement + return self._sensor.unit @property def device_state_attributes(self): @@ -193,19 +168,18 @@ class SMAsensor(Entity): """SMA sensors are updated & don't poll.""" return False - def async_update_values(self, key_values): - """Update this sensor using the data.""" + def async_update_values(self): + """Update this sensor.""" update = False - for key, val in self._attr.items(): - newval = '{} {}'.format(key_values[key], self._sensor_defs[key][1]) - if val != newval: + for sens in self._sub_sensors: + newval = '{} {}'.format(sens.value, sens.unit) + if self._attr[sens.name] != newval: update = True - self._attr[key] = newval + self._attr[sens.name] = newval - new_state = key_values[self._name] - if new_state != self._state: + if self._sensor.value != self._state: update = True - self._state = new_state + self._state = self._sensor.value return self.async_update_ha_state() if update else None diff --git a/requirements_all.txt b/requirements_all.txt index cfc514c72fb..45305c26e3b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1084,7 +1084,7 @@ pysesame==0.1.0 pysher==1.0.4 # homeassistant.components.sensor.sma -pysma==0.2 +pysma==0.2.2 # homeassistant.components.device_tracker.snmp # homeassistant.components.sensor.snmp From 145677ed750e106c8a6c99d04c2f352096dfb262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Wed, 31 Oct 2018 21:39:13 +0100 Subject: [PATCH 148/230] Mill, support opeation mode (#18059) --- homeassistant/components/climate/mill.py | 28 +++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/climate/mill.py index 57d14126a93..3e8955e2be6 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -10,9 +10,9 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, DOMAIN, PLATFORM_SCHEMA, + ClimateDevice, DOMAIN, PLATFORM_SCHEMA, STATE_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, - SUPPORT_ON_OFF) + SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE) from homeassistant.const import ( ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, STATE_ON, STATE_OFF, TEMP_CELSIUS) @@ -32,7 +32,8 @@ MIN_TEMP = 5 SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature' SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_FAN_MODE | SUPPORT_ON_OFF) + SUPPORT_FAN_MODE | SUPPORT_ON_OFF | + SUPPORT_OPERATION_MODE) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, @@ -167,6 +168,16 @@ class MillHeater(ClimateDevice): """Return the maximum temperature.""" return MAX_TEMP + @property + def current_operation(self): + """Return current operation.""" + return STATE_HEAT if self.is_on else STATE_OFF + + @property + def operation_list(self): + """List of available operation modes.""" + return [STATE_HEAT, STATE_OFF] + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) @@ -194,3 +205,14 @@ class MillHeater(ClimateDevice): async def async_update(self): """Retrieve latest state.""" self._heater = await self._conn.update_device(self._heater.device_id) + + async def async_set_operation_mode(self, operation_mode): + """Set operation mode.""" + if operation_mode == STATE_HEAT: + await self.async_turn_on() + elif operation_mode == STATE_OFF: + await self.async_turn_off() + else: + _LOGGER.error("Unrecognized operation mode: %s", operation_mode) + return + self.schedule_update_ha_state() From a9140dc8f5950fc0a79b5f0726ce53b5ab3a4ee6 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 31 Oct 2018 22:38:04 +0100 Subject: [PATCH 149/230] deCONZ - retry if setup fails (#17772) * Make component retry if setup fails * Improve overall test coverage --- .coveragerc | 3 - .../components/binary_sensor/deconz.py | 11 +- homeassistant/components/cover/deconz.py | 13 +- homeassistant/components/deconz/__init__.py | 162 +++----------- .../components/deconz/config_flow.py | 6 +- homeassistant/components/deconz/const.py | 3 + homeassistant/components/deconz/gateway.py | 165 +++++++++++++++ homeassistant/components/light/deconz.py | 16 +- homeassistant/components/scene/deconz.py | 10 +- homeassistant/components/sensor/deconz.py | 18 +- homeassistant/components/switch/deconz.py | 12 +- tests/components/binary_sensor/test_deconz.py | 85 ++++++-- tests/components/cover/test_deconz.py | 101 +++++++-- tests/components/deconz/test_config_flow.py | 27 ++- tests/components/deconz/test_init.py | 198 ++++++------------ tests/components/light/test_deconz.py | 148 ++++++++++--- tests/components/scene/test_deconz.py | 77 +++++-- tests/components/sensor/test_deconz.py | 133 ++++++++---- tests/components/switch/test_deconz.py | 110 +++++++--- 19 files changed, 823 insertions(+), 475 deletions(-) create mode 100644 homeassistant/components/deconz/gateway.py diff --git a/.coveragerc b/.coveragerc index 72451dab531..5727ec1d43a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -76,9 +76,6 @@ omit = homeassistant/components/daikin.py homeassistant/components/*/daikin.py - homeassistant/components/deconz/* - homeassistant/components/*/deconz.py - homeassistant/components/digital_ocean.py homeassistant/components/*/digital_ocean.py diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py index b0728ad167c..fe00402ec95 100644 --- a/homeassistant/components/binary_sensor/deconz.py +++ b/homeassistant/components/binary_sensor/deconz.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.deconz/ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.deconz.const import ( ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN) + DECONZ_DOMAIN) from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -36,10 +36,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzBinarySensor(sensor)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor)) - async_add_sensor(hass.data[DATA_DECONZ].sensors.values()) + async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values()) class DeconzBinarySensor(BinarySensorDevice): @@ -52,7 +52,8 @@ class DeconzBinarySensor(BinarySensorDevice): async def async_added_to_hass(self): """Subscribe sensors events.""" self._sensor.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._sensor.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect sensor object when removed.""" @@ -127,7 +128,7 @@ class DeconzBinarySensor(BinarySensorDevice): self._sensor.uniqueid.count(':') != 7): return None serial = self._sensor.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, diff --git a/homeassistant/components/cover/deconz.py b/homeassistant/components/cover/deconz.py index 89b29aa10a5..cd5871e153a 100644 --- a/homeassistant/components/cover/deconz.py +++ b/homeassistant/components/cover/deconz.py @@ -5,8 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.deconz/ """ from homeassistant.components.deconz.const import ( - COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, - DATA_DECONZ_UNSUB, DECONZ_DOMAIN, WINDOW_COVERS) + COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, WINDOW_COVERS) from homeassistant.components.cover import ( ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, SUPPORT_SET_POSITION) @@ -42,10 +41,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzCover(light)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover)) - async_add_cover(hass.data[DATA_DECONZ].lights.values()) + async_add_cover(hass.data[DATA_DECONZ].api.lights.values()) class DeconzCover(CoverDevice): @@ -62,7 +61,8 @@ class DeconzCover(CoverDevice): async def async_added_to_hass(self): """Subscribe to covers events.""" self._cover.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._cover.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._cover.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect cover object when removed.""" @@ -103,7 +103,6 @@ class DeconzCover(CoverDevice): return 'damper' if self._cover.type in WINDOW_COVERS: return 'window' - return None @property def supported_features(self): @@ -151,7 +150,7 @@ class DeconzCover(CoverDevice): self._cover.uniqueid.count(':') != 7): return None serial = self._cover.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 648aebc8c89..c314a1191db 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -8,21 +8,15 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_API_KEY, CONF_EVENT, CONF_HOST, - CONF_ID, CONF_PORT, EVENT_HOMEASSISTANT_STOP) -from homeassistant.core import EventOrigin, callback -from homeassistant.helpers import aiohttp_client, config_validation as cv + CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) -from homeassistant.util import slugify from homeassistant.util.json import load_json # Loading the config flow file will register the flow from .config_flow import configured_hosts -from .const import ( - CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER) +from .const import CONFIG_FILE, DOMAIN, _LOGGER +from .gateway import DeconzGateway REQUIREMENTS = ['pydeconz==47'] @@ -80,61 +74,26 @@ async def async_setup_entry(hass, config_entry): Load config, group, light and sensor data for server information. Start websocket for push notification of state changes from deCONZ. """ - from pydeconz import DeconzSession if DOMAIN in hass.data: _LOGGER.error( "Config entry failed since one deCONZ instance already exists") return False - @callback - def async_add_device_callback(device_type, device): - """Handle event of new device creation in deCONZ.""" - if not isinstance(device, list): - device = [device] - async_dispatcher_send( - hass, 'deconz_new_{}'.format(device_type), device) + gateway = DeconzGateway(hass, config_entry) - session = aiohttp_client.async_get_clientsession(hass) - deconz = DeconzSession(hass.loop, session, **config_entry.data, - async_add_device=async_add_device_callback) - result = await deconz.async_load_parameters() + hass.data[DOMAIN] = gateway - if result is False: + if not await gateway.async_setup(): return False - hass.data[DOMAIN] = deconz - hass.data[DATA_DECONZ_ID] = {} - hass.data[DATA_DECONZ_EVENT] = [] - hass.data[DATA_DECONZ_UNSUB] = [] - - for component in SUPPORTED_PLATFORMS: - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, component)) - - @callback - def async_add_remote(sensors): - """Set up remote from deCONZ.""" - from pydeconz.sensor import SWITCH as DECONZ_REMOTE - allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True) - for sensor in sensors: - if sensor.type in DECONZ_REMOTE and \ - not (not allow_clip_sensor and sensor.type.startswith('CLIP')): - hass.data[DATA_DECONZ_EVENT].append(DeconzEvent(hass, sensor)) - hass.data[DATA_DECONZ_UNSUB].append( - async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_remote)) - - async_add_remote(deconz.sensors.values()) - - deconz.start() - device_registry = await \ hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, - connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)}, - identifiers={(DOMAIN, deconz.config.bridgeid)}, - manufacturer='Dresden Elektronik', model=deconz.config.modelid, - name=deconz.config.name, sw_version=deconz.config.swversion) + connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)}, + identifiers={(DOMAIN, gateway.api.config.bridgeid)}, + manufacturer='Dresden Elektronik', model=gateway.api.config.modelid, + name=gateway.api.config.name, sw_version=gateway.api.config.swversion) async def async_configure(call): """Set attribute of device in deCONZ. @@ -155,121 +114,66 @@ async def async_setup_entry(hass, config_entry): field = call.data.get(SERVICE_FIELD, '') entity_id = call.data.get(SERVICE_ENTITY) data = call.data.get(SERVICE_DATA) - deconz = hass.data[DOMAIN] + gateway = hass.data[DOMAIN] + if entity_id: try: - field = hass.data[DATA_DECONZ_ID][entity_id] + field + field = gateway.deconz_ids[entity_id] + field except KeyError: _LOGGER.error('Could not find the entity %s', entity_id) return - await deconz.async_put_state(field, data) + await gateway.api.async_put_state(field, data) hass.services.async_register( DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA) async def async_refresh_devices(call): """Refresh available devices from deCONZ.""" - deconz = hass.data[DOMAIN] + gateway = hass.data[DOMAIN] - groups = list(deconz.groups.keys()) - lights = list(deconz.lights.keys()) - scenes = list(deconz.scenes.keys()) - sensors = list(deconz.sensors.keys()) + groups = set(gateway.api.groups.keys()) + lights = set(gateway.api.lights.keys()) + scenes = set(gateway.api.scenes.keys()) + sensors = set(gateway.api.sensors.keys()) - if not await deconz.async_load_parameters(): + if not await gateway.api.async_load_parameters(): return - async_add_device_callback( + gateway.async_add_device_callback( 'group', [group - for group_id, group in deconz.groups.items() + for group_id, group in gateway.api.groups.items() if group_id not in groups] ) - async_add_device_callback( + gateway.async_add_device_callback( 'light', [light - for light_id, light in deconz.lights.items() + for light_id, light in gateway.api.lights.items() if light_id not in lights] ) - async_add_device_callback( + gateway.async_add_device_callback( 'scene', [scene - for scene_id, scene in deconz.scenes.items() + for scene_id, scene in gateway.api.scenes.items() if scene_id not in scenes] ) - async_add_device_callback( + gateway.async_add_device_callback( 'sensor', [sensor - for sensor_id, sensor in deconz.sensors.items() + for sensor_id, sensor in gateway.api.sensors.items() if sensor_id not in sensors] ) hass.services.async_register( DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices) - @callback - def deconz_shutdown(event): - """ - Wrap the call to deconz.close. - - Used as an argument to EventBus.async_listen_once - EventBus calls - this method with the event as the first argument, which should not - be passed on to deconz.close. - """ - deconz.close() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) return True async def async_unload_entry(hass, config_entry): """Unload deCONZ config entry.""" - deconz = hass.data.pop(DOMAIN) + gateway = hass.data.pop(DOMAIN) hass.services.async_remove(DOMAIN, SERVICE_DECONZ) - deconz.close() - - for component in SUPPORTED_PLATFORMS: - await hass.config_entries.async_forward_entry_unload( - config_entry, component) - - dispatchers = hass.data[DATA_DECONZ_UNSUB] - for unsub_dispatcher in dispatchers: - unsub_dispatcher() - hass.data[DATA_DECONZ_UNSUB] = [] - - for event in hass.data[DATA_DECONZ_EVENT]: - event.async_will_remove_from_hass() - hass.data[DATA_DECONZ_EVENT].remove(event) - - hass.data[DATA_DECONZ_ID] = [] - - return True - - -class DeconzEvent: - """When you want signals instead of entities. - - Stateless sensors such as remotes are expected to generate an event - instead of a sensor entity in hass. - """ - - def __init__(self, hass, device): - """Register callback that will be used for signals.""" - self._hass = hass - self._device = device - self._device.register_async_callback(self.async_update_callback) - self._event = 'deconz_{}'.format(CONF_EVENT) - self._id = slugify(self._device.name) - - @callback - def async_will_remove_from_hass(self) -> None: - """Disconnect event object when removed.""" - self._device.remove_callback(self.async_update_callback) - self._device = None - - @callback - def async_update_callback(self, reason): - """Fire the event if reason is that state is updated.""" - if reason['state']: - data = {CONF_ID: self._id, CONF_EVENT: self._device.state} - self._hass.bus.async_fire(self._event, data, EventOrigin.remote) + hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH) + return await gateway.async_reset() diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 65fcf51b930..293b6c1b540 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -35,10 +35,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow): self.deconz_config = {} async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - return await self.async_step_init(user_input) - - async def async_step_init(self, user_input=None): """Handle a deCONZ config flow start. Only allows one instance to be set up. @@ -67,7 +63,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow): for bridge in self.bridges: hosts.append(bridge[CONF_HOST]) return self.async_show_form( - step_id='init', + step_id='user', data_schema=vol.Schema({ vol.Required(CONF_HOST): vol.In(hosts) }) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index 5462b5b61b9..ccd1eac77ea 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -13,6 +13,9 @@ DECONZ_DOMAIN = 'deconz' CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor' CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups' +SUPPORTED_PLATFORMS = ['binary_sensor', 'cover', + 'light', 'scene', 'sensor', 'switch'] + ATTR_DARK = 'dark' ATTR_ON = 'on' diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py new file mode 100644 index 00000000000..a64f9af886b --- /dev/null +++ b/homeassistant/components/deconz/gateway.py @@ -0,0 +1,165 @@ +"""Representation of a deCONZ gateway.""" +from homeassistant import config_entries +from homeassistant.const import CONF_EVENT, CONF_ID +from homeassistant.core import EventOrigin, callback +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, async_dispatcher_send) +from homeassistant.util import slugify + +from .const import ( + _LOGGER, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS) + + +class DeconzGateway: + """Manages a single deCONZ gateway.""" + + def __init__(self, hass, config_entry): + """Initialize the system.""" + self.hass = hass + self.config_entry = config_entry + self.api = None + self._cancel_retry_setup = None + + self.deconz_ids = {} + self.events = [] + self.listeners = [] + + async def async_setup(self, tries=0): + """Set up a deCONZ gateway.""" + hass = self.hass + + self.api = await get_gateway( + hass, self.config_entry.data, self.async_add_device_callback + ) + + if self.api is False: + retry_delay = 2 ** (tries + 1) + _LOGGER.error( + "Error connecting to deCONZ gateway. Retrying in %d seconds", + retry_delay) + + async def retry_setup(_now): + """Retry setup.""" + if await self.async_setup(tries + 1): + # This feels hacky, we should find a better way to do this + self.config_entry.state = config_entries.ENTRY_STATE_LOADED + + self._cancel_retry_setup = hass.helpers.event.async_call_later( + retry_delay, retry_setup) + + return False + + for component in SUPPORTED_PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup( + self.config_entry, component)) + + self.listeners.append( + async_dispatcher_connect( + hass, 'deconz_new_sensor', self.async_add_remote)) + + self.async_add_remote(self.api.sensors.values()) + + self.api.start() + + return True + + @callback + def async_add_device_callback(self, device_type, device): + """Handle event of new device creation in deCONZ.""" + if not isinstance(device, list): + device = [device] + async_dispatcher_send( + self.hass, 'deconz_new_{}'.format(device_type), device) + + @callback + def async_add_remote(self, sensors): + """Set up remote from deCONZ.""" + from pydeconz.sensor import SWITCH as DECONZ_REMOTE + allow_clip_sensor = self.config_entry.data.get( + CONF_ALLOW_CLIP_SENSOR, True) + for sensor in sensors: + if sensor.type in DECONZ_REMOTE and \ + not (not allow_clip_sensor and sensor.type.startswith('CLIP')): + self.events.append(DeconzEvent(self.hass, sensor)) + + @callback + def shutdown(self, event): + """Wrap the call to deconz.close. + + Used as an argument to EventBus.async_listen_once. + """ + self.api.close() + + async def async_reset(self): + """Reset this gateway to default state. + + Will cancel any scheduled setup retry and will unload + the config entry. + """ + # If we have a retry scheduled, we were never setup. + if self._cancel_retry_setup is not None: + self._cancel_retry_setup() + self._cancel_retry_setup = None + return True + + self.api.close() + + for component in SUPPORTED_PLATFORMS: + await self.hass.config_entries.async_forward_entry_unload( + self.config_entry, component) + + for unsub_dispatcher in self.listeners: + unsub_dispatcher() + self.listeners = [] + + for event in self.events: + event.async_will_remove_from_hass() + self.events.remove(event) + + self.deconz_ids = {} + return True + + +async def get_gateway(hass, config, async_add_device_callback): + """Create a gateway object and verify configuration.""" + from pydeconz import DeconzSession + + session = aiohttp_client.async_get_clientsession(hass) + deconz = DeconzSession(hass.loop, session, **config, + async_add_device=async_add_device_callback) + result = await deconz.async_load_parameters() + + if result: + return deconz + return result + + +class DeconzEvent: + """When you want signals instead of entities. + + Stateless sensors such as remotes are expected to generate an event + instead of a sensor entity in hass. + """ + + def __init__(self, hass, device): + """Register callback that will be used for signals.""" + self._hass = hass + self._device = device + self._device.register_async_callback(self.async_update_callback) + self._event = 'deconz_{}'.format(CONF_EVENT) + self._id = slugify(self._device.name) + + @callback + def async_will_remove_from_hass(self) -> None: + """Disconnect event object when removed.""" + self._device.remove_callback(self.async_update_callback) + self._device = None + + @callback + def async_update_callback(self, reason): + """Fire the event if reason is that state is updated.""" + if reason['state']: + data = {CONF_ID: self._id, CONF_EVENT: self._device.state} + self._hass.bus.async_fire(self._event, data, EventOrigin.remote) diff --git a/homeassistant/components/light/deconz.py b/homeassistant/components/light/deconz.py index d3bec079a4c..61f5ea39603 100644 --- a/homeassistant/components/light/deconz.py +++ b/homeassistant/components/light/deconz.py @@ -5,8 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/light.deconz/ """ from homeassistant.components.deconz.const import ( - CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN, + CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, COVER_TYPES, SWITCH_TYPES) from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, @@ -38,7 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzLight(light)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_light', async_add_light)) @callback @@ -51,11 +50,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzLight(group)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_group', async_add_group)) - async_add_light(hass.data[DATA_DECONZ].lights.values()) - async_add_group(hass.data[DATA_DECONZ].groups.values()) + async_add_light(hass.data[DATA_DECONZ].api.lights.values()) + async_add_group(hass.data[DATA_DECONZ].api.groups.values()) class DeconzLight(Light): @@ -81,7 +80,8 @@ class DeconzLight(Light): async def async_added_to_hass(self): """Subscribe to lights events.""" self._light.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._light.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect light object when removed.""" @@ -214,7 +214,7 @@ class DeconzLight(Light): self._light.uniqueid.count(':') != 7): return None serial = self._light.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, diff --git a/homeassistant/components/scene/deconz.py b/homeassistant/components/scene/deconz.py index b8fca6d8630..6319e52f6ef 100644 --- a/homeassistant/components/scene/deconz.py +++ b/homeassistant/components/scene/deconz.py @@ -4,8 +4,7 @@ Support for deCONZ scenes. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/scene.deconz/ """ -from homeassistant.components.deconz import ( - DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB) +from homeassistant.components.deconz import DOMAIN as DATA_DECONZ from homeassistant.components.scene import Scene from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -28,10 +27,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for scene in scenes: entities.append(DeconzScene(scene)) async_add_entities(entities) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_scene', async_add_scene)) - async_add_scene(hass.data[DATA_DECONZ].scenes.values()) + async_add_scene(hass.data[DATA_DECONZ].api.scenes.values()) class DeconzScene(Scene): @@ -43,7 +42,8 @@ class DeconzScene(Scene): async def async_added_to_hass(self): """Subscribe to sensors events.""" - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._scene.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._scene.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect scene object when removed.""" diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index c66bda2bc1d..99f450d018e 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.deconz/ """ from homeassistant.components.deconz.const import ( ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN) + DECONZ_DOMAIN) from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY) from homeassistant.core import callback @@ -46,10 +46,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzSensor(sensor)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor)) - async_add_sensor(hass.data[DATA_DECONZ].sensors.values()) + async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values()) class DeconzSensor(Entity): @@ -62,7 +62,8 @@ class DeconzSensor(Entity): async def async_added_to_hass(self): """Subscribe to sensors events.""" self._sensor.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._sensor.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect sensor object when removed.""" @@ -147,7 +148,7 @@ class DeconzSensor(Entity): self._sensor.uniqueid.count(':') != 7): return None serial = self._sensor.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, @@ -171,7 +172,8 @@ class DeconzBattery(Entity): async def async_added_to_hass(self): """Subscribe to sensors events.""" self._sensor.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._sensor.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect sensor object when removed.""" @@ -181,7 +183,7 @@ class DeconzBattery(Entity): @callback def async_update_callback(self, reason): """Update the battery's state, if needed.""" - if 'battery' in reason['attr']: + if 'reachable' in reason['attr'] or 'battery' in reason['attr']: self.async_schedule_update_ha_state() @property @@ -229,7 +231,7 @@ class DeconzBattery(Entity): self._sensor.uniqueid.count(':') != 7): return None serial = self._sensor.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, diff --git a/homeassistant/components/switch/deconz.py b/homeassistant/components/switch/deconz.py index f8911d65d98..4c2fcca052c 100644 --- a/homeassistant/components/switch/deconz.py +++ b/homeassistant/components/switch/deconz.py @@ -5,8 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.deconz/ """ from homeassistant.components.deconz.const import ( - DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB, - DECONZ_DOMAIN, POWER_PLUGS, SIRENS) + DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, POWER_PLUGS, SIRENS) from homeassistant.components.switch import SwitchDevice from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -37,10 +36,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(DeconzSiren(light)) async_add_entities(entities, True) - hass.data[DATA_DECONZ_UNSUB].append( + hass.data[DATA_DECONZ].listeners.append( async_dispatcher_connect(hass, 'deconz_new_light', async_add_switch)) - async_add_switch(hass.data[DATA_DECONZ].lights.values()) + async_add_switch(hass.data[DATA_DECONZ].api.lights.values()) class DeconzSwitch(SwitchDevice): @@ -53,7 +52,8 @@ class DeconzSwitch(SwitchDevice): async def async_added_to_hass(self): """Subscribe to switches events.""" self._switch.register_async_callback(self.async_update_callback) - self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._switch.deconz_id + self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \ + self._switch.deconz_id async def async_will_remove_from_hass(self) -> None: """Disconnect switch object when removed.""" @@ -92,7 +92,7 @@ class DeconzSwitch(SwitchDevice): self._switch.uniqueid.count(':') != 7): return None serial = self._switch.uniqueid.split('-', 1)[0] - bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid + bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid return { 'connections': {(CONNECTION_ZIGBEE, serial)}, 'identifiers': {(DECONZ_DOMAIN, serial)}, diff --git a/tests/components/binary_sensor/test_deconz.py b/tests/components/binary_sensor/test_deconz.py index 5fd6e132e03..ba39afa0e88 100644 --- a/tests/components/binary_sensor/test_deconz.py +++ b/tests/components/binary_sensor/test_deconz.py @@ -4,6 +4,9 @@ from unittest.mock import Mock, patch from homeassistant import config_entries from homeassistant.components import deconz from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +import homeassistant.components.binary_sensor as binary_sensor from tests.common import mock_coro @@ -14,7 +17,8 @@ SENSOR = { "name": "Sensor 1 name", "type": "ZHAPresence", "state": {"presence": False}, - "config": {} + "config": {}, + "uniqueid": "00:00:00:00:00:00:00:00-00" }, "2": { "id": "Sensor 2 id", @@ -26,70 +30,105 @@ SENSOR = { } -async def setup_bridge(hass, data, allow_clip_sensor=True): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ binary sensor platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) - bridge.config = Mock() + + ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', - {'host': 'mock-host', 'allow_clip_sensor': allow_clip_sensor}, 'test', - config_entries.CONN_CLASS_LOCAL_PUSH) + await gateway.api.async_load_parameters() + await hass.config_entries.async_forward_entry_setup( config_entry, 'binary_sensor') # To flush out the service call to update the group await hass.async_block_till_done() +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, binary_sensor.DOMAIN, { + 'binary_sensor': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + async def test_no_binary_sensors(hass): """Test that no sensors in deconz results in no sensor entities.""" data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, data) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 async def test_binary_sensors(hass): """Test successful creation of binary sensor entities.""" data = {"sensors": SENSOR} - await setup_bridge(hass, data) - assert "binary_sensor.sensor_1_name" in hass.data[deconz.DATA_DECONZ_ID] + await setup_gateway(hass, data) + assert "binary_sensor.sensor_1_name" in \ + hass.data[deconz.DOMAIN].deconz_ids assert "binary_sensor.sensor_2_name" not in \ - hass.data[deconz.DATA_DECONZ_ID] + hass.data[deconz.DOMAIN].deconz_ids assert len(hass.states.async_all()) == 1 + hass.data[deconz.DOMAIN].api.sensors['1'].async_update( + {'state': {'on': False}}) + async def test_add_new_sensor(hass): """Test successful creation of sensor entities.""" data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, data) sensor = Mock() sensor.name = 'name' sensor.type = 'ZHAPresence' sensor.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_sensor', [sensor]) await hass.async_block_till_done() - assert "binary_sensor.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "binary_sensor.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_do_not_allow_clip_sensor(hass): """Test that clip sensors can be ignored.""" data = {} - await setup_bridge(hass, data, allow_clip_sensor=False) + await setup_gateway(hass, data, allow_clip_sensor=False) sensor = Mock() sensor.name = 'name' sensor.type = 'CLIPPresence' sensor.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_sensor', [sensor]) await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 + + +async def test_unload_switch(hass): + """Test that it works to unload switch entities.""" + data = {"sensors": SENSOR} + await setup_gateway(hass, data) + + await hass.data[deconz.DOMAIN].async_reset() + + assert len(hass.states.async_all()) == 0 diff --git a/tests/components/cover/test_deconz.py b/tests/components/cover/test_deconz.py index e9c630823bd..b021bcb8d51 100644 --- a/tests/components/cover/test_deconz.py +++ b/tests/components/cover/test_deconz.py @@ -5,6 +5,9 @@ from homeassistant import config_entries from homeassistant.components import deconz from homeassistant.components.deconz.const import COVER_TYPES from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +import homeassistant.components.cover as cover from tests.common import mock_coro @@ -13,14 +16,15 @@ SUPPORTED_COVERS = { "id": "Cover 1 id", "name": "Cover 1 name", "type": "Level controllable output", - "state": {}, - "modelid": "Not zigbee spec" + "state": {"bri": 255, "reachable": True}, + "modelid": "Not zigbee spec", + "uniqueid": "00:00:00:00:00:00:00:00-00" }, "2": { "id": "Cover 2 id", "name": "Cover 2 name", "type": "Window covering device", - "state": {}, + "state": {"bri": 255, "reachable": True}, "modelid": "lumi.curtain" } } @@ -35,58 +39,109 @@ UNSUPPORTED_COVER = { } -async def setup_bridge(hass, data): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data): """Load the deCONZ cover platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test', - config_entries.CONN_CLASS_LOCAL_PUSH) + await gateway.api.async_load_parameters() + await hass.config_entries.async_forward_entry_setup(config_entry, 'cover') # To flush out the service call to update the group await hass.async_block_till_done() -async def test_no_switches(hass): +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, cover.DOMAIN, { + 'cover': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + +async def test_no_covers(hass): """Test that no cover entities are created.""" - data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, {}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 async def test_cover(hass): """Test that all supported cover entities are created.""" - await setup_bridge(hass, {"lights": SUPPORTED_COVERS}) - assert "cover.cover_1_name" in hass.data[deconz.DATA_DECONZ_ID] + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): + await setup_gateway(hass, {"lights": SUPPORTED_COVERS}) + assert "cover.cover_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert len(SUPPORTED_COVERS) == len(COVER_TYPES) assert len(hass.states.async_all()) == 3 + cover_1 = hass.states.get('cover.cover_1_name') + assert cover_1 is not None + assert cover_1.state == 'closed' + + hass.data[deconz.DOMAIN].api.lights['1'].async_update({}) + + await hass.services.async_call('cover', 'open_cover', { + 'entity_id': 'cover.cover_1_name' + }, blocking=True) + await hass.services.async_call('cover', 'close_cover', { + 'entity_id': 'cover.cover_1_name' + }, blocking=True) + await hass.services.async_call('cover', 'stop_cover', { + 'entity_id': 'cover.cover_1_name' + }, blocking=True) + + await hass.services.async_call('cover', 'close_cover', { + 'entity_id': 'cover.cover_2_name' + }, blocking=True) + async def test_add_new_cover(hass): """Test successful creation of cover entity.""" data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, data) cover = Mock() cover.name = 'name' cover.type = "Level controllable output" cover.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_light', [cover]) await hass.async_block_till_done() - assert "cover.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "cover.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_unsupported_cover(hass): """Test that unsupported covers are not created.""" - await setup_bridge(hass, {"lights": UNSUPPORTED_COVER}) + await setup_gateway(hass, {"lights": UNSUPPORTED_COVER}) assert len(hass.states.async_all()) == 0 + + +async def test_unload_cover(hass): + """Test that it works to unload switch entities.""" + await setup_gateway(hass, {"lights": SUPPORTED_COVERS}) + + await hass.data[deconz.DOMAIN].async_reset() + + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 111cfbe9697..20b7a88bc05 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -20,7 +20,7 @@ async def test_flow_works(hass, aioclient_mock): flow = config_flow.DeconzFlowHandler() flow.hass = hass - await flow.async_step_init() + await flow.async_step_user() await flow.async_step_link(user_input={}) result = await flow.async_step_options( user_input={'allow_clip_sensor': True, 'allow_deconz_groups': True}) @@ -45,7 +45,7 @@ async def test_flow_already_registered_bridge(hass): flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'abort' @@ -55,7 +55,7 @@ async def test_flow_no_discovered_bridges(hass, aioclient_mock): flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'abort' @@ -67,7 +67,7 @@ async def test_flow_one_bridge_discovered(hass, aioclient_mock): flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'form' assert result['step_id'] == 'link' @@ -81,9 +81,9 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock): flow = config_flow.DeconzFlowHandler() flow.hass = hass - result = await flow.async_step_init() + result = await flow.async_step_user() assert result['type'] == 'form' - assert result['step_id'] == 'init' + assert result['step_id'] == 'user' with pytest.raises(vol.Invalid): assert result['data_schema']({'host': '0.0.0.0'}) @@ -92,6 +92,21 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock): result['data_schema']({'host': '5.6.7.8'}) +async def test_flow_two_bridges_selection(hass, aioclient_mock): + """Test config flow selection of one of two bridges.""" + flow = config_flow.DeconzFlowHandler() + flow.hass = hass + flow.bridges = [ + {'bridgeid': 'id1', 'host': '1.2.3.4', 'port': 80}, + {'bridgeid': 'id2', 'host': '5.6.7.8', 'port': 80} + ] + + result = await flow.async_step_user(user_input={'host': '1.2.3.4'}) + assert result['type'] == 'form' + assert result['step_id'] == 'link' + assert flow.deconz_config['host'] == '1.2.3.4' + + async def test_link_no_api_key(hass, aioclient_mock): """Test config flow should abort if no API key was possible to retrieve.""" aioclient_mock.post('http://1.2.3.4:80/api', json=[]) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 8cc8c4bc242..3453dd86c12 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,11 +1,11 @@ """Test deCONZ component setup process.""" from unittest.mock import Mock, patch -from homeassistant.components import deconz -from homeassistant.components.deconz import DATA_DECONZ_ID -from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from homeassistant.components import deconz + +from tests.common import mock_coro, MockConfigEntry + CONFIG = { "config": { @@ -99,173 +99,113 @@ async def test_setup_entry_no_available_bridge(hass): async def test_setup_entry_successful(hass): """Test setup entry is successful.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - with patch.object(hass, 'async_create_task') as mock_add_job, \ - patch.object(hass, 'config_entries') as mock_config_entries, \ - patch('pydeconz.DeconzSession.async_get_state', - return_value=mock_coro(CONFIG)), \ - patch('pydeconz.DeconzSession.start', return_value=True), \ + entry = MockConfigEntry(domain=deconz.DOMAIN, data={ + 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF' + }) + entry.add_to_hass(hass) + mock_registry = Mock() + with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ patch('homeassistant.helpers.device_registry.async_get_registry', - return_value=mock_coro(Mock())): + return_value=mock_coro(mock_registry)): + mock_gateway.return_value.async_setup.return_value = mock_coro(True) assert await deconz.async_setup_entry(hass, entry) is True assert hass.data[deconz.DOMAIN] - assert hass.data[deconz.DATA_DECONZ_ID] == {} - assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 1 - assert len(mock_add_job.mock_calls) == \ - len(deconz.SUPPORTED_PLATFORMS) - assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == \ - len(deconz.SUPPORTED_PLATFORMS) - assert mock_config_entries.async_forward_entry_setup.mock_calls[0][1] == \ - (entry, 'binary_sensor') - assert mock_config_entries.async_forward_entry_setup.mock_calls[1][1] == \ - (entry, 'cover') - assert mock_config_entries.async_forward_entry_setup.mock_calls[2][1] == \ - (entry, 'light') - assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \ - (entry, 'scene') - assert mock_config_entries.async_forward_entry_setup.mock_calls[4][1] == \ - (entry, 'sensor') - assert mock_config_entries.async_forward_entry_setup.mock_calls[5][1] == \ - (entry, 'switch') async def test_unload_entry(hass): """Test being able to unload an entry.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - entry.async_unload.return_value = mock_coro(True) - deconzmock = Mock() - deconzmock.async_load_parameters.return_value = mock_coro(True) - deconzmock.sensors = {} - with patch('pydeconz.DeconzSession', return_value=deconzmock): + entry = MockConfigEntry(domain=deconz.DOMAIN, data={ + 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF' + }) + entry.add_to_hass(hass) + mock_registry = Mock() + with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ + patch('homeassistant.helpers.device_registry.async_get_registry', + return_value=mock_coro(mock_registry)): + mock_gateway.return_value.async_setup.return_value = mock_coro(True) assert await deconz.async_setup_entry(hass, entry) is True - assert deconz.DATA_DECONZ_EVENT in hass.data - - hass.data[deconz.DATA_DECONZ_EVENT].append(Mock()) - hass.data[deconz.DATA_DECONZ_ID] = {'id': 'deconzid'} + mock_gateway.return_value.async_reset.return_value = mock_coro(True) assert await deconz.async_unload_entry(hass, entry) assert deconz.DOMAIN not in hass.data - assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 0 - assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0 - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 - - -async def test_add_new_device(hass): - """Test adding a new device generates a signal for platforms.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, - 'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False} - new_event = { - "t": "event", - "e": "added", - "r": "sensors", - "id": "1", - "sensor": { - "config": { - "on": "True", - "reachable": "True" - }, - "name": "event", - "state": {}, - "type": "ZHASwitch" - } - } - with patch.object(deconz, 'async_dispatcher_send') as mock_dispatch_send, \ - patch('pydeconz.DeconzSession.async_get_state', - return_value=mock_coro(CONFIG)), \ - patch('pydeconz.DeconzSession.start', return_value=True): - assert await deconz.async_setup_entry(hass, entry) is True - hass.data[deconz.DOMAIN].async_event_handler(new_event) - await hass.async_block_till_done() - assert len(mock_dispatch_send.mock_calls) == 1 - assert len(mock_dispatch_send.mock_calls[0]) == 3 - - -async def test_add_new_remote(hass): - """Test new added device creates a new remote.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, - 'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False} - remote = Mock() - remote.name = 'name' - remote.type = 'ZHASwitch' - remote.register_async_callback = Mock() - with patch('pydeconz.DeconzSession.async_get_state', - return_value=mock_coro(CONFIG)), \ - patch('pydeconz.DeconzSession.start', return_value=True): - assert await deconz.async_setup_entry(hass, entry) is True - async_dispatcher_send(hass, 'deconz_new_sensor', [remote]) - await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 1 - - -async def test_do_not_allow_clip_sensor(hass): - """Test that clip sensors can be ignored.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, - 'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False} - remote = Mock() - remote.name = 'name' - remote.type = 'CLIPSwitch' - remote.register_async_callback = Mock() - with patch('pydeconz.DeconzSession.async_get_state', - return_value=mock_coro(CONFIG)), \ - patch('pydeconz.DeconzSession.start', return_value=True): - assert await deconz.async_setup_entry(hass, entry) is True - - async_dispatcher_send(hass, 'deconz_new_sensor', [remote]) - await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0 async def test_service_configure(hass): """Test that service invokes pydeconz with the correct path and data.""" - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - with patch('pydeconz.DeconzSession.async_get_state', - return_value=mock_coro(CONFIG)), \ - patch('pydeconz.DeconzSession.start', return_value=True), \ + entry = MockConfigEntry(domain=deconz.DOMAIN, data={ + 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF' + }) + entry.add_to_hass(hass) + mock_registry = Mock() + with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ patch('homeassistant.helpers.device_registry.async_get_registry', - return_value=mock_coro(Mock())): + return_value=mock_coro(mock_registry)): + mock_gateway.return_value.async_setup.return_value = mock_coro(True) assert await deconz.async_setup_entry(hass, entry) is True - hass.data[DATA_DECONZ_ID] = { + hass.data[deconz.DOMAIN].deconz_ids = { 'light.test': '/light/1' } data = {'on': True, 'attr1': 10, 'attr2': 20} # only field - with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): await hass.services.async_call('deconz', 'configure', service_data={ 'field': '/light/42', 'data': data }) await hass.async_block_till_done() - async_put_state.assert_called_with('/light/42', data) + # only entity - with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): await hass.services.async_call('deconz', 'configure', service_data={ 'entity': 'light.test', 'data': data }) await hass.async_block_till_done() - async_put_state.assert_called_with('/light/1', data) + # entity + field - with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): await hass.services.async_call('deconz', 'configure', service_data={ 'entity': 'light.test', 'field': '/state', 'data': data}) await hass.async_block_till_done() - async_put_state.assert_called_with('/light/1/state', data) # non-existing entity (or not from deCONZ) - with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): await hass.services.async_call('deconz', 'configure', service_data={ 'entity': 'light.nonexisting', 'field': '/state', 'data': data}) await hass.async_block_till_done() - async_put_state.assert_not_called() + # field does not start with / - with patch('pydeconz.DeconzSession.async_put_state') as async_put_state: + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): await hass.services.async_call('deconz', 'configure', service_data={ 'entity': 'light.test', 'field': 'state', 'data': data}) await hass.async_block_till_done() - async_put_state.assert_not_called() + + +async def test_service_refresh_devices(hass): + """Test that service can refresh devices.""" + entry = MockConfigEntry(domain=deconz.DOMAIN, data={ + 'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF' + }) + entry.add_to_hass(hass) + mock_registry = Mock() + with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ + patch('homeassistant.helpers.device_registry.async_get_registry', + return_value=mock_coro(mock_registry)): + mock_gateway.return_value.async_setup.return_value = mock_coro(True) + assert await deconz.async_setup_entry(hass, entry) is True + + with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters', + return_value=mock_coro(True)): + await hass.services.async_call( + 'deconz', 'device_refresh', service_data={}) + await hass.async_block_till_done() + with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters', + return_value=mock_coro(False)): + await hass.services.async_call( + 'deconz', 'device_refresh', service_data={}) + await hass.async_block_till_done() diff --git a/tests/components/light/test_deconz.py b/tests/components/light/test_deconz.py index 96f180505b8..081fd61ec4e 100644 --- a/tests/components/light/test_deconz.py +++ b/tests/components/light/test_deconz.py @@ -4,6 +4,9 @@ from unittest.mock import Mock, patch from homeassistant import config_entries from homeassistant.components import deconz from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +import homeassistant.components.light as light from tests.common import mock_coro @@ -12,7 +15,18 @@ LIGHT = { "1": { "id": "Light 1 id", "name": "Light 1 name", - "state": {} + "state": { + "on": True, "bri": 255, "colormode": "xy", "xy": (500, 500), + "reachable": True + }, + "uniqueid": "00:00:00:00:00:00:00:00-00" + }, + "2": { + "id": "Light 2 id", + "name": "Light 2 name", + "state": { + "on": True, "colormode": "ct", "ct": 2500, "reachable": True + } } } @@ -20,6 +34,7 @@ GROUP = { "1": { "id": "Group 1 id", "name": "Group 1 name", + "type": "LightGroup", "state": {}, "action": {}, "scenes": [], @@ -47,85 +62,152 @@ SWITCH = { } -async def setup_bridge(hass, data, allow_deconz_groups=True): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data, allow_deconz_groups=True): """Load the deCONZ light platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) - bridge.config = Mock() + + ENTRY_CONFIG[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', - {'host': 'mock-host', 'allow_deconz_groups': allow_deconz_groups}, - 'test', config_entries.CONN_CLASS_LOCAL_PUSH) + await gateway.api.async_load_parameters() + await hass.config_entries.async_forward_entry_setup(config_entry, 'light') # To flush out the service call to update the group await hass.async_block_till_done() +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, light.DOMAIN, { + 'light': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + async def test_no_lights_or_groups(hass): """Test that no lights or groups entities are created.""" - data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, {}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 async def test_lights_and_groups(hass): """Test that lights or groups entities are created.""" - await setup_bridge(hass, {"lights": LIGHT, "groups": GROUP}) - assert "light.light_1_name" in hass.data[deconz.DATA_DECONZ_ID] - assert "light.group_1_name" in hass.data[deconz.DATA_DECONZ_ID] - assert "light.group_2_name" not in hass.data[deconz.DATA_DECONZ_ID] - assert len(hass.states.async_all()) == 3 + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): + await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP}) + assert "light.light_1_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "light.light_2_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "light.group_1_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "light.group_2_name" not in hass.data[deconz.DOMAIN].deconz_ids + assert len(hass.states.async_all()) == 4 + + lamp_1 = hass.states.get('light.light_1_name') + assert lamp_1 is not None + assert lamp_1.state == 'on' + assert lamp_1.attributes['brightness'] == 255 + assert lamp_1.attributes['hs_color'] == (224.235, 100.0) + + light_2 = hass.states.get('light.light_2_name') + assert light_2 is not None + assert light_2.state == 'on' + assert light_2.attributes['color_temp'] == 2500 + + hass.data[deconz.DOMAIN].api.lights['1'].async_update({}) + + await hass.services.async_call('light', 'turn_on', { + 'entity_id': 'light.light_1_name', + 'color_temp': 2500, + 'brightness': 200, + 'transition': 5, + 'flash': 'short', + 'effect': 'colorloop' + }, blocking=True) + await hass.services.async_call('light', 'turn_on', { + 'entity_id': 'light.light_1_name', + 'hs_color': (20, 30), + 'flash': 'long', + 'effect': 'None' + }, blocking=True) + await hass.services.async_call('light', 'turn_off', { + 'entity_id': 'light.light_1_name', + 'transition': 5, + 'flash': 'short' + }, blocking=True) + await hass.services.async_call('light', 'turn_off', { + 'entity_id': 'light.light_1_name', + 'flash': 'long' + }, blocking=True) async def test_add_new_light(hass): """Test successful creation of light entities.""" - data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, {}) light = Mock() light.name = 'name' light.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_light', [light]) await hass.async_block_till_done() - assert "light.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_add_new_group(hass): """Test successful creation of group entities.""" - data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, {}) group = Mock() group.name = 'name' group.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_group', [group]) await hass.async_block_till_done() - assert "light.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_do_not_add_deconz_groups(hass): """Test that clip sensors can be ignored.""" - data = {} - await setup_bridge(hass, data, allow_deconz_groups=False) + await setup_gateway(hass, {}, allow_deconz_groups=False) group = Mock() group.name = 'name' group.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_group', [group]) await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 async def test_no_switch(hass): """Test that a switch doesn't get created as a light entity.""" - await setup_bridge(hass, {"lights": SWITCH}) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, {"lights": SWITCH}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 + + +async def test_unload_light(hass): + """Test that it works to unload switch entities.""" + await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP}) + + await hass.data[deconz.DOMAIN].async_reset() + + # Group.all_lights will not be removed + assert len(hass.states.async_all()) == 1 diff --git a/tests/components/scene/test_deconz.py b/tests/components/scene/test_deconz.py index 89bb5297e78..788c6dc1c3e 100644 --- a/tests/components/scene/test_deconz.py +++ b/tests/components/scene/test_deconz.py @@ -1,8 +1,11 @@ -"""deCONZ scenes platform tests.""" +"""deCONZ scene platform tests.""" from unittest.mock import Mock, patch from homeassistant import config_entries from homeassistant.components import deconz +from homeassistant.setup import async_setup_component + +import homeassistant.components.scene as scene from tests.common import mock_coro @@ -21,39 +24,73 @@ GROUP = { } -async def setup_bridge(hass, data): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data): """Load the deCONZ scene platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test', - config_entries.CONN_CLASS_LOCAL_PUSH) + await gateway.api.async_load_parameters() + await hass.config_entries.async_forward_entry_setup(config_entry, 'scene') # To flush out the service call to update the group await hass.async_block_till_done() +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, scene.DOMAIN, { + 'scene': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + async def test_no_scenes(hass): - """Test the update_lights function with some lights.""" - data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + """Test that scenes can be loaded without scenes being available.""" + await setup_gateway(hass, {}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 async def test_scenes(hass): - """Test the update_lights function with some lights.""" - data = {"groups": GROUP} - await setup_bridge(hass, data) - assert "scene.group_1_name_scene_1" in hass.data[deconz.DATA_DECONZ_ID] + """Test that scenes works.""" + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): + await setup_gateway(hass, {"groups": GROUP}) + assert "scene.group_1_name_scene_1" in hass.data[deconz.DOMAIN].deconz_ids assert len(hass.states.async_all()) == 1 + + await hass.services.async_call('scene', 'turn_on', { + 'entity_id': 'scene.group_1_name_scene_1' + }, blocking=True) + + +async def test_unload_scene(hass): + """Test that it works to unload scene entities.""" + await setup_gateway(hass, {"groups": GROUP}) + + await hass.data[deconz.DOMAIN].async_reset() + + assert len(hass.states.async_all()) == 0 diff --git a/tests/components/sensor/test_deconz.py b/tests/components/sensor/test_deconz.py index ae9e75d6a41..f5cfbe2c183 100644 --- a/tests/components/sensor/test_deconz.py +++ b/tests/components/sensor/test_deconz.py @@ -1,10 +1,12 @@ """deCONZ sensor platform tests.""" from unittest.mock import Mock, patch - from homeassistant import config_entries from homeassistant.components import deconz from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +import homeassistant.components.sensor as sensor from tests.common import mock_coro @@ -13,9 +15,10 @@ SENSOR = { "1": { "id": "Sensor 1 id", "name": "Sensor 1 name", - "type": "ZHATemperature", - "state": {"temperature": False}, - "config": {} + "type": "ZHALightLevel", + "state": {"lightlevel": 30000, "dark": False}, + "config": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00" }, "2": { "id": "Sensor 2 id", @@ -36,80 +39,134 @@ SENSOR = { "name": "Sensor 4 name", "type": "ZHASwitch", "state": {"buttonevent": 1000}, - "config": {"battery": 100} + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:01-00" + }, + "5": { + "id": "Sensor 5 id", + "name": "Sensor 5 name", + "type": "ZHASwitch", + "state": {"buttonevent": 1000}, + "config": {"battery": 100}, + "uniqueid": "00:00:00:00:00:00:00:02:00-00" + }, + "6": { + "id": "Sensor 6 id", + "name": "Sensor 6 name", + "type": "Daylight", + "state": {"daylight": True}, + "config": {} + }, + "7": { + "id": "Sensor 7 id", + "name": "Sensor 7 name", + "type": "ZHAPower", + "state": {"current": 2, "power": 6, "voltage": 3}, + "config": {"reachable": True} } } -async def setup_bridge(hass, data, allow_clip_sensor=True): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data, allow_clip_sensor=True): """Load the deCONZ sensor platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) - bridge.config = Mock() + + ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_EVENT] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', - {'host': 'mock-host', 'allow_clip_sensor': allow_clip_sensor}, 'test', - config_entries.CONN_CLASS_LOCAL_PUSH) - await hass.config_entries.async_forward_entry_setup(config_entry, 'sensor') + await gateway.api.async_load_parameters() + + await hass.config_entries.async_forward_entry_setup( + config_entry, 'sensor') # To flush out the service call to update the group await hass.async_block_till_done() +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, sensor.DOMAIN, { + 'sensor': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + async def test_no_sensors(hass): """Test that no sensors in deconz results in no sensor entities.""" - data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, {}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 async def test_sensors(hass): """Test successful creation of sensor entities.""" - data = {"sensors": SENSOR} - await setup_bridge(hass, data) - assert "sensor.sensor_1_name" in hass.data[deconz.DATA_DECONZ_ID] - assert "sensor.sensor_2_name" not in hass.data[deconz.DATA_DECONZ_ID] - assert "sensor.sensor_3_name" not in hass.data[deconz.DATA_DECONZ_ID] + await setup_gateway(hass, {"sensors": SENSOR}) + assert "sensor.sensor_1_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids + assert "sensor.sensor_3_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_3_name_battery_level" not in \ - hass.data[deconz.DATA_DECONZ_ID] - assert "sensor.sensor_4_name" not in hass.data[deconz.DATA_DECONZ_ID] + hass.data[deconz.DOMAIN].deconz_ids + assert "sensor.sensor_4_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_4_name_battery_level" in \ - hass.data[deconz.DATA_DECONZ_ID] - assert len(hass.states.async_all()) == 2 + hass.data[deconz.DOMAIN].deconz_ids + assert len(hass.states.async_all()) == 5 + + hass.data[deconz.DOMAIN].api.sensors['1'].async_update( + {'state': {'on': False}}) + hass.data[deconz.DOMAIN].api.sensors['4'].async_update( + {'config': {'battery': 75}}) async def test_add_new_sensor(hass): """Test successful creation of sensor entities.""" - data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, {}) sensor = Mock() sensor.name = 'name' sensor.type = 'ZHATemperature' sensor.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_sensor', [sensor]) await hass.async_block_till_done() - assert "sensor.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "sensor.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_do_not_allow_clipsensor(hass): """Test that clip sensors can be ignored.""" - data = {} - await setup_bridge(hass, data, allow_clip_sensor=False) + await setup_gateway(hass, {}, allow_clip_sensor=False) sensor = Mock() sensor.name = 'name' sensor.type = 'CLIPTemperature' sensor.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_sensor', [sensor]) await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 + + +async def test_unload_sensor(hass): + """Test that it works to unload sensor entities.""" + await setup_gateway(hass, {"sensors": SENSOR}) + + await hass.data[deconz.DOMAIN].async_reset() + + assert len(hass.states.async_all()) == 0 diff --git a/tests/components/switch/test_deconz.py b/tests/components/switch/test_deconz.py index 6833cab33d7..245be27961d 100644 --- a/tests/components/switch/test_deconz.py +++ b/tests/components/switch/test_deconz.py @@ -5,6 +5,9 @@ from homeassistant import config_entries from homeassistant.components import deconz from homeassistant.components.deconz.const import SWITCH_TYPES from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_setup_component + +import homeassistant.components.switch as switch from tests.common import mock_coro @@ -13,19 +16,20 @@ SUPPORTED_SWITCHES = { "id": "Switch 1 id", "name": "Switch 1 name", "type": "On/Off plug-in unit", - "state": {} + "state": {"on": True, "reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:00-00" }, "2": { "id": "Switch 2 id", "name": "Switch 2 name", "type": "Smart plug", - "state": {} + "state": {"on": True, "reachable": True} }, "3": { "id": "Switch 3 id", "name": "Switch 3 name", "type": "Warning device", - "state": {} + "state": {"alert": "lselect", "reachable": True} } } @@ -39,61 +43,113 @@ UNSUPPORTED_SWITCH = { } -async def setup_bridge(hass, data): +ENTRY_CONFIG = { + deconz.const.CONF_ALLOW_CLIP_SENSOR: True, + deconz.const.CONF_ALLOW_DECONZ_GROUPS: True, + deconz.config_flow.CONF_API_KEY: "ABCDEF", + deconz.config_flow.CONF_BRIDGEID: "0123456789", + deconz.config_flow.CONF_HOST: "1.2.3.4", + deconz.config_flow.CONF_PORT: 80 +} + + +async def setup_gateway(hass, data): """Load the deCONZ switch platform.""" from pydeconz import DeconzSession loop = Mock() session = Mock() - entry = Mock() - entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} - bridge = DeconzSession(loop, session, **entry.data) - bridge.config = Mock() + + config_entry = config_entries.ConfigEntry( + 1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test', + config_entries.CONN_CLASS_LOCAL_PUSH) + gateway = deconz.DeconzGateway(hass, config_entry) + gateway.api = DeconzSession(loop, session, **config_entry.data) + gateway.api.config = Mock() + hass.data[deconz.DOMAIN] = gateway + with patch('pydeconz.DeconzSession.async_get_state', return_value=mock_coro(data)): - await bridge.async_load_parameters() - hass.data[deconz.DOMAIN] = bridge - hass.data[deconz.DATA_DECONZ_UNSUB] = [] - hass.data[deconz.DATA_DECONZ_ID] = {} - config_entry = config_entries.ConfigEntry( - 1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test', - config_entries.CONN_CLASS_LOCAL_PUSH) + await gateway.api.async_load_parameters() + await hass.config_entries.async_forward_entry_setup(config_entry, 'switch') # To flush out the service call to update the group await hass.async_block_till_done() +async def test_platform_manually_configured(hass): + """Test that we do not discover anything or try to set up a gateway.""" + assert await async_setup_component(hass, switch.DOMAIN, { + 'switch': { + 'platform': deconz.DOMAIN + } + }) is True + assert deconz.DOMAIN not in hass.data + + async def test_no_switches(hass): """Test that no switch entities are created.""" - data = {} - await setup_bridge(hass, data) - assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 + await setup_gateway(hass, {}) + assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.states.async_all()) == 0 -async def test_switch(hass): +async def test_switches(hass): """Test that all supported switch entities are created.""" - await setup_bridge(hass, {"lights": SUPPORTED_SWITCHES}) - assert "switch.switch_1_name" in hass.data[deconz.DATA_DECONZ_ID] - assert "switch.switch_2_name" in hass.data[deconz.DATA_DECONZ_ID] - assert "switch.switch_3_name" in hass.data[deconz.DATA_DECONZ_ID] + with patch('pydeconz.DeconzSession.async_put_state', + return_value=mock_coro(True)): + await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES}) + assert "switch.switch_1_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "switch.switch_2_name" in hass.data[deconz.DOMAIN].deconz_ids + assert "switch.switch_3_name" in hass.data[deconz.DOMAIN].deconz_ids assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES) assert len(hass.states.async_all()) == 4 + switch_1 = hass.states.get('switch.switch_1_name') + assert switch_1 is not None + assert switch_1.state == 'on' + switch_3 = hass.states.get('switch.switch_3_name') + assert switch_3 is not None + assert switch_3.state == 'on' + + hass.data[deconz.DOMAIN].api.lights['1'].async_update({}) + + await hass.services.async_call('switch', 'turn_on', { + 'entity_id': 'switch.switch_1_name' + }, blocking=True) + await hass.services.async_call('switch', 'turn_off', { + 'entity_id': 'switch.switch_1_name' + }, blocking=True) + + await hass.services.async_call('switch', 'turn_on', { + 'entity_id': 'switch.switch_3_name' + }, blocking=True) + await hass.services.async_call('switch', 'turn_off', { + 'entity_id': 'switch.switch_3_name' + }, blocking=True) + async def test_add_new_switch(hass): """Test successful creation of switch entity.""" - data = {} - await setup_bridge(hass, data) + await setup_gateway(hass, {}) switch = Mock() switch.name = 'name' switch.type = "Smart plug" switch.register_async_callback = Mock() async_dispatcher_send(hass, 'deconz_new_light', [switch]) await hass.async_block_till_done() - assert "switch.name" in hass.data[deconz.DATA_DECONZ_ID] + assert "switch.name" in hass.data[deconz.DOMAIN].deconz_ids async def test_unsupported_switch(hass): """Test that unsupported switches are not created.""" - await setup_bridge(hass, {"lights": UNSUPPORTED_SWITCH}) + await setup_gateway(hass, {"lights": UNSUPPORTED_SWITCH}) assert len(hass.states.async_all()) == 0 + + +async def test_unload_switch(hass): + """Test that it works to unload switch entities.""" + await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES}) + + await hass.data[deconz.DOMAIN].async_reset() + + assert len(hass.states.async_all()) == 1 From 3d1a324f33493ba6477e6caf61248a0a6160b7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 1 Nov 2018 09:20:30 +0100 Subject: [PATCH 150/230] Add functionality to the version sensor (#18067) * Added functionality to the version sensor. * Corrected typo. * Change default name to not cause a breaking change. * Use vol.lower in the schema. * Add missing blank line. * Change order of cv.string and vol.Lower. --- homeassistant/components/sensor/version.py | 78 +++++++++++++++++++--- requirements_all.txt | 3 + 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/sensor/version.py b/homeassistant/components/sensor/version.py index eba4b1b8350..b71ae158181 100644 --- a/homeassistant/components/sensor/version.py +++ b/homeassistant/components/sensor/version.py @@ -1,54 +1,110 @@ """ -Support for displaying the current version of Home Assistant. +Sensor that can display the current Home Assistant versions. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.version/ """ import logging +from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import __version__, CONF_NAME +from homeassistant.const import CONF_NAME, CONF_SOURCE from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['pyhaversion==2.0.1'] _LOGGER = logging.getLogger(__name__) +CONF_BETA = 'beta' +CONF_IMAGE = 'image' + +DEFAULT_IMAGE = 'default' DEFAULT_NAME = "Current Version" +DEFAULT_SOURCE = 'local' + +ICON = 'mdi:package-up' + +TIME_BETWEEN_UPDATES = timedelta(minutes=5) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_BETA, default=False): cv.boolean, + vol.Optional(CONF_IMAGE, default=DEFAULT_IMAGE): vol.All(cv.string, + vol.Lower), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_SOURCE, default=DEFAULT_SOURCE): vol.All(cv.string, + vol.Lower), }) async def async_setup_platform( hass, config, async_add_entities, discovery_info=None): """Set up the Version sensor platform.""" + from pyhaversion import Version + beta = config.get(CONF_BETA) + image = config.get(CONF_IMAGE) name = config.get(CONF_NAME) + source = config.get(CONF_SOURCE) - async_add_entities([VersionSensor(name)]) + session = async_get_clientsession(hass) + if beta: + branch = 'beta' + else: + branch = 'stable' + haversion = VersionData(Version(hass.loop, session, branch, image), source) + + async_add_entities([VersionSensor(haversion, name)], True) class VersionSensor(Entity): """Representation of a Home Assistant version sensor.""" - def __init__(self, name): + def __init__(self, haversion, name): """Initialize the Version sensor.""" + self.haversion = haversion self._name = name - self._state = __version__ + self._state = None + + async def async_update(self): + """Get the latest version information.""" + await self.haversion.async_update() @property def name(self): """Return the name of the sensor.""" return self._name - @property - def should_poll(self): - """No polling needed.""" - return False - @property def state(self): """Return the state of the sensor.""" - return self._state + return self.haversion.api.version + + @property + def device_state_attributes(self): + """Return attributes for the sensor.""" + return self.haversion.api.version_data + + +class VersionData: + """Get the latest data and update the states.""" + + def __init__(self, api, source): + """Initialize the data object.""" + self.api = api + self.source = source + + @Throttle(TIME_BETWEEN_UPDATES) + async def async_update(self): + """Get the latest version information.""" + if self.source == 'pypi': + await self.api.get_pypi_version() + elif self.source == 'hassio': + await self.api.get_hassio_version() + elif self.source == 'docker': + await self.api.get_docker_version() + else: + await self.api.get_local_version() diff --git a/requirements_all.txt b/requirements_all.txt index 45305c26e3b..d6c8965d31a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -919,6 +919,9 @@ pygtfs-homeassistant==0.1.3.dev0 # homeassistant.components.remote.harmony pyharmony==1.0.20 +# homeassistant.components.sensor.version +pyhaversion==2.0.1 + # homeassistant.components.binary_sensor.hikvision pyhik==0.1.8 From 32ee4f07146a0cf0e6c876476c44d89c956e611f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Thu, 1 Nov 2018 09:24:25 +0100 Subject: [PATCH 151/230] remove schedule_update_ha_state from mill (#18080) * remove schedule_update_ha_state from mill * remove return --- homeassistant/components/climate/mill.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/climate/mill.py b/homeassistant/components/climate/mill.py index 3e8955e2be6..a533cc37fd3 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -214,5 +214,3 @@ class MillHeater(ClimateDevice): await self.async_turn_off() else: _LOGGER.error("Unrecognized operation mode: %s", operation_mode) - return - self.schedule_update_ha_state() From f516550f9f2926da96898156e2d397e25a90e69f Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Thu, 1 Nov 2018 01:28:23 -0700 Subject: [PATCH 152/230] Fix camera mjpeg stream handling (#18076) * Fix handle_async_mjpeg_stream * Lint --- homeassistant/components/camera/__init__.py | 3 ++- homeassistant/components/camera/amcrest.py | 23 +++++++++++---------- homeassistant/components/camera/arlo.py | 10 +++++---- homeassistant/components/camera/canary.py | 10 +++++---- homeassistant/components/camera/mjpeg.py | 3 +-- homeassistant/components/camera/onvif.py | 10 +++++---- homeassistant/components/camera/ring.py | 10 +++++---- homeassistant/components/camera/synology.py | 2 +- homeassistant/components/camera/xiaomi.py | 10 +++++---- homeassistant/components/camera/yi.py | 10 +++++---- 10 files changed, 52 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 5897d972b31..0463b172d7a 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -299,7 +299,8 @@ class Camera(Entity): a direct stream from the camera. This method must be run in the event loop. """ - await self.handle_async_still_stream(request, self.frame_interval) + return await self.handle_async_still_stream( + request, self.frame_interval) @property def state(self): diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py index 55a9c2e4294..4ba527b4805 100644 --- a/homeassistant/components/camera/amcrest.py +++ b/homeassistant/components/camera/amcrest.py @@ -59,8 +59,7 @@ class AmcrestCam(Camera): """Return an MJPEG stream.""" # The snapshot implementation is handled by the parent class if self._stream_source == STREAM_SOURCE_LIST['snapshot']: - await super().handle_async_mjpeg_stream(request) - return + return await super().handle_async_mjpeg_stream(request) if self._stream_source == STREAM_SOURCE_LIST['mjpeg']: # stream an MJPEG image stream directly from the camera @@ -69,20 +68,22 @@ class AmcrestCam(Camera): stream_coro = websession.get( streaming_url, auth=self._token, timeout=TIMEOUT) - await async_aiohttp_proxy_web(self.hass, request, stream_coro) + return await async_aiohttp_proxy_web( + self.hass, request, stream_coro) - else: - # streaming via fmpeg - from haffmpeg import CameraMjpeg + # streaming via ffmpeg + from haffmpeg import CameraMjpeg - streaming_url = self._camera.rtsp_url(typeno=self._resolution) - stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) - await stream.open_camera( - streaming_url, extra_cmd=self._ffmpeg_arguments) + streaming_url = self._camera.rtsp_url(typeno=self._resolution) + stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) + await stream.open_camera( + streaming_url, extra_cmd=self._ffmpeg_arguments) - await async_aiohttp_proxy_stream( + try: + return await async_aiohttp_proxy_stream( self.hass, request, stream, 'multipart/x-mixed-replace;boundary=ffserver') + finally: await stream.close() @property diff --git a/homeassistant/components/camera/arlo.py b/homeassistant/components/camera/arlo.py index af931c74cfa..d56616218e7 100644 --- a/homeassistant/components/camera/arlo.py +++ b/homeassistant/components/camera/arlo.py @@ -101,10 +101,12 @@ class ArloCam(Camera): await stream.open_camera( video.video_url, extra_cmd=self._ffmpeg_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() @property def name(self): diff --git a/homeassistant/components/camera/canary.py b/homeassistant/components/camera/canary.py index b9951d8efa2..7a83e2da4d1 100644 --- a/homeassistant/components/camera/canary.py +++ b/homeassistant/components/camera/canary.py @@ -98,10 +98,12 @@ class CanaryCamera(Camera): self._live_stream_session.live_stream_url, extra_cmd=self._ffmpeg_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) def renew_live_stream_session(self): diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 1df06b546cd..9db7c138182 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -134,8 +134,7 @@ class MjpegCamera(Camera): """Generate an HTTP MJPEG stream from the camera.""" # aiohttp don't support DigestAuth -> Fallback if self._authentication == HTTP_DIGEST_AUTHENTICATION: - await super().handle_async_mjpeg_stream(request) - return + return await super().handle_async_mjpeg_stream(request) # connect to stream websession = async_get_clientsession(self.hass) diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py index 2576dfa7f92..c418f68a260 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -221,10 +221,12 @@ class ONVIFHassCamera(Camera): await stream.open_camera( self._input, extra_cmd=self._ffmpeg_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() @property def name(self): diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/camera/ring.py index eafb3066e48..ad351fb59cf 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/camera/ring.py @@ -139,10 +139,12 @@ class RingCam(Camera): await stream.open_camera( self._video_url, extra_cmd=self._ffmpeg_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() @property def should_poll(self): diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index b504fe34d86..b094cf98edf 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -92,7 +92,7 @@ class SynologyCamera(Camera): websession = async_get_clientsession(self.hass, self._verify_ssl) stream_coro = websession.get(streaming_url) - await async_aiohttp_proxy_web(self.hass, request, stream_coro) + return await async_aiohttp_proxy_web(self.hass, request, stream_coro) @property def name(self): diff --git a/homeassistant/components/camera/xiaomi.py b/homeassistant/components/camera/xiaomi.py index 3d6b51cf229..916a4cb9a90 100644 --- a/homeassistant/components/camera/xiaomi.py +++ b/homeassistant/components/camera/xiaomi.py @@ -158,7 +158,9 @@ class XiaomiCamera(Camera): await stream.open_camera( self._last_url, extra_cmd=self._extra_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() diff --git a/homeassistant/components/camera/yi.py b/homeassistant/components/camera/yi.py index f3800ee0648..22311510ede 100644 --- a/homeassistant/components/camera/yi.py +++ b/homeassistant/components/camera/yi.py @@ -144,7 +144,9 @@ class YiCamera(Camera): await stream.open_camera( self._last_url, extra_cmd=self._extra_arguments) - await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') - await stream.close() + try: + return await async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + finally: + await stream.close() From 329d128e032b35efe04a0a3d2b3799bacafdeab8 Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz Date: Thu, 1 Nov 2018 10:40:31 +0200 Subject: [PATCH 153/230] Change test to parametrized test using pytest (#18047) --- .../components/sensor/test_jewish_calendar.py | 239 +++++------------- 1 file changed, 59 insertions(+), 180 deletions(-) diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py index 02539f8503e..47874d1da42 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -1,9 +1,10 @@ """The tests for the Jewish calendar sensor platform.""" -import unittest from datetime import time from datetime import datetime as dt from unittest.mock import patch +import pytest + from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.util.dt import get_time_zone, set_default_time_zone from homeassistant.setup import setup_component @@ -11,17 +12,14 @@ from homeassistant.components.sensor.jewish_calendar import JewishCalSensor from tests.common import get_test_home_assistant -class TestJewishCalenderSensor(unittest.TestCase): +class TestJewishCalenderSensor(): """Test the Jewish Calendar sensor.""" - TEST_LATITUDE = 31.778 - TEST_LONGITUDE = 35.235 - - def setUp(self): + def setup_method(self, method): """Set up things to run when tests begin.""" self.hass = get_test_home_assistant() - def tearDown(self): + def teardown_method(self, method): """Stop everything that was started.""" self.hass.stop() # Reset the default timezone, so we don't affect other tests @@ -62,182 +60,63 @@ class TestJewishCalenderSensor(unittest.TestCase): assert setup_component(self.hass, 'sensor', config) - def test_jewish_calendar_sensor_date_output(self): - """Test Jewish calendar sensor date output.""" - test_time = dt(2018, 9, 3) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE + test_params = [ + (dt(2018, 9, 3), 'UTC', 31.778, 35.235, "english", "date", + False, "23 Elul 5778"), + (dt(2018, 9, 3), 'UTC', 31.778, 35.235, "hebrew", "date", + False, "כ\"ג באלול ה\' תשע\"ח"), + (dt(2018, 9, 10), 'UTC', 31.778, 35.235, "hebrew", "holiday_name", + False, "א\' ראש השנה"), + (dt(2018, 9, 10), 'UTC', 31.778, 35.235, "english", "holiday_name", + False, "Rosh Hashana I"), + (dt(2018, 9, 10), 'UTC', 31.778, 35.235, "english", "holyness", + False, 1), + (dt(2018, 9, 8), 'UTC', 31.778, 35.235, "hebrew", "weekly_portion", + False, "פרשת נצבים"), + (dt(2018, 9, 8), 'America/New_York', 40.7128, -74.0060, "hebrew", + "first_stars", True, time(19, 48)), + (dt(2018, 9, 8), "Asia/Jerusalem", 31.778, 35.235, "hebrew", + "first_stars", False, time(19, 21)), + (dt(2018, 10, 14), "Asia/Jerusalem", 31.778, 35.235, "hebrew", + "weekly_portion", False, "פרשת לך לך"), + (dt(2018, 10, 14, 17, 0, 0), "Asia/Jerusalem", 31.778, 35.235, + "hebrew", "date", False, "ה\' בחשון ה\' תשע\"ט"), + (dt(2018, 10, 14, 19, 0, 0), "Asia/Jerusalem", 31.778, 35.235, + "hebrew", "date", False, "ו\' בחשון ה\' תשע\"ט") + ] + + test_ids = [ + "date_output", + "date_output_hebrew", + "holiday_name", + "holiday_name_english", + "holyness", + "torah_reading", + "first_stars_ny", + "first_stars_jerusalem", + "torah_reading_weekday", + "date_before_sunset", + "date_after_sunset" + ] + + @pytest.mark.parametrize(["time", "tzname", "latitude", "longitude", + "language", "sensor", "diaspora", "result"], + test_params, ids=test_ids) + def test_jewish_calendar_sensor(self, time, tzname, latitude, longitude, + language, sensor, diaspora, result): + """Test Jewish calendar sensor output.""" + tz = get_time_zone(tzname) + set_default_time_zone(tz) + test_time = tz.localize(time) + self.hass.config.latitude = latitude + self.hass.config.longitude = longitude sensor = JewishCalSensor( - name='test', language='english', sensor_type='date', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) + name='test', language=language, sensor_type=sensor, + latitude=latitude, longitude=longitude, + timezone=tz, diaspora=diaspora) sensor.hass = self.hass with patch('homeassistant.util.dt.now', return_value=test_time): run_coroutine_threadsafe( sensor.async_update(), self.hass.loop).result() - assert sensor.state == '23 Elul 5778' - - def test_jewish_calendar_sensor_date_output_hebrew(self): - """Test Jewish calendar sensor date output in hebrew.""" - test_time = dt(2018, 9, 3) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='date', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "כ\"ג באלול ה\' תשע\"ח" - - def test_jewish_calendar_sensor_holiday_name(self): - """Test Jewish calendar sensor holiday name output in hebrew.""" - test_time = dt(2018, 9, 10) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='holiday_name', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "א\' ראש השנה" - - def test_jewish_calendar_sensor_holiday_name_english(self): - """Test Jewish calendar sensor holiday name output in english.""" - test_time = dt(2018, 9, 10) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='english', sensor_type='holiday_name', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "Rosh Hashana I" - - def test_jewish_calendar_sensor_holyness(self): - """Test Jewish calendar sensor holyness value.""" - test_time = dt(2018, 9, 10) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='holyness', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == 1 - - def test_jewish_calendar_sensor_torah_reading(self): - """Test Jewish calendar sensor torah reading in hebrew.""" - test_time = dt(2018, 9, 8) - set_default_time_zone(get_time_zone('UTC')) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='weekly_portion', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("UTC"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "פרשת נצבים" - - def test_jewish_calendar_sensor_first_stars_ny(self): - """Test Jewish calendar sensor first stars time in NY, US.""" - test_time = dt(2018, 9, 8) - set_default_time_zone(get_time_zone('America/New_York')) - self.hass.config.latitude = 40.7128 - self.hass.config.longitude = -74.0060 - # self.hass.config.time_zone = get_time_zone("America/New_York") - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='first_stars', - latitude=40.7128, longitude=-74.0060, - timezone=get_time_zone("America/New_York"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == time(19, 48) - - def test_jewish_calendar_sensor_first_stars_jerusalem(self): - """Test Jewish calendar sensor first stars time in Jerusalem, IL.""" - set_default_time_zone(get_time_zone('Asia/Jerusalem')) - test_time = dt(2018, 9, 8) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='first_stars', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == time(19, 21) - - def test_jewish_calendar_sensor_torah_reading_weekday(self): - """Test the sensor showing torah reading also on weekdays.""" - set_default_time_zone(get_time_zone('Asia/Jerusalem')) - test_time = dt(2018, 10, 14) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='weekly_portion', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "פרשת לך לך" - - def test_jewish_calendar_sensor_date_before_sunset(self): - """Test the sensor showing the correct date before sunset.""" - tz = get_time_zone('Asia/Jerusalem') - set_default_time_zone(tz) - test_time = tz.localize(dt(2018, 10, 14, 17, 0, 0)) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='date', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "ה\' בחשון ה\' תשע\"ט" - - def test_jewish_calendar_sensor_date_after_sunset(self): - """Test the sensor showing the correct date after sunset.""" - tz = get_time_zone('Asia/Jerusalem') - set_default_time_zone(tz) - test_time = tz.localize(dt(2018, 10, 14, 19, 0, 0)) - self.hass.config.latitude = self.TEST_LATITUDE - self.hass.config.longitude = self.TEST_LONGITUDE - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='date', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone=get_time_zone("Asia/Jerusalem"), diaspora=False) - sensor.hass = self.hass - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - assert sensor.state == "ו\' בחשון ה\' תשע\"ט" + assert sensor.state == result From 4163889c6bf165fd94aa9e5c43883fa6bfec432a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 1 Nov 2018 09:44:38 +0100 Subject: [PATCH 154/230] Add view commands to Lovelace (#18063) * Add get and update view command * Add add view command * Add move view command * Add delete command * lint --- homeassistant/components/lovelace/__init__.py | 190 +++++++++++++++++- tests/components/lovelace/test_init.py | 181 +++++++++++++++++ 2 files changed, 369 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index d21dc3867d8..540fb601a90 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -21,14 +21,20 @@ FORMAT_JSON = 'json' OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' - WS_TYPE_MIGRATE_CONFIG = 'lovelace/config/migrate' + WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' WS_TYPE_ADD_CARD = 'lovelace/config/card/add' WS_TYPE_MOVE_CARD = 'lovelace/config/card/move' WS_TYPE_DELETE_CARD = 'lovelace/config/card/delete' +WS_TYPE_GET_VIEW = 'lovelace/config/view/get' +WS_TYPE_UPDATE_VIEW = 'lovelace/config/view/update' +WS_TYPE_ADD_VIEW = 'lovelace/config/view/add' +WS_TYPE_MOVE_VIEW = 'lovelace/config/view/move' +WS_TYPE_DELETE_VIEW = 'lovelace/config/view/delete' + SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, OLD_WS_TYPE_GET_LOVELACE_UI), @@ -74,6 +80,40 @@ SCHEMA_DELETE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('card_id'): str, }) +SCHEMA_GET_VIEW = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_GET_VIEW, + vol.Required('view_id'): str, + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), +}) + +SCHEMA_UPDATE_VIEW = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_UPDATE_VIEW, + vol.Required('view_id'): str, + vol.Required('view_config'): vol.Any(str, Dict), + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), +}) + +SCHEMA_ADD_VIEW = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_ADD_VIEW, + vol.Required('view_config'): vol.Any(str, Dict), + vol.Optional('position'): int, + vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, + FORMAT_YAML), +}) + +SCHEMA_MOVE_VIEW = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_MOVE_VIEW, + vol.Required('view_id'): str, + vol.Required('new_position'): int, +}) + +SCHEMA_DELETE_VIEW = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_DELETE_VIEW, + vol.Required('view_id'): str, +}) + class CardNotFoundError(HomeAssistantError): """Card not found in data.""" @@ -156,6 +196,7 @@ def update_card(fname: str, card_id: str, card_config: str, continue if data_format == FORMAT_YAML: card_config = yaml.yaml_to_object(card_config) + card.clear() card.update(card_config) yaml.save_yaml(fname, config) return @@ -234,7 +275,7 @@ def move_card_view(fname: str, card_id: str, view_id: str, yaml.save_yaml(fname, config) -def delete_card(fname: str, card_id: str, position: int = None) -> None: +def delete_card(fname: str, card_id: str) -> None: """Delete a card from view.""" config = yaml.load_yaml(fname, True) for view in config.get('views', []): @@ -250,6 +291,85 @@ def delete_card(fname: str, card_id: str, position: int = None) -> None: "Card with ID: {} was not found in {}.".format(card_id, fname)) +def get_view(fname: str, view_id: str, data_format: str = FORMAT_YAML) -> None: + """Get view without it's cards.""" + round_trip = data_format == FORMAT_YAML + config = yaml.load_yaml(fname, round_trip) + for view in config.get('views', []): + if str(view.get('id', '')) != view_id: + continue + del view['cards'] + if data_format == FORMAT_YAML: + return yaml.object_to_yaml(view) + return view + + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + + +def update_view(fname: str, view_id: str, view_config, data_format: + str = FORMAT_YAML) -> None: + """Update view.""" + config = yaml.load_yaml(fname, True) + for view in config.get('views', []): + if str(view.get('id', '')) != view_id: + continue + if data_format == FORMAT_YAML: + view_config = yaml.yaml_to_object(view_config) + view_config['cards'] = view.get('cards', []) + view.clear() + view.update(view_config) + yaml.save_yaml(fname, config) + return + + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + + +def add_view(fname: str, view_config: str, + position: int = None, data_format: str = FORMAT_YAML) -> None: + """Add a view.""" + config = yaml.load_yaml(fname, True) + views = config.get('views', []) + if data_format == FORMAT_YAML: + view_config = yaml.yaml_to_object(view_config) + if position is None: + views.append(view_config) + else: + views.insert(position, view_config) + yaml.save_yaml(fname, config) + + +def move_view(fname: str, view_id: str, position: int) -> None: + """Move a view to a different position.""" + config = yaml.load_yaml(fname, True) + views = config.get('views', []) + for view in views: + if str(view.get('id', '')) != view_id: + continue + views.insert(position, views.pop(views.index(view))) + yaml.save_yaml(fname, config) + return + + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + + +def delete_view(fname: str, view_id: str) -> None: + """Delete a view.""" + config = yaml.load_yaml(fname, True) + views = config.get('views', []) + for view in views: + if str(view.get('id', '')) != view_id: + continue + views.pop(views.index(view)) + yaml.save_yaml(fname, config) + return + + raise ViewNotFoundError( + "View with ID: {} was not found in {}.".format(view_id, fname)) + + async def async_setup(hass, config): """Set up the Lovelace commands.""" # Backwards compat. Added in 0.80. Remove after 0.85 @@ -285,6 +405,26 @@ async def async_setup(hass, config): WS_TYPE_DELETE_CARD, websocket_lovelace_delete_card, SCHEMA_DELETE_CARD) + hass.components.websocket_api.async_register_command( + WS_TYPE_GET_VIEW, websocket_lovelace_get_view, + SCHEMA_GET_VIEW) + + hass.components.websocket_api.async_register_command( + WS_TYPE_UPDATE_VIEW, websocket_lovelace_update_view, + SCHEMA_UPDATE_VIEW) + + hass.components.websocket_api.async_register_command( + WS_TYPE_ADD_VIEW, websocket_lovelace_add_view, + SCHEMA_ADD_VIEW) + + hass.components.websocket_api.async_register_command( + WS_TYPE_MOVE_VIEW, websocket_lovelace_move_view, + SCHEMA_MOVE_VIEW) + + hass.components.websocket_api.async_register_command( + WS_TYPE_DELETE_VIEW, websocket_lovelace_delete_view, + SCHEMA_DELETE_VIEW) + return True @@ -385,3 +525,49 @@ async def websocket_lovelace_delete_card(hass, connection, msg): return await hass.async_add_executor_job( delete_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id']) + + +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_get_view(hass, connection, msg): + """Send lovelace view config over websocket config.""" + return await hass.async_add_executor_job( + get_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id'], + msg.get('format', FORMAT_YAML)) + + +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_update_view(hass, connection, msg): + """Receive lovelace card config over websocket and save.""" + return await hass.async_add_executor_job( + update_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_id'], msg['view_config'], msg.get('format', FORMAT_YAML)) + + +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_add_view(hass, connection, msg): + """Add new view over websocket and save.""" + return await hass.async_add_executor_job( + add_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_config'], msg.get('position'), + msg.get('format', FORMAT_YAML)) + + +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_move_view(hass, connection, msg): + """Move view to different position over websocket and save.""" + return await hass.async_add_executor_job( + move_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_id'], msg['new_position']) + + +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_delete_view(hass, connection, msg): + """Delete card from lovelace over websocket and save.""" + return await hass.async_add_executor_job( + delete_view, hass.config.path(LOVELACE_CONFIG_FILE), + msg['view_id']) diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py index 690c4976565..e296d14c6f8 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -565,3 +565,184 @@ async def test_lovelace_delete_card(hass, hass_ws_client): assert msg['id'] == 5 assert msg['type'] == TYPE_RESULT assert msg['success'] + + +async def test_lovelace_get_view(hass, hass_ws_client): + """Test get_view command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/get', + 'view_id': 'example', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + assert "".join(msg['result'].split()) == "".join('title: Example\n # \ + Optional unique id for direct\ + access /lovelace/${id}\nid: example\n # Optional\ + background (overwrites the global background).\n\ + background: radial-gradient(crimson, skyblue)\n\ + # Each view can have a different theme applied.\n\ + theme: dark-mode\n'.split()) + + +async def test_lovelace_get_view_not_found(hass, hass_ws_client): + """Test get_card command cannot find card.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)): + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/get', + 'view_id': 'not_found', + }) + msg = await client.receive_json() + + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] is False + assert msg['error']['code'] == 'view_not_found' + + +async def test_lovelace_update_view(hass, hass_ws_client): + """Test update_view command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + origyaml = yaml.load(TEST_YAML_A) + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=origyaml), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/update', + 'view_id': 'example', + 'view_config': 'id: example2\ntitle: New title\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + orig_view = origyaml.mlget(['views', 0], list_ok=True) + new_view = result.mlget(['views', 0], list_ok=True) + assert new_view['title'] == 'New title' + assert new_view['cards'] == orig_view['cards'] + assert 'theme' not in new_view + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_add_view(hass, hass_ws_client): + """Test add_view command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/add', + 'view_config': 'id: test\ntitle: added\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 2, 'title'], + list_ok=True) == 'added' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_add_view_position(hass, hass_ws_client): + """Test add_view command with position.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/add', + 'position': 0, + 'view_config': 'id: test\ntitle: added\n', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 0, 'title'], + list_ok=True) == 'added' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_move_view_position(hass, hass_ws_client): + """Test move_view command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/move', + 'view_id': 'example', + 'new_position': 1, + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + assert result.mlget(['views', 1, 'title'], + list_ok=True) == 'Example' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] + + +async def test_lovelace_delete_view(hass, hass_ws_client): + """Test delete_card command.""" + await async_setup_component(hass, 'lovelace') + client = await hass_ws_client(hass) + yaml = YAML(typ='rt') + + with patch('homeassistant.util.ruamel_yaml.load_yaml', + return_value=yaml.load(TEST_YAML_A)), \ + patch('homeassistant.util.ruamel_yaml.save_yaml') \ + as save_yaml_mock: + await client.send_json({ + 'id': 5, + 'type': 'lovelace/config/view/delete', + 'view_id': 'example', + }) + msg = await client.receive_json() + + result = save_yaml_mock.call_args_list[0][0][1] + views = result.get('views', []) + assert len(views) == 1 + assert views[0]['title'] == 'Second view' + assert msg['id'] == 5 + assert msg['type'] == TYPE_RESULT + assert msg['success'] From bfa86b8138b0ab42ee07f53f9e491f151a1f18a6 Mon Sep 17 00:00:00 2001 From: thoscut Date: Thu, 1 Nov 2018 09:48:11 +0100 Subject: [PATCH 155/230] Add message template support for alert component (#17516) * Add problem text to message if available * Revert "Add problem text to message if available" This reverts commit 7be519bf7fb58356b80bfa459ca4ae229ccd483c. * Cleanup setup * Add message template support * Fix for failing test * Added tests * Refactor changes * Fix lint violation * Fix failing tests * Unify handling for message and done_message parameter and sending function * Update tests * Fix lint warnings --- homeassistant/components/alert.py | 100 +++++++++++++++++++++--------- tests/components/test_alert.py | 63 ++++++++++++++++++- 2 files changed, 130 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py index e224351f9db..759a2185047 100644 --- a/homeassistant/components/alert.py +++ b/homeassistant/components/alert.py @@ -24,23 +24,25 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'alert' ENTITY_ID_FORMAT = DOMAIN + '.{}' -CONF_DONE_MESSAGE = 'done_message' CONF_CAN_ACK = 'can_acknowledge' CONF_NOTIFIERS = 'notifiers' CONF_REPEAT = 'repeat' CONF_SKIP_FIRST = 'skip_first' +CONF_ALERT_MESSAGE = 'message' +CONF_DONE_MESSAGE = 'done_message' DEFAULT_CAN_ACK = True DEFAULT_SKIP_FIRST = False ALERT_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_DONE_MESSAGE): cv.string, vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_STATE, default=STATE_ON): cv.string, vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]), vol.Required(CONF_CAN_ACK, default=DEFAULT_CAN_ACK): cv.boolean, vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean, + vol.Optional(CONF_ALERT_MESSAGE): cv.template, + vol.Optional(CONF_DONE_MESSAGE): cv.template, vol.Required(CONF_NOTIFIERS): cv.ensure_list}) CONFIG_SCHEMA = vol.Schema({ @@ -62,31 +64,47 @@ def is_on(hass, entity_id): async def async_setup(hass, config): """Set up the Alert component.""" - alerts = config.get(DOMAIN) - all_alerts = {} + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + if not cfg: + cfg = {} + + name = cfg.get(CONF_NAME) + watched_entity_id = cfg.get(CONF_ENTITY_ID) + alert_state = cfg.get(CONF_STATE) + repeat = cfg.get(CONF_REPEAT) + skip_first = cfg.get(CONF_SKIP_FIRST) + message_template = cfg.get(CONF_ALERT_MESSAGE) + done_message_template = cfg.get(CONF_DONE_MESSAGE) + notifiers = cfg.get(CONF_NOTIFIERS) + can_ack = cfg.get(CONF_CAN_ACK) + + entities.append(Alert(hass, object_id, name, + watched_entity_id, alert_state, repeat, + skip_first, message_template, + done_message_template, notifiers, + can_ack)) + + if not entities: + return False async def async_handle_alert_service(service_call): """Handle calls to alert services.""" alert_ids = service.extract_entity_ids(hass, service_call) for alert_id in alert_ids: - alert = all_alerts[alert_id] - alert.async_set_context(service_call.context) - if service_call.service == SERVICE_TURN_ON: - await alert.async_turn_on() - elif service_call.service == SERVICE_TOGGLE: - await alert.async_toggle() - else: - await alert.async_turn_off() + for alert in entities: + if alert.entity_id != alert_id: + continue - # Setup alerts - for entity_id, alert in alerts.items(): - entity = Alert(hass, entity_id, - alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE), - alert[CONF_ENTITY_ID], alert[CONF_STATE], - alert[CONF_REPEAT], alert[CONF_SKIP_FIRST], - alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK]) - all_alerts[entity.entity_id] = entity + alert.async_set_context(service_call.context) + if service_call.service == SERVICE_TURN_ON: + await alert.async_turn_on() + elif service_call.service == SERVICE_TOGGLE: + await alert.async_toggle() + else: + await alert.async_turn_off() # Setup service calls hass.services.async_register( @@ -99,7 +117,7 @@ async def async_setup(hass, config): DOMAIN, SERVICE_TOGGLE, async_handle_alert_service, schema=ALERT_SERVICE_SCHEMA) - tasks = [alert.async_update_ha_state() for alert in all_alerts.values()] + tasks = [alert.async_update_ha_state() for alert in entities] if tasks: await asyncio.wait(tasks, loop=hass.loop) @@ -109,16 +127,25 @@ async def async_setup(hass, config): class Alert(ToggleEntity): """Representation of an alert.""" - def __init__(self, hass, entity_id, name, done_message, watched_entity_id, - state, repeat, skip_first, notifiers, can_ack): + def __init__(self, hass, entity_id, name, watched_entity_id, + state, repeat, skip_first, message_template, + done_message_template, notifiers, can_ack): """Initialize the alert.""" self.hass = hass self._name = name self._alert_state = state self._skip_first = skip_first + + self._message_template = message_template + if self._message_template is not None: + self._message_template.hass = hass + + self._done_message_template = done_message_template + if self._done_message_template is not None: + self._done_message_template.hass = hass + self._notifiers = notifiers self._can_ack = can_ack - self._done_message = done_message self._delay = [timedelta(minutes=val) for val in repeat] self._next_delay = 0 @@ -184,7 +211,7 @@ class Alert(ToggleEntity): self._cancel() self._ack = False self._firing = False - if self._done_message and self._send_done_message: + if self._send_done_message: await self._notify_done_message() self.async_schedule_update_ha_state() @@ -204,18 +231,31 @@ class Alert(ToggleEntity): if not self._ack: _LOGGER.info("Alerting: %s", self._name) self._send_done_message = True - for target in self._notifiers: - await self.hass.services.async_call( - DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name}) + + if self._message_template is not None: + message = self._message_template.async_render() + else: + message = self._name + + await self._send_notification_message(message) await self._schedule_notify() async def _notify_done_message(self, *args): """Send notification of complete alert.""" - _LOGGER.info("Alerting: %s", self._done_message) + _LOGGER.info("Alerting: %s", self._done_message_template) self._send_done_message = False + + if self._done_message_template is None: + return + + message = self._done_message_template.async_render() + + await self._send_notification_message(message) + + async def _send_notification_message(self, message): for target in self._notifiers: await self.hass.services.async_call( - DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message}) + DOMAIN_NOTIFY, target, {ATTR_MESSAGE: message}) async def async_turn_on(self, **kwargs): """Async Unacknowledge alert.""" diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index d7aaa3dd038..76610421563 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -17,19 +17,21 @@ from tests.common import get_test_home_assistant NAME = "alert_test" DONE_MESSAGE = "alert_gone" NOTIFIER = 'test' +TEMPLATE = "{{ states.sensor.test.entity_id }}" +TEST_ENTITY = "sensor.test" TEST_CONFIG = \ {alert.DOMAIN: { NAME: { CONF_NAME: NAME, alert.CONF_DONE_MESSAGE: DONE_MESSAGE, - CONF_ENTITY_ID: "sensor.test", + CONF_ENTITY_ID: TEST_ENTITY, CONF_STATE: STATE_ON, alert.CONF_REPEAT: 30, alert.CONF_SKIP_FIRST: False, alert.CONF_NOTIFIERS: [NOTIFIER]} }} -TEST_NOACK = [NAME, NAME, DONE_MESSAGE, "sensor.test", - STATE_ON, [30], False, NOTIFIER, False] +TEST_NOACK = [NAME, NAME, "sensor.test", + STATE_ON, [30], False, None, None, NOTIFIER, False] ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME) @@ -102,6 +104,19 @@ class TestAlert(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() + def _setup_notify(self): + events = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + self.hass.services.register( + notify.DOMAIN, NOTIFIER, record_event) + + return events + def test_is_on(self): """Test is_on method.""" self.hass.states.set(ENTITY_ID, STATE_ON) @@ -228,6 +243,48 @@ class TestAlert(unittest.TestCase): self.hass.block_till_done() assert 2 == len(events) + def test_sending_non_templated_notification(self): + """Test notifications.""" + events = self._setup_notify() + + assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG) + + self.hass.states.set(TEST_ENTITY, STATE_ON) + self.hass.block_till_done() + self.assertEqual(1, len(events)) + last_event = events[-1] + self.assertEqual(last_event.data[notify.ATTR_MESSAGE], NAME) + + def test_sending_templated_notification(self): + """Test templated notification.""" + events = self._setup_notify() + + config = deepcopy(TEST_CONFIG) + config[alert.DOMAIN][NAME][alert.CONF_ALERT_MESSAGE] = TEMPLATE + assert setup_component(self.hass, alert.DOMAIN, config) + + self.hass.states.set(TEST_ENTITY, STATE_ON) + self.hass.block_till_done() + self.assertEqual(1, len(events)) + last_event = events[-1] + self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY) + + def test_sending_templated_done_notification(self): + """Test templated notification.""" + events = self._setup_notify() + + config = deepcopy(TEST_CONFIG) + config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE] = TEMPLATE + assert setup_component(self.hass, alert.DOMAIN, config) + + self.hass.states.set(TEST_ENTITY, STATE_ON) + self.hass.block_till_done() + self.hass.states.set(TEST_ENTITY, STATE_OFF) + self.hass.block_till_done() + self.assertEqual(2, len(events)) + last_event = events[-1] + self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY) + def test_skipfirst(self): """Test skipping first notification.""" config = deepcopy(TEST_CONFIG) From 1c5800d98bd83299581360663fd0d331fc1be2f0 Mon Sep 17 00:00:00 2001 From: Jared Quinn Date: Thu, 1 Nov 2018 19:48:44 +1100 Subject: [PATCH 156/230] Added identifier and name to connect/disconnect events (#18078) * Added identifier and name to connect/disconnect events * Fix indentation from failed tests --- homeassistant/components/keyboard_remote.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote.py index ffc92f1949a..e02c2ee5475 100644 --- a/homeassistant/components/keyboard_remote.py +++ b/homeassistant/components/keyboard_remote.py @@ -4,6 +4,7 @@ Receive signals from a keyboard and use it as a remote control. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/keyboard_remote/ """ +# pylint: disable=import-error import threading import logging import os @@ -89,7 +90,6 @@ class KeyboardRemoteThread(threading.Thread): id_folder = '/dev/input/by-id/' if os.path.isdir(id_folder): - # pylint: disable=import-error from evdev import InputDevice, list_devices device_names = [InputDevice(file_name).name for file_name in list_devices()] @@ -104,7 +104,6 @@ class KeyboardRemoteThread(threading.Thread): def _get_keyboard_device(self): """Get the keyboard device.""" - # pylint: disable=import-error from evdev import InputDevice, list_devices if self.device_name: devices = [InputDevice(file_name) for file_name in list_devices()] @@ -122,7 +121,6 @@ class KeyboardRemoteThread(threading.Thread): def run(self): """Run the loop of the KeyboardRemote.""" - # pylint: disable=import-error from evdev import categorize, ecodes if self.dev is not None: @@ -137,7 +135,13 @@ class KeyboardRemoteThread(threading.Thread): self.dev = self._get_keyboard_device() if self.dev is not None: self.dev.grab() - self.hass.bus.fire(KEYBOARD_REMOTE_CONNECTED) + self.hass.bus.fire( + KEYBOARD_REMOTE_CONNECTED, + { + DEVICE_DESCRIPTOR: self.device_descriptor, + DEVICE_NAME: self.device_name + } + ) _LOGGER.debug("Keyboard re-connected, %s", self.device_id) else: continue @@ -146,7 +150,13 @@ class KeyboardRemoteThread(threading.Thread): event = self.dev.read_one() except IOError: # Keyboard Disconnected self.dev = None - self.hass.bus.fire(KEYBOARD_REMOTE_DISCONNECTED) + self.hass.bus.fire( + KEYBOARD_REMOTE_DISCONNECTED, + { + DEVICE_DESCRIPTOR: self.device_descriptor, + DEVICE_NAME: self.device_name + } + ) _LOGGER.debug("Keyboard disconnected, %s", self.device_id) continue From ab8299b6cf2b4b860042d9c74ac31f157191e7ae Mon Sep 17 00:00:00 2001 From: MatteGary Date: Thu, 1 Nov 2018 10:29:48 +0100 Subject: [PATCH 157/230] Fix in Daikin.py for set swing_mode and speed (#18013) --- homeassistant/components/climate/daikin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/climate/daikin.py index 66e380ad68d..2d4e01aaee9 100644 --- a/homeassistant/components/climate/daikin.py +++ b/homeassistant/components/climate/daikin.py @@ -174,8 +174,10 @@ class DaikinClimate(ClimateDevice): daikin_attr = HA_ATTR_TO_DAIKIN.get(attr) if daikin_attr is not None: - if value in self._list[attr]: + if attr == ATTR_OPERATION_MODE: values[daikin_attr] = HA_STATE_TO_DAIKIN[value] + elif value in self._list[attr]: + values[daikin_attr] = value.lower() else: _LOGGER.error("Invalid value %s for %s", attr, value) From e9f96bfd7fce39fb51e75d45c9d5299a4456edb4 Mon Sep 17 00:00:00 2001 From: akloeckner Date: Thu, 1 Nov 2018 10:37:19 +0100 Subject: [PATCH 158/230] Allow different types to match in pilight (#17922) * Allow different types to match see discussion here: https://github.com/home-assistant/home-assistant/pull/17870 * lint spaces * line length < 79 --- homeassistant/components/binary_sensor/pilight.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/binary_sensor/pilight.py b/homeassistant/components/binary_sensor/pilight.py index abffffe8651..de23baef884 100644 --- a/homeassistant/components/binary_sensor/pilight.py +++ b/homeassistant/components/binary_sensor/pilight.py @@ -37,8 +37,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_VARIABLE): cv.string, vol.Required(CONF_PAYLOAD): vol.Schema(dict), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default='on'): vol.Any( + cv.positive_int, cv.small_float, cv.string), + vol.Optional(CONF_PAYLOAD_OFF, default='off'): vol.Any( + cv.positive_int, cv.small_float, cv.string), vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean, vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int }) From 19ebdf2cf14f5fdeab28892eacc9e5006dadee38 Mon Sep 17 00:00:00 2001 From: Jonathan Keljo Date: Thu, 1 Nov 2018 02:46:11 -0700 Subject: [PATCH 159/230] Add a component for GreenEye Monitor (#16378) * Add a component for GreenEye Monitor [GreenEye Monitor](http://www.brultech.com/greeneye/) is an energy monitor that can monitor emergy usage of individual circuits, count pulses from things like water or gas meters, and monitor temperatures. This component exposes these various sensors in Home Assistant, for both data tracking and automation triggering purposes. * Consolidate sensors * lint * .coveragerc * - cv.ensure_list - DOMAIN, where appropriate - defaults to schema - single invocation of async_load_platform - async_create_task instead of async_add_job - fail if no sensors - monitors required - async_add_entities - call add_devices once - remove unused schema - use properties rather than set fields - move _number and unique_id to GEMSensor - remove unnecessary get(xxx, None) - keep params on one line when possible - new-style string format * Fix `ensure_list` usage, log message * Pass config through --- .coveragerc | 3 + homeassistant/components/greeneye_monitor.py | 171 +++++++++++ .../components/sensor/greeneye_monitor.py | 282 ++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 459 insertions(+) create mode 100644 homeassistant/components/greeneye_monitor.py create mode 100644 homeassistant/components/sensor/greeneye_monitor.py diff --git a/.coveragerc b/.coveragerc index 5727ec1d43a..8cb2b06683b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -126,6 +126,9 @@ omit = homeassistant/components/google.py homeassistant/components/*/google.py + homeassistant/components/greeneye_monitor.py + homeassistant/components/sensor/greeneye_monitor.py + homeassistant/components/habitica/* homeassistant/components/*/habitica.py diff --git a/homeassistant/components/greeneye_monitor.py b/homeassistant/components/greeneye_monitor.py new file mode 100644 index 00000000000..f5c51da88be --- /dev/null +++ b/homeassistant/components/greeneye_monitor.py @@ -0,0 +1,171 @@ +""" +Support for monitoring a GreenEye Monitor energy monitor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/greeneye_monitor/ +""" +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, + CONF_PORT, + CONF_TEMPERATURE_UNIT, + EVENT_HOMEASSISTANT_STOP) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import async_load_platform + +REQUIREMENTS = ['greeneye_monitor==0.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_CHANNELS = 'channels' +CONF_COUNTED_QUANTITY = 'counted_quantity' +CONF_COUNTED_QUANTITY_PER_PULSE = 'counted_quantity_per_pulse' +CONF_MONITOR_SERIAL_NUMBER = 'monitor' +CONF_MONITORS = 'monitors' +CONF_NET_METERING = 'net_metering' +CONF_NUMBER = 'number' +CONF_PULSE_COUNTERS = 'pulse_counters' +CONF_SERIAL_NUMBER = 'serial_number' +CONF_SENSORS = 'sensors' +CONF_SENSOR_TYPE = 'sensor_type' +CONF_TEMPERATURE_SENSORS = 'temperature_sensors' +CONF_TIME_UNIT = 'time_unit' + +DATA_GREENEYE_MONITOR = 'greeneye_monitor' +DOMAIN = 'greeneye_monitor' + +SENSOR_TYPE_CURRENT = 'current_sensor' +SENSOR_TYPE_PULSE_COUNTER = 'pulse_counter' +SENSOR_TYPE_TEMPERATURE = 'temperature_sensor' + +TEMPERATURE_UNIT_CELSIUS = 'C' + +TIME_UNIT_SECOND = 's' +TIME_UNIT_MINUTE = 'min' +TIME_UNIT_HOUR = 'h' + +TEMPERATURE_SENSOR_SCHEMA = vol.Schema({ + vol.Required(CONF_NUMBER): vol.Range(1, 8), + vol.Required(CONF_NAME): cv.string, +}) + +TEMPERATURE_SENSORS_SCHEMA = vol.Schema({ + vol.Required(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, + [TEMPERATURE_SENSOR_SCHEMA]), +}) + +PULSE_COUNTER_SCHEMA = vol.Schema({ + vol.Required(CONF_NUMBER): vol.Range(1, 4), + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_COUNTED_QUANTITY): cv.string, + vol.Optional( + CONF_COUNTED_QUANTITY_PER_PULSE, default=1.0): vol.Coerce(float), + vol.Optional(CONF_TIME_UNIT, default=TIME_UNIT_SECOND): vol.Any( + TIME_UNIT_SECOND, + TIME_UNIT_MINUTE, + TIME_UNIT_HOUR), +}) + +PULSE_COUNTERS_SCHEMA = vol.All(cv.ensure_list, [PULSE_COUNTER_SCHEMA]) + +CHANNEL_SCHEMA = vol.Schema({ + vol.Required(CONF_NUMBER): vol.Range(1, 48), + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_NET_METERING, default=False): cv.boolean, +}) + +CHANNELS_SCHEMA = vol.All(cv.ensure_list, [CHANNEL_SCHEMA]) + +MONITOR_SCHEMA = vol.Schema({ + vol.Required(CONF_SERIAL_NUMBER): cv.positive_int, + vol.Optional(CONF_CHANNELS, default=[]): CHANNELS_SCHEMA, + vol.Optional( + CONF_TEMPERATURE_SENSORS, + default={ + CONF_TEMPERATURE_UNIT: TEMPERATURE_UNIT_CELSIUS, + CONF_SENSORS: [], + }): TEMPERATURE_SENSORS_SCHEMA, + vol.Optional(CONF_PULSE_COUNTERS, default=[]): PULSE_COUNTERS_SCHEMA, +}) + +MONITORS_SCHEMA = vol.All(cv.ensure_list, [MONITOR_SCHEMA]) + +COMPONENT_SCHEMA = vol.Schema({ + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_MONITORS): MONITORS_SCHEMA, +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: COMPONENT_SCHEMA, +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the GreenEye Monitor component.""" + from greeneye import Monitors + + monitors = Monitors() + hass.data[DATA_GREENEYE_MONITOR] = monitors + + server_config = config[DOMAIN] + server = await monitors.start_server(server_config[CONF_PORT]) + + async def close_server(*args): + """Close the monitoring server.""" + await server.close() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_server) + + all_sensors = [] + for monitor_config in server_config[CONF_MONITORS]: + monitor_serial_number = { + CONF_MONITOR_SERIAL_NUMBER: monitor_config[CONF_SERIAL_NUMBER], + } + + channel_configs = monitor_config[CONF_CHANNELS] + for channel_config in channel_configs: + all_sensors.append({ + CONF_SENSOR_TYPE: SENSOR_TYPE_CURRENT, + **monitor_serial_number, + **channel_config, + }) + + sensor_configs = \ + monitor_config[CONF_TEMPERATURE_SENSORS] + if sensor_configs: + temperature_unit = { + CONF_TEMPERATURE_UNIT: sensor_configs[CONF_TEMPERATURE_UNIT], + } + for sensor_config in sensor_configs[CONF_SENSORS]: + all_sensors.append({ + CONF_SENSOR_TYPE: SENSOR_TYPE_TEMPERATURE, + **monitor_serial_number, + **temperature_unit, + **sensor_config, + }) + + counter_configs = monitor_config[CONF_PULSE_COUNTERS] + for counter_config in counter_configs: + all_sensors.append({ + CONF_SENSOR_TYPE: SENSOR_TYPE_PULSE_COUNTER, + **monitor_serial_number, + **counter_config, + }) + + if not all_sensors: + _LOGGER.error("Configuration must specify at least one " + "channel, pulse counter or temperature sensor") + return False + + hass.async_create_task(async_load_platform( + hass, + 'sensor', + DOMAIN, + all_sensors, + config)) + + return True diff --git a/homeassistant/components/sensor/greeneye_monitor.py b/homeassistant/components/sensor/greeneye_monitor.py new file mode 100644 index 00000000000..3793ea7846c --- /dev/null +++ b/homeassistant/components/sensor/greeneye_monitor.py @@ -0,0 +1,282 @@ +""" +Support for the sensors in a GreenEye Monitor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensors.greeneye_monitor_temperature/ +""" +import logging + +from homeassistant.const import CONF_NAME, CONF_TEMPERATURE_UNIT +from homeassistant.helpers.entity import Entity + +from ..greeneye_monitor import ( + CONF_COUNTED_QUANTITY, + CONF_COUNTED_QUANTITY_PER_PULSE, + CONF_MONITOR_SERIAL_NUMBER, + CONF_NET_METERING, + CONF_NUMBER, + CONF_SENSOR_TYPE, + CONF_TIME_UNIT, + DATA_GREENEYE_MONITOR, + SENSOR_TYPE_CURRENT, + SENSOR_TYPE_PULSE_COUNTER, + SENSOR_TYPE_TEMPERATURE, + TIME_UNIT_HOUR, + TIME_UNIT_MINUTE, + TIME_UNIT_SECOND, +) + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['greeneye_monitor'] + +DATA_PULSES = 'pulses' +DATA_WATT_SECONDS = 'watt_seconds' + +UNIT_WATTS = 'W' + +COUNTER_ICON = 'mdi:counter' +CURRENT_SENSOR_ICON = 'mdi:flash' +TEMPERATURE_ICON = 'mdi:thermometer' + + +async def async_setup_platform( + hass, + config, + async_add_entities, + discovery_info=None): + """Set up a single GEM temperature sensor.""" + if not discovery_info: + return + + entities = [] + for sensor in discovery_info: + sensor_type = sensor[CONF_SENSOR_TYPE] + if sensor_type == SENSOR_TYPE_CURRENT: + entities.append(CurrentSensor( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_NET_METERING])) + elif sensor_type == SENSOR_TYPE_PULSE_COUNTER: + entities.append(PulseCounter( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_COUNTED_QUANTITY], + sensor[CONF_TIME_UNIT], + sensor[CONF_COUNTED_QUANTITY_PER_PULSE])) + elif sensor_type == SENSOR_TYPE_TEMPERATURE: + entities.append(TemperatureSensor( + sensor[CONF_MONITOR_SERIAL_NUMBER], + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_TEMPERATURE_UNIT])) + + async_add_entities(entities) + + +class GEMSensor(Entity): + """Base class for GreenEye Monitor sensors.""" + + def __init__(self, monitor_serial_number, name, sensor_type, number): + """Construct the entity.""" + self._monitor_serial_number = monitor_serial_number + self._name = name + self._sensor = None + self._sensor_type = sensor_type + self._number = number + + @property + def should_poll(self): + """GEM pushes changes, so this returns False.""" + return False + + @property + def unique_id(self): + """Return a unique ID for this sensor.""" + return "{serial}-{sensor_type}-{number}".format( + serial=self._monitor_serial_number, + sensor_type=self._sensor_type, + number=self._number, + ) + + @property + def name(self): + """Return the name of the channel.""" + return self._name + + async def async_added_to_hass(self): + """Wait for and connect to the sensor.""" + monitors = self.hass.data[DATA_GREENEYE_MONITOR] + + if not self._try_connect_to_monitor(monitors): + monitors.add_listener(self._on_new_monitor) + + def _on_new_monitor(self, *args): + monitors = self.hass.data[DATA_GREENEYE_MONITOR] + if self._try_connect_to_monitor(monitors): + monitors.remove_listener(self._on_new_monitor) + + async def async_will_remove_from_hass(self): + """Remove listener from the sensor.""" + if self._sensor: + self._sensor.remove_listener(self._schedule_update) + else: + monitors = self.hass.data[DATA_GREENEYE_MONITOR] + monitors.remove_listener(self._on_new_monitor) + + def _try_connect_to_monitor(self, monitors): + monitor = monitors.monitors.get(self._monitor_serial_number) + if not monitor: + return False + + self._sensor = self._get_sensor(monitor) + self._sensor.add_listener(self._schedule_update) + + return True + + def _get_sensor(self, monitor): + raise NotImplementedError() + + def _schedule_update(self): + self.async_schedule_update_ha_state(False) + + +class CurrentSensor(GEMSensor): + """Entity showing power usage on one channel of the monitor.""" + + def __init__(self, monitor_serial_number, number, name, net_metering): + """Construct the entity.""" + super().__init__(monitor_serial_number, name, 'current', number) + self._net_metering = net_metering + + def _get_sensor(self, monitor): + return monitor.channels[self._number - 1] + + @property + def icon(self): + """Return the icon that should represent this sensor in the UI.""" + return CURRENT_SENSOR_ICON + + @property + def unit_of_measurement(self): + """Return the unit of measurement used by this sensor.""" + return UNIT_WATTS + + @property + def state(self): + """Return the current number of watts being used by the channel.""" + if not self._sensor: + return None + + return self._sensor.watts + + @property + def device_state_attributes(self): + """Return total wattseconds in the state dictionary.""" + if not self._sensor: + return None + + if self._net_metering: + watt_seconds = self._sensor.polarized_watt_seconds + else: + watt_seconds = self._sensor.absolute_watt_seconds + + return { + DATA_WATT_SECONDS: watt_seconds + } + + +class PulseCounter(GEMSensor): + """Entity showing rate of change in one pulse counter of the monitor.""" + + def __init__( + self, + monitor_serial_number, + number, + name, + counted_quantity, + time_unit, + counted_quantity_per_pulse): + """Construct the entity.""" + super().__init__(monitor_serial_number, name, 'pulse', number) + self._counted_quantity = counted_quantity + self._counted_quantity_per_pulse = counted_quantity_per_pulse + self._time_unit = time_unit + + def _get_sensor(self, monitor): + return monitor.pulse_counters[self._number - 1] + + @property + def icon(self): + """Return the icon that should represent this sensor in the UI.""" + return COUNTER_ICON + + @property + def state(self): + """Return the current rate of change for the given pulse counter.""" + if not self._sensor or self._sensor.pulses_per_second is None: + return None + + return (self._sensor.pulses_per_second * + self._counted_quantity_per_pulse * + self._seconds_per_time_unit) + + @property + def _seconds_per_time_unit(self): + """Return the number of seconds in the given display time unit.""" + if self._time_unit == TIME_UNIT_SECOND: + return 1 + if self._time_unit == TIME_UNIT_MINUTE: + return 60 + if self._time_unit == TIME_UNIT_HOUR: + return 3600 + + @property + def unit_of_measurement(self): + """Return the unit of measurement for this pulse counter.""" + return "{counted_quantity}/{time_unit}".format( + counted_quantity=self._counted_quantity, + time_unit=self._time_unit, + ) + + @property + def device_state_attributes(self): + """Return total pulses in the data dictionary.""" + if not self._sensor: + return None + + return { + DATA_PULSES: self._sensor.pulses + } + + +class TemperatureSensor(GEMSensor): + """Entity showing temperature from one temperature sensor.""" + + def __init__(self, monitor_serial_number, number, name, unit): + """Construct the entity.""" + super().__init__(monitor_serial_number, name, 'temp', number) + self._unit = unit + + def _get_sensor(self, monitor): + return monitor.temperature_sensors[self._number - 1] + + @property + def icon(self): + """Return the icon that should represent this sensor in the UI.""" + return TEMPERATURE_ICON + + @property + def state(self): + """Return the current temperature being reported by this sensor.""" + if not self._sensor: + return None + + return self._sensor.temperature + + @property + def unit_of_measurement(self): + """Return the unit of measurement for this sensor (user specified).""" + return self._unit diff --git a/requirements_all.txt b/requirements_all.txt index d6c8965d31a..2cce54c5548 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -433,6 +433,9 @@ googlemaps==2.5.1 # homeassistant.components.sensor.gpsd gps3==0.33.3 +# homeassistant.components.greeneye_monitor +greeneye_monitor==0.1 + # homeassistant.components.light.greenwave greenwavereality==0.5.1 From caa48fab13fc674a591c6e39773bcc41b80e9d4f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 11:26:33 +0100 Subject: [PATCH 160/230] Upgrade astral to 1.7.1 --- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 531c4c2e8a6..578cd915fcd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,5 +1,5 @@ aiohttp==3.4.4 -astral==1.6.1 +astral==1.7.1 async_timeout==3.0.1 attrs==18.2.0 bcrypt==3.1.4 diff --git a/requirements_all.txt b/requirements_all.txt index 2cce54c5548..104179ae762 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1,6 +1,6 @@ # Home Assistant core aiohttp==3.4.4 -astral==1.6.1 +astral==1.7.1 async_timeout==3.0.1 attrs==18.2.0 bcrypt==3.1.4 diff --git a/setup.py b/setup.py index 4ade305c590..7cd551b573a 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ PACKAGES = find_packages(exclude=['tests', 'tests.*']) REQUIRES = [ 'aiohttp==3.4.4', - 'astral==1.6.1', + 'astral==1.7.1', 'async_timeout==3.0.1', 'attrs==18.2.0', 'bcrypt==3.1.4', From 2fce79eccff0b1a83d5be795c2d04db4b4a1dae4 Mon Sep 17 00:00:00 2001 From: Mattias Welponer Date: Thu, 1 Nov 2018 12:21:59 +0100 Subject: [PATCH 161/230] HomematicIP_Cloud fix test (#17376) * fix-homematicip_cloud-flow_config * remove-homematicip_cloud-flow_config-stale-print * Fix redundant backslash between brackets --- homeassistant/components/homematicip_cloud/hap.py | 1 - tests/components/homematicip_cloud/test_config_flow.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index d79e7c1ee14..b4605aa37f0 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -61,7 +61,6 @@ class HomematicipAuth: from homematicip.base.base_connection import HmipConnectionError auth = AsyncAuth(hass.loop, async_get_clientsession(hass)) - print(auth) try: await auth.init(hapid) if pin: diff --git a/tests/components/homematicip_cloud/test_config_flow.py b/tests/components/homematicip_cloud/test_config_flow.py index 1c2e54a1a5d..1c5d6e31190 100644 --- a/tests/components/homematicip_cloud/test_config_flow.py +++ b/tests/components/homematicip_cloud/test_config_flow.py @@ -21,6 +21,8 @@ async def test_flow_works(hass): with patch.object(hap, 'get_auth', return_value=mock_coro()), \ patch.object(hmipc.HomematicipAuth, 'async_checkbutton', return_value=mock_coro(True)), \ + patch.object(hmipc.HomematicipAuth, 'async_setup', + return_value=mock_coro(True)), \ patch.object(hmipc.HomematicipAuth, 'async_register', return_value=mock_coro(True)): hap.authtoken = 'ABC' From 957320f2656308ec86f3a35794e55085f74aecb8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 13:00:24 +0100 Subject: [PATCH 162/230] Upgrade locationsharinglib to 3.0.7 (#18083) --- homeassistant/components/device_tracker/google_maps.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py index aec1dcff355..94a2033e7c0 100644 --- a/homeassistant/components/device_tracker/google_maps.py +++ b/homeassistant/components/device_tracker/google_maps.py @@ -19,7 +19,7 @@ from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.typing import ConfigType from homeassistant.util import slugify, dt as dt_util -REQUIREMENTS = ['locationsharinglib==3.0.6'] +REQUIREMENTS = ['locationsharinglib==3.0.7'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 104179ae762..aac23ac67b5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -582,7 +582,7 @@ liveboxplaytv==2.0.2 lmnotify==0.0.4 # homeassistant.components.device_tracker.google_maps -locationsharinglib==3.0.6 +locationsharinglib==3.0.7 # homeassistant.components.logi_circle logi_circle==0.1.7 From a3e77bc5f32fe10d7fa953bbca22435cf5af6d33 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 13:25:45 +0100 Subject: [PATCH 163/230] Upgrade sqlalchemy to 1.2.13 (#18084) --- homeassistant/components/recorder/__init__.py | 2 +- homeassistant/components/sensor/sql.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index a3cd2eebd8c..bc624ca5f89 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -34,7 +34,7 @@ from . import migration, purge from .const import DATA_INSTANCE from .util import session_scope -REQUIREMENTS = ['sqlalchemy==1.2.11'] +REQUIREMENTS = ['sqlalchemy==1.2.13'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sensor/sql.py index 53821275d42..fd12ea18088 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sensor/sql.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['sqlalchemy==1.2.11'] +REQUIREMENTS = ['sqlalchemy==1.2.13'] CONF_COLUMN_NAME = 'column' CONF_QUERIES = 'queries' diff --git a/requirements_all.txt b/requirements_all.txt index aac23ac67b5..e2dc970986c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1416,7 +1416,7 @@ spotipy-homeassistant==2.4.4.dev1 # homeassistant.components.recorder # homeassistant.scripts.db_migrator # homeassistant.components.sensor.sql -sqlalchemy==1.2.11 +sqlalchemy==1.2.13 # homeassistant.components.sensor.starlingbank starlingbank==1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8b363b1a9e..eccfd9a6694 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -232,7 +232,7 @@ somecomfort==0.5.2 # homeassistant.components.recorder # homeassistant.scripts.db_migrator # homeassistant.components.sensor.sql -sqlalchemy==1.2.11 +sqlalchemy==1.2.13 # homeassistant.components.statsd statsd==3.2.1 From a69c3953f1e7ceb1dd16375f6220f62eb098b9ec Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 13:26:12 +0100 Subject: [PATCH 164/230] Upgrade youtube_dl to 2018.10.29 (#18085) --- homeassistant/components/media_extractor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py index 401fd729f9c..858f95e7ae4 100644 --- a/homeassistant/components/media_extractor.py +++ b/homeassistant/components/media_extractor.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA) from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['youtube_dl==2018.10.05'] +REQUIREMENTS = ['youtube_dl==2018.10.29'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index e2dc970986c..18d59b4a969 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1588,7 +1588,7 @@ yeelight==0.4.3 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2018.10.05 +youtube_dl==2018.10.29 # homeassistant.components.light.zengge zengge==0.2 From afc109a5858d519a5bcdc3bb238b19d456fbff28 Mon Sep 17 00:00:00 2001 From: Mike Megally Date: Thu, 1 Nov 2018 05:33:51 -0700 Subject: [PATCH 165/230] Pass though file_url from extended data attrs (#17801) * pass though file_url from extended data attrs so synology chat bot can send files * some code cleanup --- homeassistant/components/notify/synology_chat.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/notify/synology_chat.py b/homeassistant/components/notify/synology_chat.py index 8b968729074..3fbb7823dc0 100644 --- a/homeassistant/components/notify/synology_chat.py +++ b/homeassistant/components/notify/synology_chat.py @@ -11,10 +11,11 @@ import requests import voluptuous as vol from homeassistant.components.notify import ( - BaseNotificationService, PLATFORM_SCHEMA) + BaseNotificationService, PLATFORM_SCHEMA, ATTR_DATA) from homeassistant.const import CONF_RESOURCE import homeassistant.helpers.config_validation as cv +ATTR_FILE_URL = 'file_url' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_RESOURCE): cv.url, @@ -43,6 +44,12 @@ class SynologyChatNotificationService(BaseNotificationService): 'text': message } + extended_data = kwargs.get(ATTR_DATA) + file_url = extended_data.get(ATTR_FILE_URL) if extended_data else None + + if file_url: + data['file_url'] = file_url + to_send = 'payload={}'.format(json.dumps(data)) response = requests.post(self._resource, data=to_send, timeout=10) From c5f9220500c488d5e967a41c2351eb970aacfc90 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 14:03:18 +0100 Subject: [PATCH 166/230] Upgrade psutil to 5.4.8 (#18086) --- homeassistant/components/sensor/systemmonitor.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/systemmonitor.py b/homeassistant/components/sensor/systemmonitor.py index de8e9783f92..27a7f083fbe 100644 --- a/homeassistant/components/sensor/systemmonitor.py +++ b/homeassistant/components/sensor/systemmonitor.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -REQUIREMENTS = ['psutil==5.4.7'] +REQUIREMENTS = ['psutil==5.4.8'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 18d59b4a969..ec26f9b74ed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -747,7 +747,7 @@ proliphix==0.4.1 prometheus_client==0.2.0 # homeassistant.components.sensor.systemmonitor -psutil==5.4.7 +psutil==5.4.8 # homeassistant.components.wink pubnubsub-handler==1.0.2 From 2a763470711804bf5dc7f667ad8a5d19487073b8 Mon Sep 17 00:00:00 2001 From: Simon van der Veldt Date: Thu, 1 Nov 2018 14:10:43 +0100 Subject: [PATCH 167/230] sensor/wunderground add device_class (#18072) to relevant sensors --- .../components/sensor/wunderground.py | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index 590a9d96b4d..be42e10e894 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -50,7 +50,8 @@ class WUSensorConfig: def __init__(self, friendly_name, feature, value, unit_of_measurement=None, entity_picture=None, - icon="mdi:gauge", device_state_attributes=None): + icon="mdi:gauge", device_state_attributes=None, + device_class=None): """Constructor. Args: @@ -73,13 +74,14 @@ class WUSensorConfig: self.entity_picture = entity_picture self.icon = icon self.device_state_attributes = device_state_attributes or {} + self.device_class = device_class class WUCurrentConditionsSensorConfig(WUSensorConfig): """Helper for defining sensor configurations for current conditions.""" def __init__(self, friendly_name, field, icon="mdi:gauge", - unit_of_measurement=None): + unit_of_measurement=None, device_class=None): """Constructor. Args: @@ -101,7 +103,8 @@ class WUCurrentConditionsSensorConfig(WUSensorConfig): device_state_attributes={ 'date': lambda wu: wu.data['current_observation'][ 'observation_time'] - } + }, + device_class=device_class ) @@ -135,7 +138,7 @@ class WUDailySimpleForecastSensorConfig(WUSensorConfig): """Helper for defining sensor configurations for daily simpleforecasts.""" def __init__(self, friendly_name, period, field, wu_unit=None, - ha_unit=None, icon=None): + ha_unit=None, icon=None, device_class=None): """Constructor. Args: @@ -162,7 +165,8 @@ class WUDailySimpleForecastSensorConfig(WUSensorConfig): device_state_attributes={ 'date': lambda wu: wu.data['forecast']['simpleforecast'][ 'forecastday'][period]['date']['pretty'] - } + }, + device_class=device_class ) @@ -216,7 +220,7 @@ class WUHourlyForecastSensorConfig(WUSensorConfig): period]['mslp']['english'], 'date': lambda wu: wu.data['hourly_forecast'][ period]['FCTTIME']['pretty'], - }, + } ) @@ -224,7 +228,7 @@ class WUAlmanacSensorConfig(WUSensorConfig): """Helper for defining field configurations for almanac sensors.""" def __init__(self, friendly_name, field, value_type, wu_unit, - unit_of_measurement, icon): + unit_of_measurement, icon, device_class=None): """Constructor. Args: @@ -241,7 +245,8 @@ class WUAlmanacSensorConfig(WUSensorConfig): feature="almanac", value=lambda wu: wu.data['almanac'][field][value_type][wu_unit], unit_of_measurement=unit_of_measurement, - icon=icon + icon=icon, + device_class="temperature" ) @@ -336,18 +341,22 @@ SENSOR_TYPES = { 'precip_today_string': WUCurrentConditionsSensorConfig( 'Precipitation Today', 'precip_today_string', "mdi:umbrella"), 'pressure_in': WUCurrentConditionsSensorConfig( - 'Pressure', 'pressure_in', "mdi:gauge", 'inHg'), + 'Pressure', 'pressure_in', "mdi:gauge", 'inHg', + device_class="pressure"), 'pressure_mb': WUCurrentConditionsSensorConfig( - 'Pressure', 'pressure_mb', "mdi:gauge", 'mb'), + 'Pressure', 'pressure_mb', "mdi:gauge", 'mb', + device_class="pressure"), 'pressure_trend': WUCurrentConditionsSensorConfig( - 'Pressure Trend', 'pressure_trend', "mdi:gauge"), + 'Pressure Trend', 'pressure_trend', "mdi:gauge", + device_class="pressure"), 'relative_humidity': WUSensorConfig( 'Relative Humidity', 'conditions', value=lambda wu: int(wu.data['current_observation'][ 'relative_humidity'][:-1]), unit_of_measurement='%', - icon="mdi:water-percent"), + icon="mdi:water-percent", + device_class="humidity"), 'station_id': WUCurrentConditionsSensorConfig( 'Station ID', 'station_id', "mdi:home"), 'solarradiation': WUCurrentConditionsSensorConfig( @@ -355,9 +364,11 @@ SENSOR_TYPES = { 'temperature_string': WUCurrentConditionsSensorConfig( 'Temperature Summary', 'temperature_string', "mdi:thermometer"), 'temp_c': WUCurrentConditionsSensorConfig( - 'Temperature', 'temp_c', "mdi:thermometer", TEMP_CELSIUS), + 'Temperature', 'temp_c', "mdi:thermometer", TEMP_CELSIUS, + device_class="temperature"), 'temp_f': WUCurrentConditionsSensorConfig( - 'Temperature', 'temp_f', "mdi:thermometer", TEMP_FAHRENHEIT), + 'Temperature', 'temp_f', "mdi:thermometer", TEMP_FAHRENHEIT, + device_class="temperature"), 'UV': WUCurrentConditionsSensorConfig( 'UV', 'UV', "mdi:sunglasses"), 'visibility_km': WUCurrentConditionsSensorConfig( @@ -462,52 +473,52 @@ SENSOR_TYPES = { 'weather_36h': WUHourlyForecastSensorConfig(35, "condition"), 'temp_high_1d_c': WUDailySimpleForecastSensorConfig( "High Temperature Today", 0, "high", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_2d_c': WUDailySimpleForecastSensorConfig( "High Temperature Tomorrow", 1, "high", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_3d_c': WUDailySimpleForecastSensorConfig( "High Temperature in 3 Days", 2, "high", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_4d_c': WUDailySimpleForecastSensorConfig( "High Temperature in 4 Days", 3, "high", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_1d_f': WUDailySimpleForecastSensorConfig( "High Temperature Today", 0, "high", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_2d_f': WUDailySimpleForecastSensorConfig( "High Temperature Tomorrow", 1, "high", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_3d_f': WUDailySimpleForecastSensorConfig( "High Temperature in 3 Days", 2, "high", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_high_4d_f': WUDailySimpleForecastSensorConfig( "High Temperature in 4 Days", 3, "high", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_1d_c': WUDailySimpleForecastSensorConfig( "Low Temperature Today", 0, "low", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_2d_c': WUDailySimpleForecastSensorConfig( "Low Temperature Tomorrow", 1, "low", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_3d_c': WUDailySimpleForecastSensorConfig( "Low Temperature in 3 Days", 2, "low", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_4d_c': WUDailySimpleForecastSensorConfig( "Low Temperature in 4 Days", 3, "low", "celsius", TEMP_CELSIUS, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_1d_f': WUDailySimpleForecastSensorConfig( "Low Temperature Today", 0, "low", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_2d_f': WUDailySimpleForecastSensorConfig( "Low Temperature Tomorrow", 1, "low", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_3d_f': WUDailySimpleForecastSensorConfig( "Low Temperature in 3 Days", 2, "low", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'temp_low_4d_f': WUDailySimpleForecastSensorConfig( "Low Temperature in 4 Days", 3, "low", "fahrenheit", TEMP_FAHRENHEIT, - "mdi:thermometer"), + "mdi:thermometer", device_class="temperature"), 'wind_gust_1d_kph': WUDailySimpleForecastSensorConfig( "Max. Wind Today", 0, "maxwind", "kph", "kph", "mdi:weather-windy"), 'wind_gust_2d_kph': WUDailySimpleForecastSensorConfig( @@ -679,6 +690,7 @@ class WUndergroundSensor(Entity): # the entity registry later. self.entity_id = sensor.ENTITY_ID_FORMAT.format('pws_' + condition) self._unique_id = "{},{}".format(unique_id_base, condition) + self._device_class = self._cfg_expand("device_class") def _cfg_expand(self, what, default=None): """Parse and return sensor data.""" @@ -741,6 +753,11 @@ class WUndergroundSensor(Entity): """Return the units of measurement.""" return self._unit_of_measurement + @property + def device_class(self): + """Return the units of measurement.""" + return self._device_class + async def async_update(self): """Update current conditions.""" await self.rest.async_update() From 65e6c50748d5a20c7c6261a4475bdbcf1a34bd7a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 14:25:45 +0100 Subject: [PATCH 168/230] Upgrade restrictedpython to 4.0b6 (#18087) --- homeassistant/components/python_script.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/python_script.py b/homeassistant/components/python_script.py index 5c56caf6470..bf9957f36ee 100644 --- a/homeassistant/components/python_script.py +++ b/homeassistant/components/python_script.py @@ -18,7 +18,7 @@ from homeassistant.loader import bind_hass from homeassistant.util import sanitize_filename import homeassistant.util.dt as dt_util -REQUIREMENTS = ['restrictedpython==4.0b5'] +REQUIREMENTS = ['restrictedpython==4.0b6'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index ec26f9b74ed..620994b9501 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1300,7 +1300,7 @@ raincloudy==0.0.5 regenmaschine==1.0.2 # homeassistant.components.python_script -restrictedpython==4.0b5 +restrictedpython==4.0b6 # homeassistant.components.rflink rflink==0.0.37 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eccfd9a6694..c7299bcdf71 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -206,7 +206,7 @@ pyunifi==2.13 pywebpush==1.6.0 # homeassistant.components.python_script -restrictedpython==4.0b5 +restrictedpython==4.0b6 # homeassistant.components.rflink rflink==0.0.37 From 83e83520e619991bdb9bded16d8123ad7eec12ef Mon Sep 17 00:00:00 2001 From: Malte Franken Date: Fri, 2 Nov 2018 02:32:21 +1100 Subject: [PATCH 169/230] Upgrade georss_client to 0.4 (#18088) * update geojson_client to 0.3 * update georss_client to 0.4 --- homeassistant/components/geo_location/geo_json_events.py | 2 +- .../components/geo_location/nsw_rural_fire_service_feed.py | 2 +- homeassistant/components/sensor/geo_rss_events.py | 2 +- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/geo_location/geo_json_events.py b/homeassistant/components/geo_location/geo_json_events.py index 00ac85e6b27..74d1b036f6c 100644 --- a/homeassistant/components/geo_location/geo_json_events.py +++ b/homeassistant/components/geo_location/geo_json_events.py @@ -20,7 +20,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, dispatcher_send) from homeassistant.helpers.event import track_time_interval -REQUIREMENTS = ['geojson_client==0.1'] +REQUIREMENTS = ['geojson_client==0.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/geo_location/nsw_rural_fire_service_feed.py b/homeassistant/components/geo_location/nsw_rural_fire_service_feed.py index 79e0445f494..1d2a7fadaff 100644 --- a/homeassistant/components/geo_location/nsw_rural_fire_service_feed.py +++ b/homeassistant/components/geo_location/nsw_rural_fire_service_feed.py @@ -21,7 +21,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, dispatcher_send) from homeassistant.helpers.event import track_time_interval -REQUIREMENTS = ['geojson_client==0.1'] +REQUIREMENTS = ['geojson_client==0.3'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/sensor/geo_rss_events.py index 60ae9730d80..7157416d4b5 100644 --- a/homeassistant/components/sensor/geo_rss_events.py +++ b/homeassistant/components/sensor/geo_rss_events.py @@ -20,7 +20,7 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_URL) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['georss_client==0.3'] +REQUIREMENTS = ['georss_client==0.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 620994b9501..121f3e6b7fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -410,10 +410,10 @@ geizhals==0.0.7 # homeassistant.components.geo_location.geo_json_events # homeassistant.components.geo_location.nsw_rural_fire_service_feed -geojson_client==0.1 +geojson_client==0.3 # homeassistant.components.sensor.geo_rss_events -georss_client==0.3 +georss_client==0.4 # homeassistant.components.sensor.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c7299bcdf71..f17a366103d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -76,10 +76,10 @@ gTTS-token==1.1.2 # homeassistant.components.geo_location.geo_json_events # homeassistant.components.geo_location.nsw_rural_fire_service_feed -geojson_client==0.1 +geojson_client==0.3 # homeassistant.components.sensor.geo_rss_events -georss_client==0.3 +georss_client==0.4 # homeassistant.components.ffmpeg ha-ffmpeg==1.9 From a9361482d9dc0348d67f45ce3b424e40f75458a4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 1 Nov 2018 16:32:36 +0100 Subject: [PATCH 170/230] Catch KeyError if data is not available (fixes #18082) (#18089) --- .../components/sensor/openweathermap.py | 100 ++++++++++-------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 08426ed3eb8..a8c39616e15 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if None in (hass.config.latitude, hass.config.longitude): _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return False + return SENSOR_TYPES['temperature'][1] = hass.config.units.temperature_unit @@ -72,10 +72,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if not owm: _LOGGER.error("Unable to connect to OpenWeatherMap") - return False + return - data = WeatherData(owm, forecast, hass.config.latitude, - hass.config.longitude) + data = WeatherData( + owm, forecast, hass.config.latitude, hass.config.longitude) dev = [] for variable in config[CONF_MONITORED_CONDITIONS]: dev.append(OpenWeatherMapSensor( @@ -131,7 +131,7 @@ class OpenWeatherMapSensor(Entity): try: self.owa_client.update() except APICallError: - _LOGGER.error("Exception when calling OWM web API to update data") + _LOGGER.error("Error when calling API to update data") return data = self.owa_client.data @@ -140,46 +140,52 @@ class OpenWeatherMapSensor(Entity): if data is None: return - if self.type == 'weather': - self._state = data.get_detailed_status() - elif self.type == 'temperature': - if self.temp_unit == TEMP_CELSIUS: - self._state = round(data.get_temperature('celsius')['temp'], 1) - elif self.temp_unit == TEMP_FAHRENHEIT: - self._state = round(data.get_temperature('fahrenheit')['temp'], - 1) - else: - self._state = round(data.get_temperature()['temp'], 1) - elif self.type == 'wind_speed': - self._state = round(data.get_wind()['speed'], 1) - elif self.type == 'wind_bearing': - self._state = round(data.get_wind()['deg'], 1) - elif self.type == 'humidity': - self._state = round(data.get_humidity(), 1) - elif self.type == 'pressure': - self._state = round(data.get_pressure()['press'], 0) - elif self.type == 'clouds': - self._state = data.get_clouds() - elif self.type == 'rain': - if data.get_rain(): - self._state = round(data.get_rain()['3h'], 0) - self._unit_of_measurement = 'mm' - else: - self._state = 'not raining' - self._unit_of_measurement = '' - elif self.type == 'snow': - if data.get_snow(): - self._state = round(data.get_snow(), 0) - self._unit_of_measurement = 'mm' - else: - self._state = 'not snowing' - self._unit_of_measurement = '' - elif self.type == 'forecast': - if fc_data is None: - return - self._state = fc_data.get_weathers()[0].get_detailed_status() - elif self.type == 'weather_code': - self._state = data.get_weather_code() + try: + if self.type == 'weather': + self._state = data.get_detailed_status() + elif self.type == 'temperature': + if self.temp_unit == TEMP_CELSIUS: + self._state = round( + data.get_temperature('celsius')['temp'], 1) + elif self.temp_unit == TEMP_FAHRENHEIT: + self._state = round( + data.get_temperature('fahrenheit')['temp'], 1) + else: + self._state = round(data.get_temperature()['temp'], 1) + elif self.type == 'wind_speed': + self._state = round(data.get_wind()['speed'], 1) + elif self.type == 'wind_bearing': + self._state = round(data.get_wind()['deg'], 1) + elif self.type == 'humidity': + self._state = round(data.get_humidity(), 1) + elif self.type == 'pressure': + self._state = round(data.get_pressure()['press'], 0) + elif self.type == 'clouds': + self._state = data.get_clouds() + elif self.type == 'rain': + if data.get_rain(): + self._state = round(data.get_rain()['3h'], 0) + self._unit_of_measurement = 'mm' + else: + self._state = 'not raining' + self._unit_of_measurement = '' + elif self.type == 'snow': + if data.get_snow(): + self._state = round(data.get_snow(), 0) + self._unit_of_measurement = 'mm' + else: + self._state = 'not snowing' + self._unit_of_measurement = '' + elif self.type == 'forecast': + if fc_data is None: + return + self._state = fc_data.get_weathers()[0].get_detailed_status() + elif self.type == 'weather_code': + self._state = data.get_weather_code() + except KeyError: + self._state = None + _LOGGER.warning( + "Condition is currently not available: %s", self.type) class WeatherData: @@ -202,8 +208,8 @@ class WeatherData: try: obs = self.owm.weather_at_coords(self.latitude, self.longitude) except (APICallError, TypeError): - _LOGGER.error("Exception when calling OWM web API " - "to get weather at coords") + _LOGGER.error( + "Error when calling API to get weather at coordinates") obs = None if obs is None: From c75c00d568af86f20c54378f5d285e8f92e6c253 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 1 Nov 2018 17:53:48 +0100 Subject: [PATCH 171/230] Bump python-miio version (#18095) --- homeassistant/components/device_tracker/xiaomi_miio.py | 2 +- homeassistant/components/fan/xiaomi_miio.py | 4 ++-- homeassistant/components/light/xiaomi_miio.py | 2 +- homeassistant/components/remote/xiaomi_miio.py | 2 +- homeassistant/components/sensor/xiaomi_miio.py | 2 +- homeassistant/components/switch/xiaomi_miio.py | 2 +- homeassistant/components/vacuum/xiaomi_miio.py | 2 +- requirements_all.txt | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/device_tracker/xiaomi_miio.py b/homeassistant/components/device_tracker/xiaomi_miio.py index d8767a4cd46..1c02efe4489 100644 --- a/homeassistant/components/device_tracker/xiaomi_miio.py +++ b/homeassistant/components/device_tracker/xiaomi_miio.py @@ -13,7 +13,7 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_TOKEN import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 67a12442629..35bb92fa610 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -18,7 +18,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) @@ -348,7 +348,7 @@ async def async_setup_platform(hass, config, async_add_entities, device = XiaomiAirPurifier(name, air_purifier, model, unique_id) elif model.startswith('zhimi.humidifier.'): from miio import AirHumidifier - air_humidifier = AirHumidifier(host, token) + air_humidifier = AirHumidifier(host, token, model=model) device = XiaomiAirHumidifier(name, air_humidifier, model, unique_id) else: _LOGGER.error( diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index 26a63b2f16b..cc88dbfe29f 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -21,7 +21,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util import dt -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index 5a914fc3652..0f63357c0dc 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -22,7 +22,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/xiaomi_miio.py b/homeassistant/components/sensor/xiaomi_miio.py index dd0785120ea..86ee2f8767c 100644 --- a/homeassistant/components/sensor/xiaomi_miio.py +++ b/homeassistant/components/sensor/xiaomi_miio.py @@ -14,7 +14,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py index 0152615109c..d55b2301745 100644 --- a/homeassistant/components/switch/xiaomi_miio.py +++ b/homeassistant/components/switch/xiaomi_miio.py @@ -17,7 +17,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py index d1d45f5ecd2..2e25af36b11 100644 --- a/homeassistant/components/vacuum/xiaomi_miio.py +++ b/homeassistant/components/vacuum/xiaomi_miio.py @@ -21,7 +21,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45'] +REQUIREMENTS = ['python-miio==0.4.3', 'construct==2.9.45'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 121f3e6b7fb..dfbfc951066 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1167,7 +1167,7 @@ python-juicenet==0.0.5 # homeassistant.components.sensor.xiaomi_miio # homeassistant.components.switch.xiaomi_miio # homeassistant.components.vacuum.xiaomi_miio -python-miio==0.4.2 +python-miio==0.4.3 # homeassistant.components.media_player.mpd python-mpd2==1.0.0 From 4a3f7540338a6a4e563b18b30a16a41694fe39f1 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Thu, 1 Nov 2018 19:35:02 +0100 Subject: [PATCH 172/230] Revert HomeKit update to 2.2.2 (#18069) --- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/homekit/type_switches.py | 7 +++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_type_locks.py | 1 - tests/components/homekit/test_type_media_players.py | 1 - 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index d4d8fe0216c..1c30de918e3 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -29,7 +29,7 @@ from .const import ( from .util import ( show_setup_message, validate_entity_config, validate_media_player_features) -REQUIREMENTS = ['HAP-python==2.3.0'] +REQUIREMENTS = ['HAP-python==2.2.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 839abe5a580..288da65a4af 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -2,8 +2,7 @@ import logging from pyhap.const import ( - CATEGORY_FAUCET, CATEGORY_OUTLET, CATEGORY_SHOWER_HEAD, - CATEGORY_SPRINKLER, CATEGORY_SWITCH) + CATEGORY_OUTLET, CATEGORY_SWITCH) from homeassistant.components.switch import DOMAIN from homeassistant.const import ( @@ -19,6 +18,10 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +CATEGORY_SPRINKLER = 28 +CATEGORY_FAUCET = 29 +CATEGORY_SHOWER_HEAD = 30 + VALVE_TYPE = { TYPE_FAUCET: (CATEGORY_FAUCET, 3), TYPE_SHOWER: (CATEGORY_SHOWER_HEAD, 2), diff --git a/requirements_all.txt b/requirements_all.txt index dfbfc951066..e6b1d258c07 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,7 +35,7 @@ Adafruit-SHT31==1.0.2 DoorBirdPy==0.1.3 # homeassistant.components.homekit -HAP-python==2.3.0 +HAP-python==2.2.2 # homeassistant.components.notify.mastodon Mastodon.py==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f17a366103d..8035918d3f2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -19,7 +19,7 @@ requests_mock==1.5.2 # homeassistant.components.homekit -HAP-python==2.3.0 +HAP-python==2.2.2 # homeassistant.components.sensor.rmvtransport PyRMVtransport==0.1.3 diff --git a/tests/components/homekit/test_type_locks.py b/tests/components/homekit/test_type_locks.py index 8132099bd3e..e7e52c65559 100644 --- a/tests/components/homekit/test_type_locks.py +++ b/tests/components/homekit/test_type_locks.py @@ -82,7 +82,6 @@ async def test_no_code(hass, hk_driver, config, events): # Set from HomeKit call_lock = async_mock_service(hass, DOMAIN, 'lock') - acc.char_target_state.value = 0 await hass.async_add_job(acc.char_target_state.client_update_value, 1) await hass.async_block_till_done() assert call_lock diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 745e4c162bc..6b23b3cc58e 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -64,7 +64,6 @@ async def test_media_player_set_state(hass, hk_driver, events): call_media_stop = async_mock_service(hass, DOMAIN, 'media_stop') call_toggle_mute = async_mock_service(hass, DOMAIN, 'volume_mute') - acc.chars[FEATURE_ON_OFF].value = False await hass.async_add_job(acc.chars[FEATURE_ON_OFF] .client_update_value, True) await hass.async_block_till_done() From 4ee21e66dc8975c201138c62b57bdbb5db8a29bb Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 1 Nov 2018 12:36:42 -0600 Subject: [PATCH 173/230] Update Pollen.com sensor platform to include asthma info (#18024) * Update Pollen.com sensor platform to include asthma info * Updated requirements * Bump to 2.2.2 --- homeassistant/components/sensor/pollen.py | 298 ++++++++++++++-------- requirements_all.txt | 2 +- 2 files changed, 188 insertions(+), 112 deletions(-) diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index 6df7047b353..62fdd5b4955 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/sensor/pollen.py @@ -18,9 +18,10 @@ from homeassistant.helpers import aiohttp_client from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pypollencom==2.1.0'] +REQUIREMENTS = ['pypollencom==2.2.2'] _LOGGER = logging.getLogger(__name__) +ATTR_ALLERGEN_AMOUNT = 'allergen_amount' ATTR_ALLERGEN_GENUS = 'allergen_genus' ATTR_ALLERGEN_NAME = 'allergen_name' ATTR_ALLERGEN_TYPE = 'allergen_type' @@ -43,21 +44,35 @@ TYPE_ALLERGY_OUTLOOK = 'allergy_outlook' TYPE_ALLERGY_TODAY = 'allergy_index_today' TYPE_ALLERGY_TOMORROW = 'allergy_index_tomorrow' TYPE_ALLERGY_YESTERDAY = 'allergy_index_yesterday' +TYPE_ASTHMA_FORECAST = 'asthma_average_forecasted' +TYPE_ASTHMA_HISTORIC = 'asthma_average_historical' +TYPE_ASTHMA_INDEX = 'asthma_index' +TYPE_ASTHMA_TODAY = 'asthma_index_today' +TYPE_ASTHMA_TOMORROW = 'asthma_index_tomorrow' +TYPE_ASTHMA_YESTERDAY = 'asthma_index_yesterday' TYPE_DISEASE_FORECAST = 'disease_average_forecasted' SENSORS = { TYPE_ALLERGY_FORECAST: ( - 'Allergy Index: Forecasted Average', None, 'mdi:flower', 'index'), + 'ForecastSensor', 'Allergy Index: Forecasted Average', 'mdi:flower'), TYPE_ALLERGY_HISTORIC: ( - 'Allergy Index: Historical Average', None, 'mdi:flower', 'index'), - TYPE_ALLERGY_TODAY: ( - 'Allergy Index: Today', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'), + 'HistoricalSensor', 'Allergy Index: Historical Average', 'mdi:flower'), + TYPE_ALLERGY_TODAY: ('IndexSensor', 'Allergy Index: Today', 'mdi:flower'), TYPE_ALLERGY_TOMORROW: ( - 'Allergy Index: Tomorrow', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'), + 'IndexSensor', 'Allergy Index: Tomorrow', 'mdi:flower'), TYPE_ALLERGY_YESTERDAY: ( - 'Allergy Index: Yesterday', TYPE_ALLERGY_INDEX, 'mdi:flower', 'index'), + 'IndexSensor', 'Allergy Index: Yesterday', 'mdi:flower'), + TYPE_ASTHMA_TODAY: ('IndexSensor', 'Ashma Index: Today', 'mdi:flower'), + TYPE_ASTHMA_TOMORROW: ( + 'IndexSensor', 'Ashma Index: Tomorrow', 'mdi:flower'), + TYPE_ASTHMA_YESTERDAY: ( + 'IndexSensor', 'Ashma Index: Yesterday', 'mdi:flower'), + TYPE_ASTHMA_FORECAST: ( + 'ForecastSensor', 'Asthma Index: Forecasted Average', 'mdi:flower'), + TYPE_ASTHMA_HISTORIC: ( + 'HistoricalSensor', 'Asthma Index: Historical Average', 'mdi:flower'), TYPE_DISEASE_FORECAST: ( - 'Cold & Flu: Forecasted Average', None, 'mdi:snowflake', 'index') + 'ForecastSensor', 'Cold & Flu: Forecasted Average', 'mdi:snowflake') } RATING_MAPPING = [{ @@ -87,7 +102,8 @@ TREND_INCREASING = 'Increasing' TREND_SUBSIDING = 'Subsiding' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ZIP_CODE): str, + vol.Required(CONF_ZIP_CODE): + str, vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All(cv.ensure_list, [vol.In(SENSORS)]) }) @@ -100,18 +116,18 @@ async def async_setup_platform( websession = aiohttp_client.async_get_clientsession(hass) - data = PollenComData( + pollen = PollenComData( Client(config[CONF_ZIP_CODE], websession), config[CONF_MONITORED_CONDITIONS]) - await data.async_update() + await pollen.async_update() sensors = [] for kind in config[CONF_MONITORED_CONDITIONS]: - name, category, icon, unit = SENSORS[kind] + sensor_class, name, icon = SENSORS[kind] sensors.append( - PollencomSensor( - data, config[CONF_ZIP_CODE], kind, category, name, icon, unit)) + globals()[sensor_class]( + pollen, kind, name, icon, config[CONF_ZIP_CODE])) async_add_entities(sensors, True) @@ -124,27 +140,31 @@ def calculate_average_rating(indices): return max(set(ratings), key=ratings.count) -class PollencomSensor(Entity): - """Define a Pollen.com sensor.""" +class BaseSensor(Entity): + """Define a base Pollen.com sensor.""" - def __init__(self, pollencom, zip_code, kind, category, name, icon, unit): + def __init__(self, pollen, kind, name, icon, zip_code): """Initialize the sensor.""" self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} - self._category = category self._icon = icon + self._kind = kind self._name = name self._state = None - self._type = kind - self._unit = unit self._zip_code = zip_code - self.pollencom = pollencom + self.pollen = pollen @property def available(self): """Return True if entity is available.""" - return bool( - self.pollencom.data.get(self._type) - or self.pollencom.data.get(self._category)) + if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, + TYPE_ALLERGY_YESTERDAY): + return bool(self.pollen.data[TYPE_ALLERGY_INDEX]) + + if self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW, + TYPE_ASTHMA_YESTERDAY): + return bool(self.pollen.data[TYPE_ASTHMA_INDEX]) + + return bool(self.pollen.data[self._kind]) @property def device_state_attributes(self): @@ -169,24 +189,24 @@ class PollencomSensor(Entity): @property def unique_id(self): """Return a unique, HASS-friendly identifier for this entity.""" - return '{0}_{1}'.format(self._zip_code, self._type) + return '{0}_{1}'.format(self._zip_code, self._kind) @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return self._unit + return 'index' + + +class ForecastSensor(BaseSensor): + """Define sensor related to forecast data.""" async def async_update(self): """Update the sensor.""" - await self.pollencom.async_update() - if not self.pollencom.data: + await self.pollen.async_update() + if not self.pollen.data: return - if self._category: - data = self.pollencom.data[self._category].get('Location') - else: - data = self.pollencom.data[self._type].get('Location') - + data = self.pollen.data[self._kind].get('Location') if not data: return @@ -196,44 +216,101 @@ class PollencomSensor(Entity): i['label'] for i in RATING_MAPPING if i['minimum'] <= average <= i['maximum'] ] + slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index']) - trend = TREND_FLAT if slope > 0: trend = TREND_INCREASING elif slope < 0: trend = TREND_SUBSIDING + else: + trend = TREND_FLAT - if self._type == TYPE_ALLERGY_FORECAST: - outlook = self.pollencom.data[TYPE_ALLERGY_OUTLOOK] + self._attrs.update({ + ATTR_CITY: data['City'].title(), + ATTR_RATING: rating, + ATTR_STATE: data['State'], + ATTR_TREND: trend, + ATTR_ZIP_CODE: data['ZIP'] + }) - self._attrs.update({ - ATTR_CITY: data['City'].title(), - ATTR_OUTLOOK: outlook['Outlook'], - ATTR_RATING: rating, - ATTR_SEASON: outlook['Season'].title(), - ATTR_STATE: data['State'], - ATTR_TREND: outlook['Trend'].title(), - ATTR_ZIP_CODE: data['ZIP'] - }) - self._state = average - elif self._type == TYPE_ALLERGY_HISTORIC: - self._attrs.update({ - ATTR_CITY: data['City'].title(), - ATTR_RATING: calculate_average_rating(indices), - ATTR_STATE: data['State'], - ATTR_TREND: trend, - ATTR_ZIP_CODE: data['ZIP'] - }) - self._state = average - elif self._type in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, - TYPE_ALLERGY_YESTERDAY): - key = self._type.split('_')[-1].title() - [period] = [p for p in data['periods'] if p['Type'] == key] - [rating] = [ - i['label'] for i in RATING_MAPPING - if i['minimum'] <= period['Index'] <= i['maximum'] - ] + if self._kind == TYPE_ALLERGY_FORECAST: + outlook = self.pollen.data[TYPE_ALLERGY_OUTLOOK] + self._attrs[ATTR_OUTLOOK] = outlook['Outlook'] + self._state = average + + +class HistoricalSensor(BaseSensor): + """Define sensor related to historical data.""" + + async def async_update(self): + """Update the sensor.""" + await self.pollen.async_update() + if not self.pollen.data: + return + + data = self.pollen.data[self._kind].get('Location') + if not data: + return + + indices = [p['Index'] for p in data['periods']] + average = round(mean(indices), 1) + + slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index']) + if slope > 0: + trend = TREND_INCREASING + elif slope < 0: + trend = TREND_SUBSIDING + else: + trend = TREND_FLAT + + self._attrs.update({ + ATTR_CITY: data['City'].title(), + ATTR_RATING: calculate_average_rating(indices), + ATTR_STATE: data['State'], + ATTR_TREND: trend, + ATTR_ZIP_CODE: data['ZIP'] + }) + + self._state = average + + +class IndexSensor(BaseSensor): + """Define sensor related to indices.""" + + async def async_update(self): + """Update the sensor.""" + await self.pollen.async_update() + if not self.pollen.data: + return + + data = {} + if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, + TYPE_ALLERGY_YESTERDAY): + data = self.pollen.data[TYPE_ALLERGY_INDEX].get('Location') + elif self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW, + TYPE_ASTHMA_YESTERDAY): + data = self.pollen.data[TYPE_ASTHMA_INDEX].get('Location') + + if not data: + return + + key = self._kind.split('_')[-1].title() + [period] = [p for p in data['periods'] if p['Type'] == key] + [rating] = [ + i['label'] for i in RATING_MAPPING + if i['minimum'] <= period['Index'] <= i['maximum'] + ] + + self._attrs.update({ + ATTR_CITY: data['City'].title(), + ATTR_RATING: rating, + ATTR_STATE: data['State'], + ATTR_ZIP_CODE: data['ZIP'] + }) + + if self._kind in (TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, + TYPE_ALLERGY_YESTERDAY): for idx, attrs in enumerate(period['Triggers']): index = idx + 1 self._attrs.update({ @@ -244,23 +321,18 @@ class PollencomSensor(Entity): '{0}_{1}'.format(ATTR_ALLERGEN_TYPE, index): attrs['PlantType'], }) + elif self._kind in (TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW, + TYPE_ASTHMA_YESTERDAY): + for idx, attrs in enumerate(period['Triggers']): + index = idx + 1 + self._attrs.update({ + '{0}_{1}'.format(ATTR_ALLERGEN_NAME, index): + attrs['Name'], + '{0}_{1}'.format(ATTR_ALLERGEN_AMOUNT, index): + attrs['PPM'], + }) - self._attrs.update({ - ATTR_CITY: data['City'].title(), - ATTR_RATING: rating, - ATTR_STATE: data['State'], - ATTR_ZIP_CODE: data['ZIP'] - }) - self._state = period['Index'] - elif self._type == TYPE_DISEASE_FORECAST: - self._attrs.update({ - ATTR_CITY: data['City'].title(), - ATTR_RATING: rating, - ATTR_STATE: data['State'], - ATTR_TREND: trend, - ATTR_ZIP_CODE: data['ZIP'] - }) - self._state = average + self._state = period['Index'] class PollenComData: @@ -272,10 +344,21 @@ class PollenComData: self._sensor_types = sensor_types self.data = {} + async def _get_data(self, method, key): + """Return API data from a specific call.""" + from pypollencom.errors import PollenComError + + try: + data = await method() + self.data[key] = data + except PollenComError as err: + _LOGGER.error('Unable to get "%s" data: %s', key, err) + self.data[key] = {} + @Throttle(DEFAULT_SCAN_INTERVAL) async def async_update(self): """Update Pollen.com data.""" - from pypollencom.errors import InvalidZipError, PollenComError + from pypollencom.errors import InvalidZipError # Pollen.com requires a bit more complicated error handling, given that # it sometimes has parts (but not the whole thing) go down: @@ -285,45 +368,38 @@ class PollenComData: try: if TYPE_ALLERGY_FORECAST in self._sensor_types: - try: - data = await self._client.allergens.extended() - self.data[TYPE_ALLERGY_FORECAST] = data - except PollenComError as err: - _LOGGER.error('Unable to get allergy forecast: %s', err) - self.data[TYPE_ALLERGY_FORECAST] = {} - - try: - data = await self._client.allergens.outlook() - self.data[TYPE_ALLERGY_OUTLOOK] = data - except PollenComError as err: - _LOGGER.error('Unable to get allergy outlook: %s', err) - self.data[TYPE_ALLERGY_OUTLOOK] = {} + await self._get_data( + self._client.allergens.extended, TYPE_ALLERGY_FORECAST) + await self._get_data( + self._client.allergens.outlook, TYPE_ALLERGY_OUTLOOK) if TYPE_ALLERGY_HISTORIC in self._sensor_types: - try: - data = await self._client.allergens.historic() - self.data[TYPE_ALLERGY_HISTORIC] = data - except PollenComError as err: - _LOGGER.error('Unable to get allergy history: %s', err) - self.data[TYPE_ALLERGY_HISTORIC] = {} + await self._get_data( + self._client.allergens.historic, TYPE_ALLERGY_HISTORIC) if any(s in self._sensor_types for s in [TYPE_ALLERGY_TODAY, TYPE_ALLERGY_TOMORROW, TYPE_ALLERGY_YESTERDAY]): - try: - data = await self._client.allergens.current() - self.data[TYPE_ALLERGY_INDEX] = data - except PollenComError as err: - _LOGGER.error('Unable to get current allergies: %s', err) - self.data[TYPE_ALLERGY_TODAY] = {} + await self._get_data( + self._client.allergens.current, TYPE_ALLERGY_INDEX) + + if TYPE_ASTHMA_FORECAST in self._sensor_types: + await self._get_data( + self._client.asthma.extended, TYPE_ASTHMA_FORECAST) + + if TYPE_ASTHMA_HISTORIC in self._sensor_types: + await self._get_data( + self._client.asthma.historic, TYPE_ASTHMA_HISTORIC) + + if any(s in self._sensor_types + for s in [TYPE_ASTHMA_TODAY, TYPE_ASTHMA_TOMORROW, + TYPE_ASTHMA_YESTERDAY]): + await self._get_data( + self._client.asthma.current, TYPE_ASTHMA_INDEX) if TYPE_DISEASE_FORECAST in self._sensor_types: - try: - data = await self._client.disease.extended() - self.data[TYPE_DISEASE_FORECAST] = data - except PollenComError as err: - _LOGGER.error('Unable to get disease forecast: %s', err) - self.data[TYPE_DISEASE_FORECAST] = {} + await self._get_data( + self._client.disease.extended, TYPE_DISEASE_FORECAST) _LOGGER.debug('New data retrieved: %s', self.data) except InvalidZipError: diff --git a/requirements_all.txt b/requirements_all.txt index e6b1d258c07..be86435ea84 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1060,7 +1060,7 @@ pyowm==2.9.0 pypjlink2==1.2.0 # homeassistant.components.sensor.pollen -pypollencom==2.1.0 +pypollencom==2.2.2 # homeassistant.components.qwikswitch pyqwikswitch==0.8 From 23290fa6ee2e72ab095265a75d46a1a49d06995f Mon Sep 17 00:00:00 2001 From: Rohan Kapoor Date: Thu, 1 Nov 2018 11:37:38 -0700 Subject: [PATCH 174/230] Use a fixture for dialogflow calls in unit tests (#17999) * Use a fixture for dialogflow calls in unit tests * Lint --- tests/components/dialogflow/test_init.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/components/dialogflow/test_init.py b/tests/components/dialogflow/test_init.py index a2ac5b85d07..b6c62a2411b 100644 --- a/tests/components/dialogflow/test_init.py +++ b/tests/components/dialogflow/test_init.py @@ -16,12 +16,12 @@ REQUEST_ID = "19ef7e78-fe15-4e94-99dd-0c0b1e8753c3" REQUEST_TIMESTAMP = "2017-01-21T17:54:18.952Z" CONTEXT_NAME = "78a5db95-b7d6-4d50-9c9b-2fc73a5e34c3_id_dialog_context" -calls = [] - @pytest.fixture -async def fixture(hass, aiohttp_client): - """Initialize a Home Assistant server for testing this module.""" +async def calls(hass, fixture): + """Return a list of Dialogflow calls triggered.""" + calls = [] + @callback def mock_service(call): """Mock action call.""" @@ -29,6 +29,12 @@ async def fixture(hass, aiohttp_client): hass.services.async_register('test', 'dialogflow', mock_service) + return calls + + +@pytest.fixture +async def fixture(hass, aiohttp_client): + """Initialize a Home Assistant server for testing this module.""" await async_setup_component(hass, dialogflow.DOMAIN, { "dialogflow": {}, }) @@ -371,7 +377,7 @@ async def test_intent_request_without_slots(hass, fixture): assert "You are both home, you silly" == text -async def test_intent_request_calling_service(fixture): +async def test_intent_request_calling_service(fixture, calls): """Test a request for calling a service. If this request is done async the test could finish before the action From bcea3a9cba92ca0c965d3a4ed7e6a1e0a86d98cf Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Thu, 1 Nov 2018 14:38:23 -0400 Subject: [PATCH 175/230] Don't try to re-add existing Z-Wave entities (#17995) * Keep track of created entities * lint * Update tests --- homeassistant/components/zwave/__init__.py | 28 +++++++++++++--------- tests/components/zwave/test_init.py | 4 ++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index e91ba244ce8..a4f8dcd1b3f 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -223,10 +223,11 @@ async def async_setup_platform(hass, config, async_add_entities, if discovery_info is None or DATA_NETWORK not in hass.data: return False - device = hass.data[DATA_DEVICES].pop( + device = hass.data[DATA_DEVICES].get( discovery_info[const.DISCOVERY_DEVICE], None) if device is None: return False + async_add_entities([device]) return True @@ -340,6 +341,9 @@ async def async_setup_entry(hass, config_entry): entity = ZWaveNodeEntity(node, network) def _add_node_to_component(): + if hass.data[DATA_DEVICES].get(entity.unique_id): + return + name = node_name(node) generated_id = generate_entity_id(DOMAIN + '.{}', name, []) node_config = device_config.get(generated_id) @@ -348,6 +352,8 @@ async def async_setup_entry(hass, config_entry): "Ignoring node entity %s due to device settings", generated_id) return + + hass.data[DATA_DEVICES][entity.unique_id] = entity component.add_entities([entity]) if entity.unique_id: @@ -912,15 +918,12 @@ class ZWaveDeviceEntityValues(): self._entity = device - dict_id = id(self) - @callback def _on_ready(sec): _LOGGER.info( "Z-Wave entity %s (node_id: %d) ready after %d seconds", device.name, self._node.node_id, sec) - self._hass.async_add_job(discover_device, component, device, - dict_id) + self._hass.async_add_job(discover_device, component, device) @callback def _on_timeout(sec): @@ -928,22 +931,25 @@ class ZWaveDeviceEntityValues(): "Z-Wave entity %s (node_id: %d) not ready after %d seconds, " "continuing anyway", device.name, self._node.node_id, sec) - self._hass.async_add_job(discover_device, component, device, - dict_id) + self._hass.async_add_job(discover_device, component, device) - async def discover_device(component, device, dict_id): + async def discover_device(component, device): """Put device in a dictionary and call discovery on it.""" - self._hass.data[DATA_DEVICES][dict_id] = device + if self._hass.data[DATA_DEVICES].get(device.unique_id): + return + + self._hass.data[DATA_DEVICES][device.unique_id] = device if component in SUPPORTED_PLATFORMS: async_dispatcher_send( self._hass, 'zwave_new_{}'.format(component), device) else: await discovery.async_load_platform( self._hass, component, DOMAIN, - {const.DISCOVERY_DEVICE: dict_id}, self._zwave_config) + {const.DISCOVERY_DEVICE: device.unique_id}, + self._zwave_config) if device.unique_id: - self._hass.add_job(discover_device, component, device, dict_id) + self._hass.add_job(discover_device, component, device) else: self._hass.add_job(check_has_unique_id, device, _on_ready, _on_timeout, self._hass.loop) diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index eb28dcc0246..c2634b2d621 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -584,7 +584,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert args[0] == self.hass assert args[1] == 'mock_component' assert args[2] == 'zwave' - assert args[3] == {const.DISCOVERY_DEVICE: id(values)} + assert args[3] == {const.DISCOVERY_DEVICE: mock_device.unique_id} assert args[4] == self.zwave_config discovery.async_load_platform.reset_mock() @@ -646,7 +646,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): assert args[0] == self.hass assert args[1] == 'mock_component' assert args[2] == 'zwave' - assert args[3] == {const.DISCOVERY_DEVICE: id(values)} + assert args[3] == {const.DISCOVERY_DEVICE: mock_device.unique_id} assert args[4] == self.zwave_config assert not self.primary.enable_poll.called From c3e3f662f4a34fb5da53dcab811e15f64d634b5d Mon Sep 17 00:00:00 2001 From: Pawel Date: Thu, 1 Nov 2018 20:09:43 +0100 Subject: [PATCH 176/230] Always save current position if payload is numeric value (#16148) * Change of behaviour. Allow user to configure either a position topic or a state topic but not both. * optimistic mode in set_cover and tests added * optimistic mode in set_cover_position using percentage_position * fixes accroding to Martin review. * added validation schema for set_position_topic and get_position_topic * check only set_position_topic in supported_features. * Multidoc string fix. --- homeassistant/components/cover/mqtt.py | 162 +++++++++---- tests/components/cover/test_mqtt.py | 323 +++++++++++++++++++------ 2 files changed, 369 insertions(+), 116 deletions(-) diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 92a7fac1d33..8cc80c52bc5 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -34,9 +34,11 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] +CONF_GET_POSITION_TOPIC = 'position_topic' + CONF_TILT_COMMAND_TOPIC = 'tilt_command_topic' CONF_TILT_STATUS_TOPIC = 'tilt_status_topic' -CONF_POSITION_TOPIC = 'set_position_topic' +CONF_SET_POSITION_TOPIC = 'set_position_topic' CONF_SET_POSITION_TEMPLATE = 'set_position_template' CONF_PAYLOAD_OPEN = 'payload_open' @@ -44,6 +46,8 @@ CONF_PAYLOAD_CLOSE = 'payload_close' CONF_PAYLOAD_STOP = 'payload_stop' CONF_STATE_OPEN = 'state_open' CONF_STATE_CLOSED = 'state_closed' +CONF_POSITION_OPEN = 'position_open' +CONF_POSITION_CLOSED = 'position_closed' CONF_TILT_CLOSED_POSITION = 'tilt_closed_value' CONF_TILT_OPEN_POSITION = 'tilt_opened_value' CONF_TILT_MIN = 'tilt_min' @@ -52,10 +56,15 @@ CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic' CONF_TILT_INVERT_STATE = 'tilt_invert_state' CONF_UNIQUE_ID = 'unique_id' +TILT_PAYLOAD = "tilt" +COVER_PAYLOAD = "cover" + DEFAULT_NAME = 'MQTT Cover' DEFAULT_PAYLOAD_OPEN = 'OPEN' DEFAULT_PAYLOAD_CLOSE = 'CLOSE' DEFAULT_PAYLOAD_STOP = 'STOP' +DEFAULT_POSITION_OPEN = 100 +DEFAULT_POSITION_CLOSED = 0 DEFAULT_OPTIMISTIC = False DEFAULT_RETAIN = False DEFAULT_TILT_CLOSED_POSITION = 0 @@ -69,11 +78,25 @@ OPEN_CLOSE_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP) TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | SUPPORT_SET_TILT_POSITION) -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ + +def validate_options(value): + """Validate options. + + If set postion topic is set then get position topic is set as well. + """ + if (CONF_SET_POSITION_TOPIC in value and + CONF_GET_POSITION_TOPIC not in value): + raise vol.Invalid( + "Set position topic must be set together with get position topic.") + return value + + +PLATFORM_SCHEMA = vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_POSITION_TOPIC): valid_publish_topic, + vol.Optional(CONF_SET_POSITION_TOPIC): valid_publish_topic, vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + vol.Optional(CONF_GET_POSITION_TOPIC): valid_subscribe_topic, vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -82,6 +105,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string, vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, + vol.Optional(CONF_POSITION_OPEN, + default=DEFAULT_POSITION_OPEN): int, + vol.Optional(CONF_POSITION_CLOSED, + default=DEFAULT_POSITION_CLOSED): int, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_TILT_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_TILT_STATUS_TOPIC): valid_subscribe_topic, @@ -97,7 +124,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ default=DEFAULT_TILT_INVERT_STATE): cv.boolean, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, -}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) +}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema), validate_options) async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, @@ -132,6 +159,7 @@ async def _async_setup_entity(hass, config, async_add_entities, async_add_entities([MqttCover( config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), + config.get(CONF_GET_POSITION_TOPIC), config.get(CONF_COMMAND_TOPIC), config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_TILT_COMMAND_TOPIC), @@ -140,6 +168,8 @@ async def _async_setup_entity(hass, config, async_add_entities, config.get(CONF_RETAIN), config.get(CONF_STATE_OPEN), config.get(CONF_STATE_CLOSED), + config.get(CONF_POSITION_OPEN), + config.get(CONF_POSITION_CLOSED), config.get(CONF_PAYLOAD_OPEN), config.get(CONF_PAYLOAD_CLOSE), config.get(CONF_PAYLOAD_STOP), @@ -153,7 +183,7 @@ async def _async_setup_entity(hass, config, async_add_entities, config.get(CONF_TILT_MAX), config.get(CONF_TILT_STATE_OPTIMISTIC), config.get(CONF_TILT_INVERT_STATE), - config.get(CONF_POSITION_TOPIC), + config.get(CONF_SET_POSITION_TOPIC), set_position_template, config.get(CONF_UNIQUE_ID), config.get(CONF_DEVICE), @@ -165,15 +195,16 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, CoverDevice): """Representation of a cover that can be controlled using MQTT.""" - def __init__(self, name, state_topic, command_topic, availability_topic, + def __init__(self, name, state_topic, get_position_topic, + command_topic, availability_topic, tilt_command_topic, tilt_status_topic, qos, retain, - state_open, state_closed, payload_open, payload_close, - payload_stop, payload_available, payload_not_available, - optimistic, value_template, tilt_open_position, - tilt_closed_position, tilt_min, tilt_max, tilt_optimistic, - tilt_invert, position_topic, set_position_template, - unique_id: Optional[str], device_config: Optional[ConfigType], - discovery_hash): + state_open, state_closed, position_open, position_closed, + payload_open, payload_close, payload_stop, payload_available, + payload_not_available, optimistic, value_template, + tilt_open_position, tilt_closed_position, tilt_min, tilt_max, + tilt_optimistic, tilt_invert, set_position_topic, + set_position_template, unique_id: Optional[str], + device_config: Optional[ConfigType], discovery_hash): """Initialize the cover.""" MqttAvailability.__init__(self, availability_topic, qos, payload_available, payload_not_available) @@ -183,6 +214,7 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, self._state = None self._name = name self._state_topic = state_topic + self._get_position_topic = get_position_topic self._command_topic = command_topic self._tilt_command_topic = tilt_command_topic self._tilt_status_topic = tilt_status_topic @@ -192,17 +224,20 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, self._payload_stop = payload_stop self._state_open = state_open self._state_closed = state_closed + self._position_open = position_open + self._position_closed = position_closed self._retain = retain self._tilt_open_position = tilt_open_position self._tilt_closed_position = tilt_closed_position - self._optimistic = optimistic or state_topic is None + self._optimistic = (optimistic or (state_topic is None and + get_position_topic is None)) self._template = value_template self._tilt_value = None self._tilt_min = tilt_min self._tilt_max = tilt_max self._tilt_optimistic = tilt_optimistic self._tilt_invert = tilt_invert - self._position_topic = position_topic + self._set_position_topic = set_position_topic self._set_position_template = set_position_template self._unique_id = unique_id self._discovery_hash = discovery_hash @@ -233,27 +268,43 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, self._state = False elif payload == self._state_closed: self._state = True - elif payload.isnumeric() and 0 <= int(payload) <= 100: - if int(payload) > 0: - self._state = False - else: - self._state = True - self._position = int(payload) else: - _LOGGER.warning( - "Payload is not True, False, or integer (0-100): %s", - payload) + _LOGGER.warning("Payload is not True or False: %s", payload) return - self.async_schedule_update_ha_state() - if self._state_topic is None: - # Force into optimistic mode. - self._optimistic = True - else: + @callback + def position_message_received(topic, payload, qos): + """Handle new MQTT state messages.""" + if self._template is not None: + payload = self._template.async_render_with_possible_json_value( + payload) + if payload.isnumeric(): + if 0 <= int(payload) <= 100: + percentage_payload = int(payload) + else: + percentage_payload = self.find_percentage_in_range( + float(payload), COVER_PAYLOAD) + if 0 <= percentage_payload <= 100: + self._position = percentage_payload + self._state = self._position == 0 + else: + _LOGGER.warning( + "Payload is not integer within range: %s", + payload) + return + self.async_schedule_update_ha_state() + if self._get_position_topic: + await mqtt.async_subscribe( + self.hass, self._get_position_topic, + position_message_received, self._qos) + elif self._state_topic: await mqtt.async_subscribe( self.hass, self._state_topic, state_message_received, self._qos) + else: + # Force into optimistic mode. + self._optimistic = True if self._tilt_status_topic is None: self._tilt_optimistic = True @@ -303,7 +354,7 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, if self._command_topic is not None: supported_features = OPEN_CLOSE_FEATURES - if self._position_topic is not None: + if self._set_position_topic is not None: supported_features |= SUPPORT_SET_POSITION if self._tilt_command_topic is not None: @@ -322,6 +373,8 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, if self._optimistic: # Optimistically assume that cover has changed state. self._state = False + if self._get_position_topic: + self._position = self._position_open self.async_schedule_update_ha_state() async def async_close_cover(self, **kwargs): @@ -335,6 +388,8 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, if self._optimistic: # Optimistically assume that cover has changed state. self._state = True + if self._get_position_topic: + self._position = self._position_closed self.async_schedule_update_ha_state() async def async_stop_cover(self, **kwargs): @@ -381,6 +436,7 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, """Move the cover to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] + percentage_position = position if self._set_position_template is not None: try: position = self._set_position_template.async_render( @@ -388,23 +444,36 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, except TemplateError as ex: _LOGGER.error(ex) self._state = None + elif self._position_open != 100 and self._position_closed != 0: + position = self.find_in_range_from_percent( + position, COVER_PAYLOAD) - mqtt.async_publish(self.hass, self._position_topic, + mqtt.async_publish(self.hass, self._set_position_topic, position, self._qos, self._retain) + if self._optimistic: + self._state = percentage_position == 0 + self._position = percentage_position + self.async_schedule_update_ha_state() - def find_percentage_in_range(self, position): + def find_percentage_in_range(self, position, range_type=TILT_PAYLOAD): """Find the 0-100% value within the specified range.""" # the range of motion as defined by the min max values - tilt_range = self._tilt_max - self._tilt_min + if range_type == COVER_PAYLOAD: + max_range = self._position_open + min_range = self._position_closed + else: + max_range = self._tilt_max + min_range = self._tilt_min + current_range = max_range - min_range # offset to be zero based - offset_position = position - self._tilt_min - # the percentage value within the range - position_percentage = float(offset_position) / tilt_range * 100.0 - if self._tilt_invert: + offset_position = position - min_range + position_percentage = round( + float(offset_position) / current_range * 100.0) + if range_type == TILT_PAYLOAD and self._tilt_invert: return 100 - position_percentage return position_percentage - def find_in_range_from_percent(self, percentage): + def find_in_range_from_percent(self, percentage, range_type=TILT_PAYLOAD): """ Find the adjusted value for 0-100% within the specified range. @@ -413,14 +482,19 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, by offsetting the max and min, getting the percentage value and returning the offset """ - offset = self._tilt_min - tilt_range = self._tilt_max - self._tilt_min - - position = round(tilt_range * (percentage / 100.0)) + if range_type == COVER_PAYLOAD: + max_range = self._position_open + min_range = self._position_closed + else: + max_range = self._tilt_max + min_range = self._tilt_min + offset = min_range + current_range = max_range - min_range + position = round(current_range * (percentage / 100.0)) position += offset - if self._tilt_invert: - position = self._tilt_max - position + offset + if range_type == TILT_PAYLOAD and self._tilt_invert: + position = max_range - position + offset return position @property diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index 09ac04f359d..81c0848c4c5 100644 --- a/tests/components/cover/test_mqtt.py +++ b/tests/components/cover/test_mqtt.py @@ -50,24 +50,6 @@ class TestCoverMQTT(unittest.TestCase): assert STATE_UNKNOWN == state.state assert not state.attributes.get(ATTR_ASSUMED_STATE) - fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.block_till_done() - - state = self.hass.states.get('cover.test') - assert STATE_CLOSED == state.state - - fire_mqtt_message(self.hass, 'state-topic', '50') - self.hass.block_till_done() - - state = self.hass.states.get('cover.test') - assert STATE_OPEN == state.state - - fire_mqtt_message(self.hass, 'state-topic', '100') - self.hass.block_till_done() - - state = self.hass.states.get('cover.test') - assert STATE_OPEN == state.state - fire_mqtt_message(self.hass, 'state-topic', STATE_CLOSED) self.hass.block_till_done() @@ -80,6 +62,39 @@ class TestCoverMQTT(unittest.TestCase): state = self.hass.states.get('cover.test') assert STATE_OPEN == state.state + def test_position_via_position_topic(self): + """Test the controlling state via topic.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'position_topic': 'get-position-topic', + 'position_open': 100, + 'position_closed': 0, + 'command_topic': 'command-topic', + 'qos': 0, + 'payload_open': 'OPEN', + 'payload_close': 'CLOSE', + 'payload_stop': 'STOP' + } + })) + + state = self.hass.states.get('cover.test') + assert STATE_UNKNOWN == state.state + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + fire_mqtt_message(self.hass, 'get-position-topic', '0') + self.hass.block_till_done() + + state = self.hass.states.get('cover.test') + assert STATE_CLOSED == state.state + + fire_mqtt_message(self.hass, 'get-position-topic', '100') + self.hass.block_till_done() + + state = self.hass.states.get('cover.test') + assert STATE_OPEN == state.state + def test_state_via_template(self): """Test the controlling state via topic.""" assert setup_component(self.hass, cover.DOMAIN, { @@ -89,7 +104,12 @@ class TestCoverMQTT(unittest.TestCase): 'state_topic': 'state-topic', 'command_topic': 'command-topic', 'qos': 0, - 'value_template': '{{ (value | multiply(0.01)) | int }}', + 'value_template': '\ + {% if (value | multiply(0.01) | int) == 0 %}\ + closed\ + {% else %}\ + open\ + {% endif %}' } }) @@ -108,6 +128,40 @@ class TestCoverMQTT(unittest.TestCase): state = self.hass.states.get('cover.test') assert STATE_CLOSED == state.state + def test_position_via_template(self): + """Test the controlling state via topic.""" + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { + cover.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'position_topic': 'get-position-topic', + 'command_topic': 'command-topic', + 'qos': 0, + 'value_template': '{{ (value | multiply(0.01)) | int }}' + } + })) + + state = self.hass.states.get('cover.test') + self.assertEqual(STATE_UNKNOWN, state.state) + + fire_mqtt_message(self.hass, 'get-position-topic', '10000') + self.hass.block_till_done() + + state = self.hass.states.get('cover.test') + self.assertEqual(STATE_OPEN, state.state) + + fire_mqtt_message(self.hass, 'get-position-topic', '5000') + self.hass.block_till_done() + + state = self.hass.states.get('cover.test') + self.assertEqual(STATE_OPEN, state.state) + + fire_mqtt_message(self.hass, 'get-position-topic', '99') + self.hass.block_till_done() + + state = self.hass.states.get('cover.test') + self.assertEqual(STATE_CLOSED, state.state) + def test_optimistic_state_change(self): """Test changing state optimistically.""" assert setup_component(self.hass, cover.DOMAIN, { @@ -225,8 +279,10 @@ class TestCoverMQTT(unittest.TestCase): cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'state-topic', + 'position_topic': 'get-position-topic', 'command_topic': 'command-topic', + 'position_open': 100, + 'position_closed': 0, 'payload_open': 'OPEN', 'payload_close': 'CLOSE', 'payload_stop': 'STOP' @@ -240,25 +296,25 @@ class TestCoverMQTT(unittest.TestCase): assert not (4 & self.hass.states.get( 'cover.test').attributes['supported_features'] == 4) - fire_mqtt_message(self.hass, 'state-topic', '0') + fire_mqtt_message(self.hass, 'get-position-topic', '0') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] assert 0 == current_cover_position - fire_mqtt_message(self.hass, 'state-topic', '50') + fire_mqtt_message(self.hass, 'get-position-topic', '50') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] assert 50 == current_cover_position - fire_mqtt_message(self.hass, 'state-topic', '101') + fire_mqtt_message(self.hass, 'get-position-topic', '101') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] assert 50 == current_cover_position - fire_mqtt_message(self.hass, 'state-topic', 'non-numeric') + fire_mqtt_message(self.hass, 'get-position-topic', 'non-numeric') self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] @@ -270,9 +326,11 @@ class TestCoverMQTT(unittest.TestCase): cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'state-topic', + 'position_topic': 'get-position-topic', 'command_topic': 'command-topic', - 'set_position_topic': 'position-topic', + 'set_position_topic': 'set-position-topic', + 'position_open': 100, + 'position_closed': 0, 'payload_open': 'OPEN', 'payload_close': 'CLOSE', 'payload_stop': 'STOP' @@ -283,11 +341,10 @@ class TestCoverMQTT(unittest.TestCase): 'cover.test').attributes assert not ('current_position' in state_attributes_dict) assert not ('current_tilt_position' in state_attributes_dict) - assert 4 & self.hass.states.get( 'cover.test').attributes['supported_features'] == 4 - fire_mqtt_message(self.hass, 'state-topic', '22') + fire_mqtt_message(self.hass, 'get-position-topic', '22') self.hass.block_till_done() state_attributes_dict = self.hass.states.get( 'cover.test').attributes @@ -303,9 +360,11 @@ class TestCoverMQTT(unittest.TestCase): cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'state-topic', + 'position_topic': 'get-position-topic', 'command_topic': 'command-topic', - 'set_position_topic': 'position-topic', + 'position_open': 100, + 'position_closed': 0, + 'set_position_topic': 'set-position-topic', 'set_position_template': '{{100-62}}', 'payload_open': 'OPEN', 'payload_close': 'CLOSE', @@ -319,7 +378,7 @@ class TestCoverMQTT(unittest.TestCase): self.hass.block_till_done() self.mock_publish.async_publish.assert_called_once_with( - 'position-topic', '38', 0, False) + 'set-position-topic', '38', 0, False) def test_set_position_untemplated(self): """Test setting cover position via template.""" @@ -327,7 +386,7 @@ class TestCoverMQTT(unittest.TestCase): cover.DOMAIN: { 'platform': 'mqtt', 'name': 'test', - 'state_topic': 'state-topic', + 'position_topic': 'state-topic', 'command_topic': 'command-topic', 'set_position_topic': 'position-topic', 'payload_open': 'OPEN', @@ -612,90 +671,210 @@ class TestCoverMQTT(unittest.TestCase): def test_find_percentage_in_range_defaults(self): """Test find percentage in range with default range.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 100, 0, 0, 100, False, False, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=100, position_closed=0, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=100, tilt_closed_position=0, + tilt_min=0, tilt_max=100, tilt_optimistic=False, + tilt_invert=False, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 44 == mqtt_cover.find_percentage_in_range(44) + assert 44 == mqtt_cover.find_percentage_in_range(44, 'cover') def test_find_percentage_in_range_altered(self): """Test find percentage in range with altered range.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 180, 80, 80, 180, False, False, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=180, position_closed=80, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=180, tilt_closed_position=80, + tilt_min=80, tilt_max=180, tilt_optimistic=False, + tilt_invert=False, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 40 == mqtt_cover.find_percentage_in_range(120) + assert 40 == mqtt_cover.find_percentage_in_range(120, 'cover') def test_find_percentage_in_range_defaults_inverted(self): """Test find percentage in range with default range but inverted.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 100, 0, 0, 100, False, True, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=0, position_closed=100, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=100, tilt_closed_position=0, + tilt_min=0, tilt_max=100, tilt_optimistic=False, + tilt_invert=True, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 56 == mqtt_cover.find_percentage_in_range(44) + assert 56 == mqtt_cover.find_percentage_in_range(44, 'cover') def test_find_percentage_in_range_altered_inverted(self): """Test find percentage in range with altered range and inverted.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 180, 80, 80, 180, False, True, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=80, position_closed=180, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=180, tilt_closed_position=80, + tilt_min=80, tilt_max=180, tilt_optimistic=False, + tilt_invert=True, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 60 == mqtt_cover.find_percentage_in_range(120) + assert 60 == mqtt_cover.find_percentage_in_range(120, 'cover') def test_find_in_range_defaults(self): """Test find in range with default range.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 100, 0, 0, 100, False, False, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=100, position_closed=0, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=100, tilt_closed_position=0, + tilt_min=0, tilt_max=100, tilt_optimistic=False, + tilt_invert=False, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 44 == mqtt_cover.find_in_range_from_percent(44) + assert 44 == mqtt_cover.find_in_range_from_percent(44, 'cover') def test_find_in_range_altered(self): """Test find in range with altered range.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 180, 80, 80, 180, False, False, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=180, position_closed=80, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=180, tilt_closed_position=80, + tilt_min=80, tilt_max=180, tilt_optimistic=False, + tilt_invert=False, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 120 == mqtt_cover.find_in_range_from_percent(40) + assert 120 == mqtt_cover.find_in_range_from_percent(40, 'cover') def test_find_in_range_defaults_inverted(self): """Test find in range with default range but inverted.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 100, 0, 0, 100, False, True, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=0, position_closed=100, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=100, tilt_closed_position=0, + tilt_min=0, tilt_max=100, tilt_optimistic=False, + tilt_invert=True, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 44 == mqtt_cover.find_in_range_from_percent(56) + assert 44 == mqtt_cover.find_in_range_from_percent(56, 'cover') def test_find_in_range_altered_inverted(self): """Test find in range with altered range and inverted.""" mqtt_cover = MqttCover( - 'cover.test', 'state-topic', 'command-topic', None, - 'tilt-command-topic', 'tilt-status-topic', 0, False, - 'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None, - False, None, 180, 80, 80, 180, False, True, None, None, None, - None, None) + name='cover.test', + state_topic='state-topic', + get_position_topic=None, + command_topic='command-topic', + availability_topic=None, + tilt_command_topic='tilt-command-topic', + tilt_status_topic='tilt-status-topic', + qos=0, + retain=False, + state_open='OPEN', state_closed='CLOSE', + position_open=80, position_closed=180, + payload_open='OPEN', payload_close='CLOSE', payload_stop='STOP', + payload_available=None, payload_not_available=None, + optimistic=False, value_template=None, + tilt_open_position=180, tilt_closed_position=80, + tilt_min=80, tilt_max=180, tilt_optimistic=False, + tilt_invert=True, + set_position_topic=None, set_position_template=None, + unique_id=None, device_config=None, discovery_hash=None) assert 120 == mqtt_cover.find_in_range_from_percent(60) + assert 120 == mqtt_cover.find_in_range_from_percent(60, 'cover') def test_availability_without_topic(self): """Test availability without defined availability topic.""" From 31dc6832e71d7b60c380a0dbe122a78f901fb5a4 Mon Sep 17 00:00:00 2001 From: Oleksii Serdiuk Date: Thu, 1 Nov 2018 20:57:32 +0100 Subject: [PATCH 177/230] Darksky: Fetch summary for daily forecasts (#18031) --- homeassistant/components/sensor/darksky.py | 2 +- tests/components/sensor/test_darksky.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index ad59f28adb3..a43ab888d04 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -43,7 +43,7 @@ DEPRECATED_SENSOR_TYPES = { # Sensor types are defined like so: # Name, si unit, us unit, ca unit, uk unit, uk2 unit SENSOR_TYPES = { - 'summary': ['Summary', None, None, None, None, None, None, []], + 'summary': ['Summary', None, None, None, None, None, None, ['daily']], 'minutely_summary': ['Minutely Summary', None, None, None, None, None, None, []], 'hourly_summary': ['Hourly Summary', None, None, None, None, None, None, diff --git a/tests/components/sensor/test_darksky.py b/tests/components/sensor/test_darksky.py index fb3c89db097..ccfe4344373 100644 --- a/tests/components/sensor/test_darksky.py +++ b/tests/components/sensor/test_darksky.py @@ -154,7 +154,7 @@ class TestDarkSkySetup(unittest.TestCase): assert mock_get_forecast.called assert mock_get_forecast.call_count == 1 - assert len(self.hass.states.entity_ids()) == 7 + assert len(self.hass.states.entity_ids()) == 9 state = self.hass.states.get('sensor.dark_sky_summary') assert state is not None From e9ae862fcaf4012006da36ae049267ba77d1e28f Mon Sep 17 00:00:00 2001 From: Andy Castille Date: Thu, 1 Nov 2018 15:23:06 -0500 Subject: [PATCH 178/230] Update to DoorBirdPy v2 (again) (#14933) * Update to DoorBirdPy v2 * Move get_schedule_entry to DoorBirdPy, general cleanup * Update requirements_all.txt * Requested changes. * Update requirements post merge. * Correct call to async_add_executor_job * Update clear schedule endpoint to be async * Refactor view and device favorite reset * Register listeners so events show in GUI * Add token based authorization * Update requirements * Linting issues * Linting issues * Linting issues * Correct logging and inheritance --- .coveragerc | 4 +- homeassistant/components/doorbird.py | 310 ++++++++++++++++---- homeassistant/components/switch/doorbird.py | 81 ++--- requirements_all.txt | 6 +- 4 files changed, 283 insertions(+), 118 deletions(-) diff --git a/.coveragerc b/.coveragerc index 8cb2b06683b..e63ac6daec9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -81,8 +81,8 @@ omit = homeassistant/components/dominos.py - homeassistant/components/doorbird.py - homeassistant/components/*/doorbird.py + homeassistant/components/doorbird.py + homeassistant/components/*/doorbird.py homeassistant/components/dweet.py homeassistant/components/*/dweet.py diff --git a/homeassistant/components/doorbird.py b/homeassistant/components/doorbird.py index ab929eb90bb..578855011cc 100644 --- a/homeassistant/components/doorbird.py +++ b/homeassistant/components/doorbird.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, \ import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify -REQUIREMENTS = ['DoorBirdPy==0.1.3'] +REQUIREMENTS = ['doorbirdpy==2.0.4'] _LOGGER = logging.getLogger(__name__) @@ -22,22 +22,31 @@ DOMAIN = 'doorbird' API_URL = '/api/{}'.format(DOMAIN) -CONF_DOORBELL_EVENTS = 'doorbell_events' CONF_CUSTOM_URL = 'hass_url_override' +CONF_DOORBELL_EVENTS = 'doorbell_events' +CONF_DOORBELL_NUMS = 'doorbell_numbers' +CONF_MOTION_EVENTS = 'motion_events' +CONF_TOKEN = 'token' -DOORBELL_EVENT = 'doorbell' -MOTION_EVENT = 'motionsensor' - -# Sensor types: Name, device_class, event SENSOR_TYPES = { - 'doorbell': ['Button', 'occupancy', DOORBELL_EVENT], - 'motion': ['Motion', 'motion', MOTION_EVENT], + 'doorbell': { + 'name': 'Button', + 'device_class': 'occupancy', + }, + 'motion': { + 'name': 'Motion', + 'device_class': 'motion', + }, } +RESET_DEVICE_FAVORITES = 'doorbird_reset_favorites' + DEVICE_SCHEMA = vol.Schema({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_DOORBELL_NUMS, default=[1]): vol.All( + cv.ensure_list, [cv.positive_int]), vol.Optional(CONF_CUSTOM_URL): cv.string, vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): @@ -46,6 +55,7 @@ DEVICE_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ + vol.Required(CONF_TOKEN): cv.string, vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_SCHEMA]) }), }, extra=vol.ALLOW_EXTRA) @@ -55,8 +65,13 @@ def setup(hass, config): """Set up the DoorBird component.""" from doorbirdpy import DoorBird + token = config[DOMAIN].get(CONF_TOKEN) + # Provide an endpoint for the doorstations to call to trigger events - hass.http.register_view(DoorbirdRequestView()) + hass.http.register_view(DoorBirdRequestView(token)) + + # Provide an endpoint for the user to call to clear device changes + hass.http.register_view(DoorBirdCleanupView(token)) doorstations = [] @@ -64,6 +79,7 @@ def setup(hass, config): device_ip = doorstation_config.get(CONF_HOST) username = doorstation_config.get(CONF_USERNAME) password = doorstation_config.get(CONF_PASSWORD) + doorbell_nums = doorstation_config.get(CONF_DOORBELL_NUMS) custom_url = doorstation_config.get(CONF_CUSTOM_URL) events = doorstation_config.get(CONF_MONITORED_CONDITIONS) name = (doorstation_config.get(CONF_NAME) @@ -73,68 +89,73 @@ def setup(hass, config): status = device.ready() if status[0]: - _LOGGER.info("Connected to DoorBird at %s as %s", device_ip, - username) - doorstation = ConfiguredDoorbird(device, name, events, custom_url) + doorstation = ConfiguredDoorBird(device, name, events, custom_url, + doorbell_nums, token) doorstations.append(doorstation) + _LOGGER.info('Connected to DoorBird "%s" as %s@%s', + doorstation.name, username, device_ip) elif status[1] == 401: - _LOGGER.error("Authorization rejected by DoorBird at %s", - device_ip) + _LOGGER.error("Authorization rejected by DoorBird for %s@%s", + username, device_ip) return False else: - _LOGGER.error("Could not connect to DoorBird at %s: Error %s", - device_ip, str(status[1])) + _LOGGER.error("Could not connect to DoorBird as %s@%s: Error %s", + username, device_ip, str(status[1])) return False - # SETUP EVENT SUBSCRIBERS + # Subscribe to doorbell or motion events if events is not None: - # This will make HA the only service that receives events. - doorstation.device.reset_notifications() - - # Subscribe to doorbell or motion events - subscribe_events(hass, doorstation) + doorstation.update_schedule(hass) hass.data[DOMAIN] = doorstations + def _reset_device_favorites_handler(event): + """Handle clearing favorites on device.""" + slug = event.data.get('slug') + + if slug is None: + return + + doorstation = get_doorstation_by_slug(hass, slug) + + if doorstation is None: + _LOGGER.error('Device not found %s', format(slug)) + + # Clear webhooks + favorites = doorstation.device.favorites() + + for favorite_type in favorites: + for favorite_id in favorites[favorite_type]: + doorstation.device.delete_favorite(favorite_type, favorite_id) + + hass.bus.listen(RESET_DEVICE_FAVORITES, _reset_device_favorites_handler) + return True -def subscribe_events(hass, doorstation): - """Initialize the subscriber.""" - for sensor_type in doorstation.monitored_events: - name = '{} {}'.format(doorstation.name, - SENSOR_TYPES[sensor_type][0]) - event_type = SENSOR_TYPES[sensor_type][2] - - # Get the URL of this server - hass_url = hass.config.api.base_url - - # Override url if another is specified onth configuration - if doorstation.custom_url is not None: - hass_url = doorstation.custom_url - - slug = slugify(name) - - url = '{}{}/{}'.format(hass_url, API_URL, slug) - - _LOGGER.info("DoorBird will connect to this instance via %s", - url) - - _LOGGER.info("You may use the following event name for automations" - ": %s_%s", DOMAIN, slug) - - doorstation.device.subscribe_notification(event_type, url) +def get_doorstation_by_slug(hass, slug): + """Get doorstation by slug.""" + for doorstation in hass.data[DOMAIN]: + if slugify(doorstation.name) in slug: + return doorstation -class ConfiguredDoorbird(): +def handle_event(event): + """Handle dummy events.""" + return None + + +class ConfiguredDoorBird(): """Attach additional information to pass along with configured device.""" - def __init__(self, device, name, events=None, custom_url=None): + def __init__(self, device, name, events, custom_url, doorbell_nums, token): """Initialize configured device.""" self._name = name self._device = device self._custom_url = custom_url self._monitored_events = events + self._doorbell_nums = doorbell_nums + self._token = token @property def name(self): @@ -151,16 +172,139 @@ class ConfiguredDoorbird(): """Get custom url for device.""" return self._custom_url - @property - def monitored_events(self): - """Get monitored events.""" - if self._monitored_events is None: - return [] + def update_schedule(self, hass): + """Register monitored sensors and deregister others.""" + from doorbirdpy import DoorBirdScheduleEntrySchedule - return self._monitored_events + # Create a new schedule (24/7) + schedule = DoorBirdScheduleEntrySchedule() + schedule.add_weekday(0, 604800) # seconds in a week + + # Get the URL of this server + hass_url = hass.config.api.base_url + + # Override url if another is specified in the configuration + if self.custom_url is not None: + hass_url = self.custom_url + + # For all sensor types (enabled + disabled) + for sensor_type in SENSOR_TYPES: + name = '{} {}'.format(self.name, SENSOR_TYPES[sensor_type]['name']) + slug = slugify(name) + + url = '{}{}/{}?token={}'.format(hass_url, API_URL, slug, + self._token) + if sensor_type in self._monitored_events: + # Enabled -> register + self._register_event(url, sensor_type, schedule) + _LOGGER.info('Registered for %s pushes from DoorBird "%s". ' + 'Use the "%s_%s" event for automations.', + sensor_type, self.name, DOMAIN, slug) + + # Register a dummy listener so event is listed in GUI + hass.bus.listen('{}_{}'.format(DOMAIN, slug), handle_event) + else: + # Disabled -> deregister + self._deregister_event(url, sensor_type) + _LOGGER.info('Deregistered %s pushes from DoorBird "%s". ' + 'If any old favorites or schedules remain, ' + 'follow the instructions in the component ' + 'documentation to clear device registrations.', + sensor_type, self.name) + + def _register_event(self, hass_url, event, schedule): + """Add a schedule entry in the device for a sensor.""" + from doorbirdpy import DoorBirdScheduleEntryOutput + + # Register HA URL as webhook if not already, then get the ID + if not self.webhook_is_registered(hass_url): + self.device.change_favorite('http', + 'Home Assistant on {} ({} events)' + .format(hass_url, event), hass_url) + fav_id = self.get_webhook_id(hass_url) + + if not fav_id: + _LOGGER.warning('Could not find favorite for URL "%s". ' + 'Skipping sensor "%s".', hass_url, event) + return + + # Add event handling to device schedule + output = DoorBirdScheduleEntryOutput(event='http', + param=fav_id, + schedule=schedule) + + if event == 'doorbell': + # Repeat edit for each monitored doorbell number + for doorbell in self._doorbell_nums: + entry = self.device.get_schedule_entry(event, str(doorbell)) + entry.output.append(output) + self.device.change_schedule(entry) + else: + entry = self.device.get_schedule_entry(event) + entry.output.append(output) + self.device.change_schedule(entry) + + def _deregister_event(self, hass_url, event): + """Remove the schedule entry in the device for a sensor.""" + # Find the right favorite and delete it + fav_id = self.get_webhook_id(hass_url) + if not fav_id: + return + + self._device.delete_favorite('http', fav_id) + + if event == 'doorbell': + # Delete the matching schedule for each doorbell number + for doorbell in self._doorbell_nums: + self._delete_schedule_action(event, fav_id, str(doorbell)) + else: + self._delete_schedule_action(event, fav_id) + + def _delete_schedule_action(self, sensor, fav_id, param=""): + """Remove the HA output from a schedule.""" + entries = self._device.schedule() + for entry in entries: + if entry.input != sensor or entry.param != param: + continue + + for action in entry.output: + if action.event == 'http' and action.param == fav_id: + entry.output.remove(action) + + self._device.change_schedule(entry) + + def webhook_is_registered(self, ha_url, favs=None) -> bool: + """Return whether the given URL is registered as a device favorite.""" + favs = favs if favs else self.device.favorites() + + if 'http' not in favs: + return False + + for fav in favs['http'].values(): + if fav['value'] == ha_url: + return True + + return False + + def get_webhook_id(self, ha_url, favs=None) -> str or None: + """ + Return the device favorite ID for the given URL. + + The favorite must exist or there will be problems. + """ + favs = favs if favs else self.device.favorites() + + if 'http' not in favs: + return None + + for fav_id in favs['http']: + if favs['http'][fav_id]['value'] == ha_url: + return fav_id + + return None -class DoorbirdRequestView(HomeAssistantView): +class DoorBirdRequestView(HomeAssistantView): """Provide a page for the device to call.""" requires_auth = False @@ -168,11 +312,63 @@ class DoorbirdRequestView(HomeAssistantView): name = API_URL[1:].replace('/', ':') extra_urls = [API_URL + '/{sensor}'] + def __init__(self, token): + """Initialize view.""" + HomeAssistantView.__init__(self) + self._token = token + # pylint: disable=no-self-use async def get(self, request, sensor): """Respond to requests from the device.""" + from aiohttp import web hass = request.app['hass'] + request_token = request.query.get('token') + + authenticated = request_token == self._token + + if request_token == '' or not authenticated: + return web.Response(status=401, text='Unauthorized') + hass.bus.async_fire('{}_{}'.format(DOMAIN, sensor)) - return 'OK' + return web.Response(status=200, text='OK') + + +class DoorBirdCleanupView(HomeAssistantView): + """Provide a URL to call to delete ALL webhooks/schedules.""" + + requires_auth = False + url = API_URL + '/clear/{slug}' + name = 'DoorBird Cleanup' + + def __init__(self, token): + """Initialize view.""" + HomeAssistantView.__init__(self) + self._token = token + + # pylint: disable=no-self-use + async def get(self, request, slug): + """Act on requests.""" + from aiohttp import web + hass = request.app['hass'] + + request_token = request.query.get('token') + + authenticated = request_token == self._token + + if request_token == '' or not authenticated: + return web.Response(status=401, text='Unauthorized') + + device = get_doorstation_by_slug(hass, slug) + + # No matching device + if device is None: + return web.Response(status=404, + text='Device slug {} not found'.format(slug)) + + hass.bus.async_fire(RESET_DEVICE_FAVORITES, + {'slug': slug}) + + message = 'Clearing schedule for {}'.format(slug) + return web.Response(status=200, text=message) diff --git a/homeassistant/components/switch/doorbird.py b/homeassistant/components/switch/doorbird.py index 17a4757d4ac..376713d4b27 100644 --- a/homeassistant/components/switch/doorbird.py +++ b/homeassistant/components/switch/doorbird.py @@ -2,48 +2,14 @@ import datetime import logging -import voluptuous as vol - -import homeassistant.helpers.config_validation as cv from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice -from homeassistant.const import CONF_SWITCHES +from homeassistant.components.switch import SwitchDevice DEPENDENCIES = ['doorbird'] _LOGGER = logging.getLogger(__name__) -SWITCHES = { - "open_door": { - "name": "{} Open Door", - "icon": { - True: "lock-open", - False: "lock" - }, - "time": datetime.timedelta(seconds=3) - }, - "open_door_2": { - "name": "{} Open Door 2", - "icon": { - True: "lock-open", - False: "lock" - }, - "time": datetime.timedelta(seconds=3) - }, - "light_on": { - "name": "{} Light On", - "icon": { - True: "lightbulb-on", - False: "lightbulb" - }, - "time": datetime.timedelta(minutes=5) - } -} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SWITCHES, default=[]): - vol.All(cv.ensure_list([vol.In(SWITCHES)])) -}) +IR_RELAY = '__ir_light__' def setup_platform(hass, config, add_entities, discovery_info=None): @@ -51,14 +17,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): switches = [] for doorstation in hass.data[DOORBIRD_DOMAIN]: + relays = doorstation.device.info()['RELAYS'] + relays.append(IR_RELAY) - device = doorstation.device - - for switch in SWITCHES: - - _LOGGER.debug("Adding DoorBird switch %s", - SWITCHES[switch]["name"].format(doorstation.name)) - switches.append(DoorBirdSwitch(device, switch, doorstation.name)) + for relay in relays: + switch = DoorBirdSwitch(doorstation, relay) + switches.append(switch) add_entities(switches) @@ -66,23 +30,30 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class DoorBirdSwitch(SwitchDevice): """A relay in a DoorBird device.""" - def __init__(self, device, switch, name): + def __init__(self, doorstation, relay): """Initialize a relay in a DoorBird device.""" - self._device = device - self._switch = switch - self._name = name + self._doorstation = doorstation + self._relay = relay self._state = False self._assume_off = datetime.datetime.min + if relay == IR_RELAY: + self._time = datetime.timedelta(minutes=5) + else: + self._time = datetime.timedelta(seconds=5) + @property def name(self): """Return the name of the switch.""" - return SWITCHES[self._switch]["name"].format(self._name) + if self._relay == IR_RELAY: + return "{} IR".format(self._doorstation.name) + + return "{} Relay {}".format(self._doorstation.name, self._relay) @property def icon(self): """Return the icon to display.""" - return "mdi:{}".format(SWITCHES[self._switch]["icon"][self._state]) + return "mdi:lightbulb" if self._relay == IR_RELAY else "mdi:dip-switch" @property def is_on(self): @@ -91,15 +62,13 @@ class DoorBirdSwitch(SwitchDevice): def turn_on(self, **kwargs): """Power the relay.""" - if self._switch == "open_door": - self._state = self._device.open_door() - elif self._switch == "open_door_2": - self._state = self._device.open_door(2) - elif self._switch == "light_on": - self._state = self._device.turn_light_on() + if self._relay == IR_RELAY: + self._state = self._doorstation.device.turn_light_on() + else: + self._state = self._doorstation.device.energize_relay(self._relay) now = datetime.datetime.now() - self._assume_off = now + SWITCHES[self._switch]["time"] + self._assume_off = now + self._time def turn_off(self, **kwargs): """Turn off the relays is not needed. They are time-based.""" diff --git a/requirements_all.txt b/requirements_all.txt index be86435ea84..9f88916d245 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -31,9 +31,6 @@ Adafruit-SHT31==1.0.2 # homeassistant.components.bbb_gpio # Adafruit_BBIO==1.0.0 -# homeassistant.components.doorbird -DoorBirdPy==0.1.3 - # homeassistant.components.homekit HAP-python==2.2.2 @@ -313,6 +310,9 @@ distro==1.3.0 # homeassistant.components.switch.digitalloggers dlipower==0.7.165 +# homeassistant.components.doorbird +doorbirdpy==2.0.4 + # homeassistant.components.sensor.dovado dovado==0.4.1 From 02b46e2ba3732d1b223ebb30c2a98fe2c41e9c77 Mon Sep 17 00:00:00 2001 From: Lev Aronsky Date: Thu, 1 Nov 2018 22:25:50 +0200 Subject: [PATCH 179/230] Ignore min_cycle_duration when manually controlling the thermostat. (#16128) * Ignore min_cycle_duration when manually controlling the thermostat. * style * Generic thermostat: add minimum cycle duration to keep-alive tests. There was a bug in previous versions of the code, that would not execute the keep-alive action if the minimum cycle duration hasn't passed. This test verifies that the keep-alive action is executed correctly. * Generic thermostat: added tests to verify that changing the thermostat mode manually triggers the switch, regardless of minimum cycle duration. * Updated tests to use `common` module instead of the deprecated `climate` --- .../components/climate/generic_thermostat.py | 37 ++++++----- .../climate/test_generic_thermostat.py | 66 +++++++++++++++++++ 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index ad8875462fd..d421157c2ec 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -232,11 +232,11 @@ class GenericThermostat(ClimateDevice): if operation_mode == STATE_HEAT: self._current_operation = STATE_HEAT self._enabled = True - await self._async_control_heating() + await self._async_control_heating(force=True) elif operation_mode == STATE_COOL: self._current_operation = STATE_COOL self._enabled = True - await self._async_control_heating() + await self._async_control_heating(force=True) elif operation_mode == STATE_OFF: self._current_operation = STATE_OFF self._enabled = False @@ -262,7 +262,7 @@ class GenericThermostat(ClimateDevice): if temperature is None: return self._target_temp = temperature - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() @property @@ -307,7 +307,7 @@ class GenericThermostat(ClimateDevice): except ValueError as ex: _LOGGER.error("Unable to update from sensor: %s", ex) - async def _async_control_heating(self, time=None): + async def _async_control_heating(self, time=None, force=False): """Check if we need to turn heating on or off.""" async with self._temp_lock: if not self._active and None not in (self._cur_temp, @@ -320,16 +320,21 @@ class GenericThermostat(ClimateDevice): if not self._active or not self._enabled: return - if self.min_cycle_duration: - if self._is_device_active: - current_state = STATE_ON - else: - current_state = STATE_OFF - long_enough = condition.state( - self.hass, self.heater_entity_id, current_state, - self.min_cycle_duration) - if not long_enough: - return + if not force and time is None: + # If the `force` argument is True, we + # ignore `min_cycle_duration`. + # If the `time` argument is not none, we were invoked for + # keep-alive purposes, and `min_cycle_duration` is irrelevant. + if self.min_cycle_duration: + if self._is_device_active: + current_state = STATE_ON + else: + current_state = STATE_OFF + long_enough = condition.state( + self.hass, self.heater_entity_id, current_state, + self.min_cycle_duration) + if not long_enough: + return too_cold = \ self._target_temp - self._cur_temp >= self._cold_tolerance @@ -385,7 +390,7 @@ class GenericThermostat(ClimateDevice): self._is_away = True self._saved_target_temp = self._target_temp self._target_temp = self._away_temp - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() async def async_turn_away_mode_off(self): @@ -394,5 +399,5 @@ class GenericThermostat(ClimateDevice): return self._is_away = False self._target_temp = self._saved_target_temp - await self._async_control_heating() + await self._async_control_heating(force=True) await self.async_update_ha_state() diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index f87a5371773..6bdbc58e011 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -623,6 +623,38 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] + def test_mode_change_ac_trigger_off_not_long_enough(self): + """Test if mode change turns ac off despite minimum cycle.""" + self._setup_switch(True) + common.set_temperature(self.hass, 30) + self.hass.block_till_done() + self._setup_sensor(25) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_OFF) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + + def test_mode_change_ac_trigger_on_not_long_enough(self): + """Test if mode change turns ac on despite minimum cycle.""" + self._setup_switch(False) + common.set_temperature(self.hass, 25) + self.hass.block_till_done() + self._setup_sensor(30) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_HEAT) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_ON, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + def _setup_sensor(self, temp): """Set up the test sensor.""" self.hass.states.set(ENT_SENSOR, temp) @@ -714,6 +746,38 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): assert SERVICE_TURN_OFF == call.service assert ENT_SWITCH == call.data['entity_id'] + def test_mode_change_heater_trigger_off_not_long_enough(self): + """Test if mode change turns heater off despite minimum cycle.""" + self._setup_switch(True) + common.set_temperature(self.hass, 25) + self.hass.block_till_done() + self._setup_sensor(30) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_OFF) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_OFF, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + + def test_mode_change_heater_trigger_on_not_long_enough(self): + """Test if mode change turns heater on despite minimum cycle.""" + self._setup_switch(False) + common.set_temperature(self.hass, 30) + self.hass.block_till_done() + self._setup_sensor(25) + self.hass.block_till_done() + self.assertEqual(0, len(self.calls)) + common.set_operation_mode(self.hass, climate.STATE_HEAT) + self.hass.block_till_done() + self.assertEqual(1, len(self.calls)) + call = self.calls[0] + self.assertEqual('homeassistant', call.domain) + self.assertEqual(SERVICE_TURN_ON, call.service) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) + def _setup_sensor(self, temp): """Set up the test sensor.""" self.hass.states.set(ENT_SENSOR, temp) @@ -748,6 +812,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): 'target_temp': 25, 'target_sensor': ENT_SENSOR, 'ac_mode': True, + 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) }}) @@ -838,6 +903,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): 'target_temp': 25, 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, + 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) }}) From 9b47af68ae2623b282769950ace789a3428319b9 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Thu, 1 Nov 2018 16:26:53 -0400 Subject: [PATCH 180/230] Add surround programs to zone 2+ (#17445) * Add surround programs to zone 2+ Add surround programs and surround program to any zone that supports it. * Update yamaha.py removed double fetching. --- homeassistant/components/media_player/yamaha.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 101dfc2bc53..0bb34aee7e1 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -185,9 +185,10 @@ class YamahaDevice(MediaPlayerDevice): self._playback_support = self.receiver.get_playback_support() self._is_playback_supported = self.receiver.is_playback_supported( self._current_source) - if self._zone == "Main_Zone": + surround_programs = self.receiver.surround_programs() + if surround_programs: self._sound_mode = self.receiver.surround_program - self._sound_mode_list = self.receiver.surround_programs() + self._sound_mode_list = surround_programs else: self._sound_mode = None self._sound_mode_list = None From 82edea60774fc1caa2d107d105ea95e65f15e4cf Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 1 Nov 2018 22:59:42 +0100 Subject: [PATCH 181/230] Removed assumptions about provided upnp data (#17604) * Removed assumptions about incomplete UPnP devices * Removed assumptions about incomplete UPnP devices --- homeassistant/components/upnp/config_flow.py | 23 ++++++++++++++++---- homeassistant/components/upnp/strings.json | 1 + tests/components/upnp/test_config_flow.py | 21 ++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index c7cd55f0477..1a6526638ba 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,4 +1,5 @@ """Config flow for UPNP.""" +import logging from collections import OrderedDict import voluptuous as vol @@ -14,6 +15,9 @@ from .const import ( from .const import DOMAIN +_LOGGER = logging.getLogger(__name__) + + async def async_ensure_domain_data(hass): """Ensure hass.data is filled properly.""" hass.data[DOMAIN] = hass.data.get(DOMAIN, {}) @@ -70,14 +74,25 @@ class UpnpFlowHandler(data_entry_flow.FlowHandler): """ await async_ensure_domain_data(self.hass) + if not discovery_info.get('udn') or not discovery_info.get('host'): + # Silently ignore incomplete/broken devices to prevent constant + # errors/warnings + _LOGGER.debug('UPnP device is missing the udn. Provided info: %r', + discovery_info) + return self.async_abort(reason='incomplete_device') + # store discovered device - discovery_info['friendly_name'] = \ - '{} ({})'.format(discovery_info['host'], discovery_info['name']) + discovery_info['friendly_name'] = discovery_info.get('host', '') + + # add name if available + if discovery_info.get('name'): + discovery_info['friendly_name'] += ' ({name})'.format( + **discovery_info) + self._store_discovery_info(discovery_info) # ensure not already discovered/configured - udn = discovery_info['udn'] - if udn in self._configured_upnp_igds: + if discovery_info.get('udn') in self._configured_upnp_igds: return self.async_abort(reason='already_configured') # auto config? diff --git a/homeassistant/components/upnp/strings.json b/homeassistant/components/upnp/strings.json index 9dd4c3f5ad0..40bcb46d386 100644 --- a/homeassistant/components/upnp/strings.json +++ b/homeassistant/components/upnp/strings.json @@ -16,6 +16,7 @@ }, "abort": { "no_devices_discovered": "No UPnP/IGDs discovered", + "incomplete_device": "Ignoring incomplete UPnP device", "already_configured": "UPnP/IGD is already configured", "no_sensors_or_port_mapping": "Enable at least sensors or port mapping" } diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index 3ff1316975f..968c59955e2 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -176,6 +176,27 @@ async def test_config_entry_created(hass): assert result['title'] == 'Test device 1' +async def test_flow_discovery_no_data(hass): + """Test creation of device with auto_config.""" + flow = upnp_config_flow.UpnpFlowHandler() + flow.hass = hass + + # auto_config active + hass.data[upnp.DOMAIN] = { + 'auto_config': { + 'active': True, + 'enable_port_mapping': False, + 'enable_sensors': True, + }, + } + + # discovered device + result = await flow.async_step_discovery({}) + + assert result['type'] == 'abort' + assert result['reason'] == 'incomplete_device' + + async def test_flow_discovery_auto_config_sensors(hass): """Test creation of device with auto_config.""" flow = upnp_config_flow.UpnpFlowHandler() From 6eba7c4ff334094eccf9c91e4d2c1f94ebae302a Mon Sep 17 00:00:00 2001 From: kbickar Date: Fri, 2 Nov 2018 05:13:14 -0400 Subject: [PATCH 182/230] Add binary sensors for sense energy monitor (#17645) * Added error handling for sense API timeouts * Moved imports in function * Moved imports to more appropriate function * Change exception to custom package version * Updated sense_energy library to 0.4.2 * Added binary sensors for individual devices * Whitespace updates * Split into component, sensors, binary sensors * Fixed whitespace * Fixed whitespace * Moved time constant into sensor file * Regenerated requirements * Fixed whitespace * Updated component dependency * Fixed whitespace * Code cleanup * High and low target temps are also supported if target is supported * Revert "High and low target temps are also supported if target is supported" This reverts commit 66b33dc2b81ce81a84553fff327575a0e36d3c8d. * Added all sense components to .coveragerc * Added check authentication exception * binary/sensor platforms loaded in setup * Changed to add all detected devices * Changed to add all sensors on setup * Code cleanup * Whitespace * Whitespace * Added sense as requirement for platform * pylint fixes * Whitespace * Switched requirement to dependency * Made non-class function * Whitespace * Removed unneeded checks * Increased API delay to 60 seconds * Added guard clause for discovery_info * Tidy code * Whitespace --- .coveragerc | 4 +- .../components/binary_sensor/sense.py | 116 ++++++++++++++++++ homeassistant/components/sense.py | 53 ++++++++ homeassistant/components/sensor/sense.py | 60 +++------ requirements_all.txt | 4 +- 5 files changed, 194 insertions(+), 43 deletions(-) create mode 100644 homeassistant/components/binary_sensor/sense.py create mode 100644 homeassistant/components/sense.py diff --git a/.coveragerc b/.coveragerc index e63ac6daec9..54927f74c45 100644 --- a/.coveragerc +++ b/.coveragerc @@ -289,6 +289,9 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + homeassistant/components/sense.py + homeassistant/components/*/sense.py + homeassistant/components/simplisafe/__init__.py homeassistant/components/*/simplisafe.py @@ -760,7 +763,6 @@ omit = homeassistant/components/sensor/ripple.py homeassistant/components/sensor/rtorrent.py homeassistant/components/sensor/scrape.py - homeassistant/components/sensor/sense.py homeassistant/components/sensor/sensehat.py homeassistant/components/sensor/serial_pm.py homeassistant/components/sensor/serial.py diff --git a/homeassistant/components/binary_sensor/sense.py b/homeassistant/components/binary_sensor/sense.py new file mode 100644 index 00000000000..8c5ddda0383 --- /dev/null +++ b/homeassistant/components/binary_sensor/sense.py @@ -0,0 +1,116 @@ +""" +Support for monitoring a Sense energy sensor device. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.sense/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.sense import SENSE_DATA + +DEPENDENCIES = ['sense'] + +_LOGGER = logging.getLogger(__name__) + +BIN_SENSOR_CLASS = 'power' +MDI_ICONS = {'ac': 'air-conditioner', + 'aquarium': 'fish', + 'car': 'car-electric', + 'computer': 'desktop-classic', + 'cup': 'coffee', + 'dehumidifier': 'water-off', + 'dishes': 'dishwasher', + 'drill': 'toolbox', + 'fan': 'fan', + 'freezer': 'fridge-top', + 'fridge': 'fridge-bottom', + 'game': 'gamepad-variant', + 'garage': 'garage', + 'grill': 'stove', + 'heat': 'fire', + 'heater': 'radiatior', + 'humidifier': 'water', + 'kettle': 'kettle', + 'leafblower': 'leaf', + 'lightbulb': 'lightbulb', + 'media_console': 'set-top-box', + 'modem': 'router-wireless', + 'outlet': 'power-socket-us', + 'papershredder': 'shredder', + 'printer': 'printer', + 'pump': 'water-pump', + 'settings': 'settings', + 'skillet': 'pot', + 'smartcamera': 'webcam', + 'socket': 'power-plug', + 'sound': 'speaker', + 'stove': 'stove', + 'trash': 'trash-can', + 'tv': 'television', + 'vacuum': 'robot-vacuum', + 'washer': 'washing-machine'} + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Sense sensor.""" + if discovery_info is None: + return + + data = hass.data[SENSE_DATA] + + sense_devices = data.get_discovered_device_data() + devices = [SenseDevice(data, device) for device in sense_devices] + add_entities(devices) + + +def sense_to_mdi(sense_icon): + """Convert sense icon to mdi icon.""" + return 'mdi:' + MDI_ICONS.get(sense_icon, 'power-plug') + + +class SenseDevice(BinarySensorDevice): + """Implementation of a Sense energy device binary sensor.""" + + def __init__(self, data, device): + """Initialize the sensor.""" + self._name = device['name'] + self._id = device['id'] + self._icon = sense_to_mdi(device['icon']) + self._data = data + self._state = False + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._state + + @property + def name(self): + """Return the name of the binary sensor.""" + return self._name + + @property + def unique_id(self): + """Return the id of the binary sensor.""" + return self._id + + @property + def icon(self): + """Return the icon of the binary sensor.""" + return self._icon + + @property + def device_class(self): + """Return the device class of the binary sensor.""" + return BIN_SENSOR_CLASS + + def update(self): + """Retrieve latest state.""" + from sense_energy.sense_api import SenseAPITimeoutException + try: + self._data.get_realtime() + except SenseAPITimeoutException: + _LOGGER.error("Timeout retrieving data") + return + self._state = self._name in self._data.active_devices diff --git a/homeassistant/components/sense.py b/homeassistant/components/sense.py new file mode 100644 index 00000000000..3792e10e761 --- /dev/null +++ b/homeassistant/components/sense.py @@ -0,0 +1,53 @@ +""" +Support for monitoring a Sense energy sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sense/ +""" +import logging + +import voluptuous as vol + +from homeassistant.helpers.discovery import load_platform +from homeassistant.const import (CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['sense_energy==0.5.1'] + +_LOGGER = logging.getLogger(__name__) + +SENSE_DATA = 'sense_data' + +DOMAIN = 'sense' + +ACTIVE_UPDATE_RATE = 60 +DEFAULT_TIMEOUT = 5 + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, DEFAULT_TIMEOUT): cv.positive_int, + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the Sense sensor.""" + from sense_energy import Senseable, SenseAuthenticationException + + username = config[DOMAIN][CONF_EMAIL] + password = config[DOMAIN][CONF_PASSWORD] + + timeout = config[DOMAIN][CONF_TIMEOUT] + try: + hass.data[SENSE_DATA] = Senseable(api_timeout=timeout, + wss_timeout=timeout) + hass.data[SENSE_DATA].authenticate(username, password) + hass.data[SENSE_DATA].rate_limit = ACTIVE_UPDATE_RATE + except SenseAuthenticationException: + _LOGGER.error("Could not authenticate with sense server") + return False + load_platform(hass, 'sensor', DOMAIN, {}, config) + load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + return True diff --git a/homeassistant/components/sensor/sense.py b/homeassistant/components/sensor/sense.py index dd9d66f58f1..b494257beb7 100644 --- a/homeassistant/components/sensor/sense.py +++ b/homeassistant/components/sensor/sense.py @@ -5,24 +5,20 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.sense/ """ import logging + from datetime import timedelta -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_EMAIL, CONF_PASSWORD, - CONF_MONITORED_CONDITIONS) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -import homeassistant.helpers.config_validation as cv +from homeassistant.components.sense import SENSE_DATA -REQUIREMENTS = ['sense_energy==0.4.2'] +DEPENDENCIES = ['sense'] _LOGGER = logging.getLogger(__name__) -ACTIVE_NAME = "Energy" -PRODUCTION_NAME = "Production" -CONSUMPTION_NAME = "Usage" +ACTIVE_NAME = 'Energy' +PRODUCTION_NAME = 'Production' +CONSUMPTION_NAME = 'Usage' ACTIVE_TYPE = 'active' @@ -46,55 +42,39 @@ SENSOR_TYPES = {'active': SensorConfig(ACTIVE_NAME, ACTIVE_TYPE), # Production/consumption variants SENSOR_VARIANTS = [PRODUCTION_NAME.lower(), CONSUMPTION_NAME.lower()] -# Valid sensors for configuration -VALID_SENSORS = ['%s_%s' % (typ, var) - for typ in SENSOR_TYPES - for var in SENSOR_VARIANTS] - ICON = 'mdi:flash' MIN_TIME_BETWEEN_DAILY_UPDATES = timedelta(seconds=300) -MIN_TIME_BETWEEN_ACTIVE_UPDATES = timedelta(seconds=60) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_EMAIL): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_MONITORED_CONDITIONS): - vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(VALID_SENSORS)]), -}) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Sense sensor.""" - from sense_energy import Senseable + if discovery_info is None: + return - username = config.get(CONF_EMAIL) - password = config.get(CONF_PASSWORD) - - data = Senseable(username, password) + data = hass.data[SENSE_DATA] @Throttle(MIN_TIME_BETWEEN_DAILY_UPDATES) def update_trends(): """Update the daily power usage.""" data.update_trend_data() - @Throttle(MIN_TIME_BETWEEN_ACTIVE_UPDATES) def update_active(): """Update the active power usage.""" data.get_realtime() devices = [] - for sensor in config.get(CONF_MONITORED_CONDITIONS): - config_name, prod = sensor.rsplit('_', 1) - name = SENSOR_TYPES[config_name].name - sensor_type = SENSOR_TYPES[config_name].sensor_type - is_production = prod == PRODUCTION_NAME.lower() - if sensor_type == ACTIVE_TYPE: - update_call = update_active - else: - update_call = update_trends - devices.append(Sense(data, name, sensor_type, - is_production, update_call)) + for typ in SENSOR_TYPES.values(): + for var in SENSOR_VARIANTS: + name = typ.name + sensor_type = typ.sensor_type + is_production = var == PRODUCTION_NAME.lower() + if sensor_type == ACTIVE_TYPE: + update_call = update_active + else: + update_call = update_trends + devices.append(Sense(data, name, sensor_type, + is_production, update_call)) add_entities(devices) diff --git a/requirements_all.txt b/requirements_all.txt index 9f88916d245..2b080a5751d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1348,8 +1348,8 @@ sendgrid==5.6.0 # homeassistant.components.sensor.sensehat sense-hat==2.2.0 -# homeassistant.components.sensor.sense -sense_energy==0.4.2 +# homeassistant.components.sense +sense_energy==0.5.1 # homeassistant.components.media_player.aquostv sharp_aquos_rc==0.3.2 From a4c0c340282ce623d284b9ded52876e3da074ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 2 Nov 2018 11:50:07 +0200 Subject: [PATCH 183/230] Use ssdp udn uuid as Samsung TV unique id (#18022) --- homeassistant/components/media_player/samsungtv.py | 14 ++++++++++++-- tests/components/media_player/test_samsungtv.py | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py index 45d158a8653..1d40683e51e 100644 --- a/homeassistant/components/media_player/samsungtv.py +++ b/homeassistant/components/media_player/samsungtv.py @@ -51,6 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): known_devices = set() hass.data[KNOWN_DEVICES_KEY] = known_devices + uuid = None # Is this a manual configuration? if config.get(CONF_HOST) is not None: host = config.get(CONF_HOST) @@ -66,6 +67,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): port = DEFAULT_PORT timeout = DEFAULT_TIMEOUT mac = None + udn = discovery_info.get('udn') + if udn and udn.startswith('uuid:'): + uuid = udn[len('uuid:'):] else: _LOGGER.warning("Cannot determine device") return @@ -75,7 +79,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ip_addr = socket.gethostbyname(host) if ip_addr not in known_devices: known_devices.add(ip_addr) - add_entities([SamsungTVDevice(host, port, name, timeout, mac)]) + add_entities([SamsungTVDevice(host, port, name, timeout, mac, uuid)]) _LOGGER.info("Samsung TV %s:%d added as '%s'", host, port, name) else: _LOGGER.info("Ignoring duplicate Samsung TV %s:%d", host, port) @@ -84,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class SamsungTVDevice(MediaPlayerDevice): """Representation of a Samsung TV.""" - def __init__(self, host, port, name, timeout, mac): + def __init__(self, host, port, name, timeout, mac, uuid): """Initialize the Samsung device.""" from samsungctl import exceptions from samsungctl import Remote @@ -94,6 +98,7 @@ class SamsungTVDevice(MediaPlayerDevice): self._remote_class = Remote self._name = name self._mac = mac + self._uuid = uuid self._wol = wakeonlan # Assume that the TV is not muted self._muted = False @@ -166,6 +171,11 @@ class SamsungTVDevice(MediaPlayerDevice): return self._end_of_power_off is not None and \ self._end_of_power_off > dt_util.utcnow() + @property + def unique_id(self) -> str: + """Return the unique ID of the device.""" + return self._uuid + @property def name(self): """Return the name of the device.""" diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/media_player/test_samsungtv.py index 31ad449254b..4049ba66a3c 100644 --- a/tests/components/media_player/test_samsungtv.py +++ b/tests/components/media_player/test_samsungtv.py @@ -23,7 +23,8 @@ WORKING_CONFIG = { CONF_NAME: 'fake', CONF_PORT: 8001, CONF_TIMEOUT: 10, - CONF_MAC: 'fake' + CONF_MAC: 'fake', + 'uuid': None, } DISCOVERY_INFO = { From cb7ae5cdf28e235672df5d028b10cff5a5a01b67 Mon Sep 17 00:00:00 2001 From: Neil Crosby Date: Fri, 2 Nov 2018 09:50:43 +0000 Subject: [PATCH 184/230] Allow MS face detection to handle updating entities when no face is detected (#17593) * Allow Microsoft face detection to handle updating entities when no face is detected * Remove microsoft_face_detect_no_face_detected.json and hard code in simple empty list into the tests --- .../image_processing/microsoft_face_detect.py | 2 +- .../microsoft_face_identify.py | 14 ++++++------- .../test_microsoft_face_detect.py | 20 ++++++++++++++++++ .../test_microsoft_face_identify.py | 21 ++++++++++++++++++- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py index 69bd8a8f931..ae6b9c260cd 100644 --- a/homeassistant/components/image_processing/microsoft_face_detect.py +++ b/homeassistant/components/image_processing/microsoft_face_detect.py @@ -102,7 +102,7 @@ class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity): return if not face_data: - return + face_data = [] faces = [] for face in face_data: diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py index 0a5b7725260..7a427d9b046 100644 --- a/homeassistant/components/image_processing/microsoft_face_identify.py +++ b/homeassistant/components/image_processing/microsoft_face_identify.py @@ -83,18 +83,16 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): This method is a coroutine. """ - detect = None + detect = [] try: face_data = await self._api.call_api( 'post', 'detect', image, binary=True) - if not face_data: - return - - face_ids = [data['faceId'] for data in face_data] - detect = await self._api.call_api( - 'post', 'identify', - {'faceIds': face_ids, 'personGroupId': self._face_group}) + if face_data: + face_ids = [data['faceId'] for data in face_data] + detect = await self._api.call_api( + 'post', 'identify', + {'faceIds': face_ids, 'personGroupId': self._face_group}) except HomeAssistantError as err: _LOGGER.error("Can't process image on Microsoft face: %s", err) diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/image_processing/test_microsoft_face_detect.py index c7528c346ee..9e65386a3c6 100644 --- a/tests/components/image_processing/test_microsoft_face_detect.py +++ b/tests/components/image_processing/test_microsoft_face_detect.py @@ -160,3 +160,23 @@ class TestMicrosoftFaceDetect: assert face_events[0].data['gender'] == 'male' assert face_events[0].data['entity_id'] == \ 'image_processing.test_local' + + # Test that later, if a request is made that results in no face + # being detected, that this is reflected in the state object + aioclient_mock.clear_requests() + aioclient_mock.post( + self.endpoint_url.format("detect"), + text="[]", + params={'returnFaceAttributes': "age,gender"} + ) + + common.scan(self.hass, entity_id='image_processing.test_local') + self.hass.block_till_done() + + state = self.hass.states.get('image_processing.test_local') + + # No more face events were fired + assert len(face_events) == 1 + # Total faces and actual qualified number of faces reset to zero + assert state.attributes.get('total_faces') == 0 + assert state.state == '0' diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/image_processing/test_microsoft_face_identify.py index 892326e5bff..c24e758da97 100644 --- a/tests/components/image_processing/test_microsoft_face_identify.py +++ b/tests/components/image_processing/test_microsoft_face_identify.py @@ -2,7 +2,7 @@ from unittest.mock import patch, PropertyMock from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_PICTURE +from homeassistant.const import ATTR_ENTITY_PICTURE, STATE_UNKNOWN from homeassistant.setup import setup_component import homeassistant.components.image_processing as ip import homeassistant.components.microsoft_face as mf @@ -164,3 +164,22 @@ class TestMicrosoftFaceIdentify: assert face_events[0].data['confidence'] == float(92) assert face_events[0].data['entity_id'] == \ 'image_processing.test_local' + + # Test that later, if a request is made that results in no face + # being detected, that this is reflected in the state object + aioclient_mock.clear_requests() + aioclient_mock.post( + self.endpoint_url.format("detect"), + text="[]" + ) + + common.scan(self.hass, entity_id='image_processing.test_local') + self.hass.block_till_done() + + state = self.hass.states.get('image_processing.test_local') + + # No more face events were fired + assert len(face_events) == 1 + # Total faces and actual qualified number of faces reset to zero + assert state.attributes.get('total_faces') == 0 + assert state.state == STATE_UNKNOWN From 86136945443c5c3c8f8eea65a510c9603d5b2f58 Mon Sep 17 00:00:00 2001 From: Leothlon Date: Fri, 2 Nov 2018 11:07:36 +0100 Subject: [PATCH 185/230] Added service select_video_output and video_out attribute (#18081) * Added service select_video_output and video_out attribute * Fixed white lines and long lines * Made line shorter * Added period to comment according to flake8 rules * Imported domain const * Prefixed service with platform name * changed "video output" to "hdmi output" to better reflect eiscp cammand * video_output to hdmi_output rename --- .../components/media_player/onkyo.py | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py index 0ba098d85f5..367ad2aa972 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -14,8 +14,9 @@ import voluptuous as vol from homeassistant.components.media_player import ( PLATFORM_SCHEMA, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, MediaPlayerDevice) -from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON + SUPPORT_VOLUME_STEP, MediaPlayerDevice, DOMAIN) +from homeassistant.const import ( + CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, ATTR_ENTITY_ID) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['onkyo-eiscp==1.2.4'] @@ -55,6 +56,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ TIMEOUT_MESSAGE = 'Timeout waiting for response.' +ATTR_HDMI_OUTPUT = 'hdmi_output' +ACCEPTED_VALUES = ['no', 'analog', 'yes', 'out', + 'out-sub', 'sub', 'hdbaset', 'both', 'up'] +ONKYO_SELECT_OUTPUT_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_HDMI_OUTPUT): vol.In(ACCEPTED_VALUES) +}) + +SERVICE_SELECT_HDMI_OUTPUT = 'onkyo_select_hdmi_output' + def determine_zones(receiver): """Determine what zones are available for the receiver.""" @@ -90,6 +101,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): host = config.get(CONF_HOST) hosts = [] + def service_handle(service): + """Handle for services.""" + entity_ids = service.data.get(ATTR_ENTITY_ID) + devices = [d for d in hosts if d.entity_id in entity_ids] + + for device in devices: + if service.service == SERVICE_SELECT_HDMI_OUTPUT: + device.select_output(service.data.get(ATTR_HDMI_OUTPUT)) + + hass.services.register( + DOMAIN, SERVICE_SELECT_HDMI_OUTPUT, service_handle, + schema=ONKYO_SELECT_OUTPUT_SCHEMA) + if CONF_HOST in config and host not in KNOWN_HOSTS: try: receiver = eiscp.eISCP(host) @@ -144,6 +168,7 @@ class OnkyoDevice(MediaPlayerDevice): self._source_list = list(sources.values()) self._source_mapping = sources self._reverse_mapping = {value: key for key, value in sources.items()} + self._attributes = {} def command(self, command): """Run an eiscp command and catch connection errors.""" @@ -174,6 +199,7 @@ class OnkyoDevice(MediaPlayerDevice): volume_raw = self.command('volume query') mute_raw = self.command('audio-muting query') current_source_raw = self.command('input-selector query') + hdmi_out_raw = self.command('hdmi-output-selector query') if not (volume_raw and mute_raw and current_source_raw): return @@ -194,6 +220,7 @@ class OnkyoDevice(MediaPlayerDevice): [i for i in current_source_tuples[1]]) self._muted = bool(mute_raw[1] == 'on') self._volume = volume_raw[1] / self._max_volume + self._attributes["video_out"] = ','.join(hdmi_out_raw[1]) @property def name(self): @@ -230,6 +257,11 @@ class OnkyoDevice(MediaPlayerDevice): """List of available input sources.""" return self._source_list + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return self._attributes + def turn_off(self): """Turn the media player off.""" self.command('system-power standby') @@ -275,6 +307,10 @@ class OnkyoDevice(MediaPlayerDevice): source in DEFAULT_PLAYABLE_SOURCES): self.command('preset {}'.format(media_id)) + def select_output(self, output): + """Set hdmi-out.""" + self.command('hdmi-output-selector={}'.format(output)) + class OnkyoDeviceZone(OnkyoDevice): """Representation of an Onkyo device's extra zone.""" @@ -346,7 +382,7 @@ class OnkyoDeviceZone(OnkyoDevice): def set_volume_level(self, volume): """Set volume level, input is range 0..1. Onkyo ranges from 1-80.""" - self.command('zone{}.volume={}'.format(self._zone, int(volume*80))) + self.command('zone{}.volume={}'.format(self._zone, int(volume * 80))) def volume_up(self): """Increase volume by 1 step.""" From 93689d68f75d392d9cecb554956facc3267a56b8 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 2 Nov 2018 12:32:41 +0100 Subject: [PATCH 186/230] Fix time zone for flux switch (#18102) --- homeassistant/components/switch/flux.py | 10 ++-- tests/components/switch/test_flux.py | 64 ++++++++++++------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index 01c85dce6cf..ea7aded3e16 100644 --- a/homeassistant/components/switch/flux.py +++ b/homeassistant/components/switch/flux.py @@ -25,7 +25,7 @@ from homeassistant.util import slugify from homeassistant.util.color import ( color_temperature_to_rgb, color_RGB_to_xy_brightness, color_temperature_kelvin_to_mired) -from homeassistant.util.dt import now as dt_now +from homeassistant.util.dt import utcnow as dt_utcnow, as_local _LOGGER = logging.getLogger(__name__) @@ -195,10 +195,12 @@ class FluxSwitch(SwitchDevice): self.schedule_update_ha_state() - def flux_update(self, now=None): + def flux_update(self, utcnow=None): """Update all the lights using flux.""" - if now is None: - now = dt_now() + if utcnow is None: + utcnow = dt_utcnow() + + now = as_local(utcnow) sunset = get_astral_event_date(self.hass, SUN_EVENT_SUNSET, now.date()) start_time = self.find_start_time(now) diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index 56e9a4434c0..84db2fd0df0 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -87,7 +87,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=10, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=10, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -96,7 +96,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.util.dt.now', return_value=test_time): + with patch('homeassistant.util.dt.utcnow', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): assert setup_component(self.hass, switch.DOMAIN, { @@ -127,7 +127,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=2, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -136,7 +136,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.util.dt.now', return_value=test_time): + with patch('homeassistant.util.dt.utcnow', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): assert setup_component(self.hass, switch.DOMAIN, { @@ -172,7 +172,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=8, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -181,7 +181,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -218,7 +218,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=17, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -227,7 +227,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -265,7 +265,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=23, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=23, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -274,7 +274,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.util.dt.now', return_value=test_time): + with patch('homeassistant.util.dt.utcnow', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): assert setup_component(self.hass, switch.DOMAIN, { @@ -310,7 +310,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=17, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -319,7 +319,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -360,7 +360,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=2, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -369,7 +369,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -411,7 +411,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=8, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -420,7 +420,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -461,7 +461,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=23, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=23, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -470,7 +470,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.util.dt.now', return_value=test_time): + with patch('homeassistant.util.dt.utcnow', return_value=test_time): with patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): assert setup_component(self.hass, switch.DOMAIN, { @@ -510,7 +510,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=00, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=00, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -519,7 +519,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -560,7 +560,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=2, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=2, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -569,7 +569,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -607,7 +607,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=17, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -616,7 +616,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -656,7 +656,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=17, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=17, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -665,7 +665,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -716,7 +716,7 @@ class TestSwitchFlux(unittest.TestCase): assert state.attributes.get('xy_color') is None assert state.attributes.get('brightness') is None - test_time = dt_util.now().replace(hour=12, minute=0, second=0) + test_time = dt_util.utcnow().replace(hour=12, minute=0, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -727,7 +727,7 @@ class TestSwitchFlux(unittest.TestCase): print('sunset {}'.format(sunset_time)) return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -770,7 +770,7 @@ class TestSwitchFlux(unittest.TestCase): assert STATE_ON == state.state assert state.attributes.get('color_temp') is None - test_time = dt_util.now().replace(hour=8, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -779,7 +779,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): @@ -814,7 +814,7 @@ class TestSwitchFlux(unittest.TestCase): assert STATE_ON == state.state assert state.attributes.get('color_temp') is None - test_time = dt_util.now().replace(hour=8, minute=30, second=0) + test_time = dt_util.utcnow().replace(hour=8, minute=30, second=0) sunset_time = test_time.replace(hour=17, minute=0, second=0) sunrise_time = test_time.replace(hour=5, minute=0, second=0) @@ -823,7 +823,7 @@ class TestSwitchFlux(unittest.TestCase): return sunrise_time return sunset_time - with patch('homeassistant.components.switch.flux.dt_now', + with patch('homeassistant.components.switch.flux.dt_utcnow', return_value=test_time), \ patch('homeassistant.helpers.sun.get_astral_event_date', side_effect=event_date): From 58c7ee649d3a22c44ac98012b1c0ffa03557cb3d Mon Sep 17 00:00:00 2001 From: Oleksii Serdiuk Date: Fri, 2 Nov 2018 13:44:53 +0100 Subject: [PATCH 187/230] Darksky: Round all temperatures to 1 decimal place (#18119) Some temperatures were not rounded to 1 decimal place: `temperature_low`, `apparent_temperature_low`, `temperature_high`, `apparent_temperature_high`. --- homeassistant/components/sensor/darksky.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index a43ab888d04..9a3ba45dfa1 100644 --- a/homeassistant/components/sensor/darksky.py +++ b/homeassistant/components/sensor/darksky.py @@ -354,10 +354,12 @@ class DarkSkySensor(Entity): if self.type in ['precip_probability', 'cloud_cover', 'humidity']: return round(state * 100, 1) if self.type in ['dew_point', 'temperature', 'apparent_temperature', - 'temperature_min', 'temperature_max', - 'apparent_temperature_min', - 'apparent_temperature_max', 'precip_accumulation', - 'pressure', 'ozone', 'uvIndex']: + 'temperature_low', 'apparent_temperature_low', + 'temperature_min', 'apparent_temperature_min', + 'temperature_high', 'apparent_temperature_high', + 'temperature_max', 'apparent_temperature_max' + 'precip_accumulation', 'pressure', 'ozone', + 'uvIndex']: return round(state, 1) return state From 0a301f7dcb838bb68d92ec674e2337b20d680545 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 2 Nov 2018 09:03:05 -0400 Subject: [PATCH 188/230] Convert nsw rural fire tests to async (#18112) --- .../test_nsw_rural_fire_service_feed.py | 129 ++++++++---------- 1 file changed, 58 insertions(+), 71 deletions(-) diff --git a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py b/tests/components/geo_location/test_nsw_rural_fire_service_feed.py index 665f6017907..75397d27383 100644 --- a/tests/components/geo_location/test_nsw_rural_fire_service_feed.py +++ b/tests/components/geo_location/test_nsw_rural_fire_service_feed.py @@ -1,8 +1,6 @@ """The tests for the geojson platform.""" import datetime -import unittest -from unittest import mock -from unittest.mock import patch, MagicMock +from asynctest.mock import patch, MagicMock from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -13,9 +11,8 @@ from homeassistant.components.geo_location.nsw_rural_fire_service_feed import \ from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_START, \ CONF_RADIUS, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_FRIENDLY_NAME, \ ATTR_UNIT_OF_MEASUREMENT, ATTR_ATTRIBUTION -from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, assert_setup_component, \ - fire_time_changed +from homeassistant.setup import async_setup_component +from tests.common import assert_setup_component, async_fire_time_changed import homeassistant.util.dt as dt_util URL = 'http://geo.json.local/geo_json_events.json' @@ -30,61 +27,50 @@ CONFIG = { } -class TestGeoJsonPlatform(unittest.TestCase): - """Test the geojson platform.""" +def _generate_mock_feed_entry(external_id, title, distance_to_home, + coordinates, category=None, location=None, + attribution=None, publication_date=None, + council_area=None, status=None, + entry_type=None, fire=True, size=None, + responsible_agency=None): + """Construct a mock feed entry for testing purposes.""" + feed_entry = MagicMock() + feed_entry.external_id = external_id + feed_entry.title = title + feed_entry.distance_to_home = distance_to_home + feed_entry.coordinates = coordinates + feed_entry.category = category + feed_entry.location = location + feed_entry.attribution = attribution + feed_entry.publication_date = publication_date + feed_entry.council_area = council_area + feed_entry.status = status + feed_entry.type = entry_type + feed_entry.fire = fire + feed_entry.size = size + feed_entry.responsible_agency = responsible_agency + return feed_entry - def setUp(self): - """Initialize values for this testcase class.""" - self.hass = get_test_home_assistant() - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - @staticmethod - def _generate_mock_feed_entry(external_id, title, distance_to_home, - coordinates, category=None, location=None, - attribution=None, publication_date=None, - council_area=None, status=None, - entry_type=None, fire=True, size=None, - responsible_agency=None): - """Construct a mock feed entry for testing purposes.""" - feed_entry = MagicMock() - feed_entry.external_id = external_id - feed_entry.title = title - feed_entry.distance_to_home = distance_to_home - feed_entry.coordinates = coordinates - feed_entry.category = category - feed_entry.location = location - feed_entry.attribution = attribution - feed_entry.publication_date = publication_date - feed_entry.council_area = council_area - feed_entry.status = status - feed_entry.type = entry_type - feed_entry.fire = fire - feed_entry.size = size - feed_entry.responsible_agency = responsible_agency - return feed_entry - - @mock.patch('geojson_client.nsw_rural_fire_service_feed.' - 'NswRuralFireServiceFeed') - def test_setup(self, mock_feed): - """Test the general setup of the platform.""" - # Set up some mock feed entries for this test. - mock_entry_1 = self._generate_mock_feed_entry( +async def test_setup(hass): + """Test the general setup of the platform.""" + # Set up some mock feed entries for this test. + with patch('geojson_client.nsw_rural_fire_service_feed.' + 'NswRuralFireServiceFeed') as mock_feed: + mock_entry_1 = _generate_mock_feed_entry( '1234', 'Title 1', 15.5, (-31.0, 150.0), category='Category 1', location='Location 1', attribution='Attribution 1', publication_date=datetime.datetime(2018, 9, 22, 8, 0, tzinfo=datetime.timezone.utc), council_area='Council Area 1', status='Status 1', entry_type='Type 1', size='Size 1', responsible_agency='Agency 1') - mock_entry_2 = self._generate_mock_feed_entry('2345', 'Title 2', 20.5, - (-31.1, 150.1), - fire=False) - mock_entry_3 = self._generate_mock_feed_entry('3456', 'Title 3', 25.5, - (-31.2, 150.2)) - mock_entry_4 = self._generate_mock_feed_entry('4567', 'Title 4', 12.5, - (-31.3, 150.3)) + mock_entry_2 = _generate_mock_feed_entry('2345', 'Title 2', 20.5, + (-31.1, 150.1), + fire=False) + mock_entry_3 = _generate_mock_feed_entry('3456', 'Title 3', 25.5, + (-31.2, 150.2)) + mock_entry_4 = _generate_mock_feed_entry('4567', 'Title 4', 12.5, + (-31.3, 150.3)) mock_feed.return_value.update.return_value = 'OK', [mock_entry_1, mock_entry_2, mock_entry_3] @@ -93,16 +79,17 @@ class TestGeoJsonPlatform(unittest.TestCase): # Patching 'utcnow' to gain more control over the timed update. with patch('homeassistant.util.dt.utcnow', return_value=utcnow): with assert_setup_component(1, geo_location.DOMAIN): - assert setup_component(self.hass, geo_location.DOMAIN, CONFIG) + assert await async_setup_component( + hass, geo_location.DOMAIN, CONFIG) # Artificially trigger update. - self.hass.bus.fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. - self.hass.block_till_done() + await hass.async_block_till_done() - all_states = self.hass.states.all() + all_states = hass.states.async_all() assert len(all_states) == 3 - state = self.hass.states.get("geo_location.title_1") + state = hass.states.get("geo_location.title_1") assert state is not None assert state.name == "Title 1" assert state.attributes == { @@ -121,7 +108,7 @@ class TestGeoJsonPlatform(unittest.TestCase): ATTR_SOURCE: 'nsw_rural_fire_service_feed'} assert round(abs(float(state.state)-15.5), 7) == 0 - state = self.hass.states.get("geo_location.title_2") + state = hass.states.get("geo_location.title_2") assert state is not None assert state.name == "Title 2" assert state.attributes == { @@ -132,7 +119,7 @@ class TestGeoJsonPlatform(unittest.TestCase): ATTR_SOURCE: 'nsw_rural_fire_service_feed'} assert round(abs(float(state.state)-20.5), 7) == 0 - state = self.hass.states.get("geo_location.title_3") + state = hass.states.get("geo_location.title_3") assert state is not None assert state.name == "Title 3" assert state.attributes == { @@ -147,28 +134,28 @@ class TestGeoJsonPlatform(unittest.TestCase): # one outdated entry mock_feed.return_value.update.return_value = 'OK', [ mock_entry_1, mock_entry_4, mock_entry_3] - fire_time_changed(self.hass, utcnow + SCAN_INTERVAL) - self.hass.block_till_done() + async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = self.hass.states.all() + all_states = hass.states.async_all() assert len(all_states) == 3 # Simulate an update - empty data, but successful update, # so no changes to entities. mock_feed.return_value.update.return_value = 'OK_NO_DATA', None # mock_restdata.return_value.data = None - fire_time_changed(self.hass, utcnow + - 2 * SCAN_INTERVAL) - self.hass.block_till_done() + async_fire_time_changed(hass, utcnow + + 2 * SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = self.hass.states.all() + all_states = hass.states.async_all() assert len(all_states) == 3 # Simulate an update - empty data, removes all entities mock_feed.return_value.update.return_value = 'ERROR', None - fire_time_changed(self.hass, utcnow + - 2 * SCAN_INTERVAL) - self.hass.block_till_done() + async_fire_time_changed(hass, utcnow + + 2 * SCAN_INTERVAL) + await hass.async_block_till_done() - all_states = self.hass.states.all() + all_states = hass.states.async_all() assert len(all_states) == 0 From 3fe895c18f0ead0dd4af3b4648e88f8f14adcb6e Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Fri, 2 Nov 2018 14:43:17 +0100 Subject: [PATCH 189/230] Adding climate.velbus support (#18100) * Adding climate.velbus support * Fix version * fixed houndci-bot * More fixes * Fix typos and ordering --- homeassistant/components/climate/velbus.py | 81 ++++++++++++++++++++++ homeassistant/components/velbus.py | 5 +- requirements_all.txt | 2 +- 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/climate/velbus.py diff --git a/homeassistant/components/climate/velbus.py b/homeassistant/components/climate/velbus.py new file mode 100644 index 00000000000..ab8542541c8 --- /dev/null +++ b/homeassistant/components/climate/velbus.py @@ -0,0 +1,81 @@ +""" +Support for Velbus thermostat. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/climate.velbus/ +""" +import logging + +from homeassistant.components.climate import ( + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) +from homeassistant.components.velbus import ( + DOMAIN as VELBUS_DOMAIN, VelbusEntity) +from homeassistant.const import ATTR_TEMPERATURE + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['velbus'] + +OPERATION_LIST = ['comfort', 'day', 'night', 'safe'] + +SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the Velbus thermostat platform.""" + if discovery_info is None: + return + + sensors = [] + for sensor in discovery_info: + module = hass.data[VELBUS_DOMAIN].get_module(sensor[0]) + channel = sensor[1] + sensors.append(VelbusClimate(module, channel)) + + async_add_entities(sensors) + + +class VelbusClimate(VelbusEntity, ClimateDevice): + """Representation of a Velbus thermostat.""" + + @property + def supported_features(self): + """Return the list off supported features.""" + return SUPPORT_FLAGS + + @property + def temperature_unit(self): + """Return the unit this state is expressed in.""" + return self._module.get_unit(self._channel) + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._module.get_state(self._channel) + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + return self._module.get_climate_mode() + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return OPERATION_LIST + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._module.get_climate_target() + + def set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + self._module.set_mode(operation_mode) + self.schedule_update_ha_state() + + def set_temperature(self, **kwargs): + """Set new target temperatures.""" + if kwargs.get(ATTR_TEMPERATURE) is not None: + self._module.set_temp(kwargs.get(ATTR_TEMPERATURE)) + self.schedule_update_ha_state() diff --git a/homeassistant/components/velbus.py b/homeassistant/components/velbus.py index 2304054c404..294061527f8 100644 --- a/homeassistant/components/velbus.py +++ b/homeassistant/components/velbus.py @@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP, CONF_PORT from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-velbus==2.0.20'] +REQUIREMENTS = ['python-velbus==2.0.21'] _LOGGER = logging.getLogger(__name__) @@ -47,6 +47,7 @@ async def async_setup(hass, config): modules = controller.get_modules() discovery_info = { 'switch': [], + 'climate': [], 'binary_sensor': [], 'sensor': [] } @@ -60,6 +61,8 @@ async def async_setup(hass, config): )) load_platform(hass, 'switch', DOMAIN, discovery_info['switch'], config) + load_platform(hass, 'climate', DOMAIN, + discovery_info['climate'], config) load_platform(hass, 'binary_sensor', DOMAIN, discovery_info['binary_sensor'], config) load_platform(hass, 'sensor', DOMAIN, diff --git a/requirements_all.txt b/requirements_all.txt index 2b080a5751d..466eb6ead54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1210,7 +1210,7 @@ python-telegram-bot==11.1.0 python-twitch-client==0.6.0 # homeassistant.components.velbus -python-velbus==2.0.20 +python-velbus==2.0.21 # homeassistant.components.media_player.vlc python-vlc==1.1.2 From a39846bad92eaeae60c54b6e3ce15eec791a79b4 Mon Sep 17 00:00:00 2001 From: vacumet <44652865+vacumet@users.noreply.github.com> Date: Fri, 2 Nov 2018 14:44:20 +0100 Subject: [PATCH 190/230] Changed from pifacecommon 4.1.2 to 4.2.2 to make the piface digital i/o boards work on rpi like 3 (#18101) --- homeassistant/components/rpi_pfio.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rpi_pfio.py b/homeassistant/components/rpi_pfio.py index bf8fdccfab0..286be87bce9 100644 --- a/homeassistant/components/rpi_pfio.py +++ b/homeassistant/components/rpi_pfio.py @@ -9,7 +9,7 @@ import logging from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) -REQUIREMENTS = ['pifacecommon==4.1.2', 'pifacedigitalio==3.0.5'] +REQUIREMENTS = ['pifacecommon==4.2.2', 'pifacedigitalio==3.0.5'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 466eb6ead54..b89b496dd50 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -709,7 +709,7 @@ pdunehd==1.3 pexpect==4.6.0 # homeassistant.components.rpi_pfio -pifacecommon==4.1.2 +pifacecommon==4.2.2 # homeassistant.components.rpi_pfio pifacedigitalio==3.0.5 From 97e928df4a47e4af8febfe111f05982f51d67c26 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 2 Nov 2018 08:48:22 -0600 Subject: [PATCH 191/230] Update Pollen.com to use numpy for trend analysis (#18107) * Update Pollen.com to use numpy for trend analysis * Hound * Linting --- homeassistant/components/sensor/pollen.py | 40 +++++++++++------------ requirements_all.txt | 1 + requirements_test_all.txt | 1 + 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index 62fdd5b4955..818404aa3fe 100644 --- a/homeassistant/components/sensor/pollen.py +++ b/homeassistant/components/sensor/pollen.py @@ -18,7 +18,7 @@ from homeassistant.helpers import aiohttp_client from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pypollencom==2.2.2'] +REQUIREMENTS = ['numpy==1.15.3', 'pypollencom==2.2.2'] _LOGGER = logging.getLogger(__name__) ATTR_ALLERGEN_AMOUNT = 'allergen_amount' @@ -97,7 +97,6 @@ RATING_MAPPING = [{ 'maximum': 12 }] -TREND_FLAT = 'Flat' TREND_INCREASING = 'Increasing' TREND_SUBSIDING = 'Subsiding' @@ -140,6 +139,23 @@ def calculate_average_rating(indices): return max(set(ratings), key=ratings.count) +def calculate_trend(indices): + """Calculate the "moving average" of a set of indices.""" + import numpy as np + + def moving_average(data, samples): + """Determine the "moving average" (http://tinyurl.com/yaereb3c).""" + ret = np.cumsum(data, dtype=float) + ret[samples:] = ret[samples:] - ret[:-samples] + return ret[samples - 1:] / samples + + increasing = np.all(np.diff(moving_average(np.array(indices), 4)) > 0) + + if increasing: + return TREND_INCREASING + return TREND_SUBSIDING + + class BaseSensor(Entity): """Define a base Pollen.com sensor.""" @@ -217,19 +233,11 @@ class ForecastSensor(BaseSensor): if i['minimum'] <= average <= i['maximum'] ] - slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index']) - if slope > 0: - trend = TREND_INCREASING - elif slope < 0: - trend = TREND_SUBSIDING - else: - trend = TREND_FLAT - self._attrs.update({ ATTR_CITY: data['City'].title(), ATTR_RATING: rating, ATTR_STATE: data['State'], - ATTR_TREND: trend, + ATTR_TREND: calculate_trend(indices), ATTR_ZIP_CODE: data['ZIP'] }) @@ -256,19 +264,11 @@ class HistoricalSensor(BaseSensor): indices = [p['Index'] for p in data['periods']] average = round(mean(indices), 1) - slope = (data['periods'][-1]['Index'] - data['periods'][-2]['Index']) - if slope > 0: - trend = TREND_INCREASING - elif slope < 0: - trend = TREND_SUBSIDING - else: - trend = TREND_FLAT - self._attrs.update({ ATTR_CITY: data['City'].title(), ATTR_RATING: calculate_average_rating(indices), ATTR_STATE: data['State'], - ATTR_TREND: trend, + ATTR_TREND: calculate_trend(indices), ATTR_ZIP_CODE: data['ZIP'] }) diff --git a/requirements_all.txt b/requirements_all.txt index b89b496dd50..42aa2c08cc8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -669,6 +669,7 @@ nuheat==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv +# homeassistant.components.sensor.pollen numpy==1.15.3 # homeassistant.components.google diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8035918d3f2..6059917892c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -118,6 +118,7 @@ mficlient==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv +# homeassistant.components.sensor.pollen numpy==1.15.3 # homeassistant.components.mqtt From 283407fe6c2312c9556d0c0b13e1c541169074e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 2 Nov 2018 16:52:02 +0100 Subject: [PATCH 192/230] Exposes initial attribute on the entity. (#18123) --- homeassistant/components/input_number.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/input_number.py b/homeassistant/components/input_number.py index 9630e943bf4..f52b9add821 100644 --- a/homeassistant/components/input_number.py +++ b/homeassistant/components/input_number.py @@ -28,6 +28,7 @@ CONF_STEP = 'step' MODE_SLIDER = 'slider' MODE_BOX = 'box' +ATTR_INITIAL = 'initial' ATTR_VALUE = 'value' ATTR_MIN = 'min' ATTR_MAX = 'max' @@ -131,6 +132,7 @@ class InputNumber(Entity): self.entity_id = ENTITY_ID_FORMAT.format(object_id) self._name = name self._current_value = initial + self._initial = initial self._minimum = minimum self._maximum = maximum self._step = step @@ -167,6 +169,7 @@ class InputNumber(Entity): def state_attributes(self): """Return the state attributes.""" return { + ATTR_INITIAL: self._initial, ATTR_MIN: self._minimum, ATTR_MAX: self._maximum, ATTR_STEP: self._step, From dd938d74608d37be886db9b8aea831f231381551 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 2 Nov 2018 16:06:59 -0400 Subject: [PATCH 193/230] Async generic thermostat tests (#18111) --- tests/components/climate/common.py | 118 + .../climate/test_generic_thermostat.py | 2007 ++++++++--------- 2 files changed, 1112 insertions(+), 1013 deletions(-) diff --git a/tests/components/climate/common.py b/tests/components/climate/common.py index 4ac6f553091..d1626e1f235 100644 --- a/tests/components/climate/common.py +++ b/tests/components/climate/common.py @@ -11,9 +11,25 @@ from homeassistant.components.climate import ( SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE) +from homeassistant.core import callback from homeassistant.loader import bind_hass +@callback +@bind_hass +def async_set_away_mode(hass, away_mode, entity_id=None): + """Turn all or specified climate devices away mode on.""" + data = { + ATTR_AWAY_MODE: away_mode + } + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_AWAY_MODE, data)) + + @bind_hass def set_away_mode(hass, away_mode, entity_id=None): """Turn all or specified climate devices away mode on.""" @@ -27,6 +43,21 @@ def set_away_mode(hass, away_mode, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data) +@callback +@bind_hass +def async_set_hold_mode(hass, hold_mode, entity_id=None): + """Set new hold mode.""" + data = { + ATTR_HOLD_MODE: hold_mode + } + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_HOLD_MODE, data)) + + @bind_hass def set_hold_mode(hass, hold_mode, entity_id=None): """Set new hold mode.""" @@ -40,6 +71,21 @@ def set_hold_mode(hass, hold_mode, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data) +@callback +@bind_hass +def async_set_aux_heat(hass, aux_heat, entity_id=None): + """Turn all or specified climate devices auxiliary heater on.""" + data = { + ATTR_AUX_HEAT: aux_heat + } + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_AUX_HEAT, data)) + + @bind_hass def set_aux_heat(hass, aux_heat, entity_id=None): """Turn all or specified climate devices auxiliary heater on.""" @@ -53,6 +99,26 @@ def set_aux_heat(hass, aux_heat, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data) +@callback +@bind_hass +def async_set_temperature(hass, temperature=None, entity_id=None, + target_temp_high=None, target_temp_low=None, + operation_mode=None): + """Set new target temperature.""" + kwargs = { + key: value for key, value in [ + (ATTR_TEMPERATURE, temperature), + (ATTR_TARGET_TEMP_HIGH, target_temp_high), + (ATTR_TARGET_TEMP_LOW, target_temp_low), + (ATTR_ENTITY_ID, entity_id), + (ATTR_OPERATION_MODE, operation_mode) + ] if value is not None + } + _LOGGER.debug("set_temperature start data=%s", kwargs) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)) + + @bind_hass def set_temperature(hass, temperature=None, entity_id=None, target_temp_high=None, target_temp_low=None, @@ -71,6 +137,19 @@ def set_temperature(hass, temperature=None, entity_id=None, hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs) +@callback +@bind_hass +def async_set_humidity(hass, humidity, entity_id=None): + """Set new target humidity.""" + data = {ATTR_HUMIDITY: humidity} + + if entity_id is not None: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_HUMIDITY, data)) + + @bind_hass def set_humidity(hass, humidity, entity_id=None): """Set new target humidity.""" @@ -82,6 +161,19 @@ def set_humidity(hass, humidity, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data) +@callback +@bind_hass +def async_set_fan_mode(hass, fan, entity_id=None): + """Set all or specified climate devices fan mode on.""" + data = {ATTR_FAN_MODE: fan} + + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_FAN_MODE, data)) + + @bind_hass def set_fan_mode(hass, fan, entity_id=None): """Set all or specified climate devices fan mode on.""" @@ -93,6 +185,19 @@ def set_fan_mode(hass, fan, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data) +@callback +@bind_hass +def async_set_operation_mode(hass, operation_mode, entity_id=None): + """Set new target operation mode.""" + data = {ATTR_OPERATION_MODE: operation_mode} + + if entity_id is not None: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)) + + @bind_hass def set_operation_mode(hass, operation_mode, entity_id=None): """Set new target operation mode.""" @@ -104,6 +209,19 @@ def set_operation_mode(hass, operation_mode, entity_id=None): hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data) +@callback +@bind_hass +def async_set_swing_mode(hass, swing_mode, entity_id=None): + """Set new target swing mode.""" + data = {ATTR_SWING_MODE: swing_mode} + + if entity_id is not None: + data[ATTR_ENTITY_ID] = entity_id + + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_SWING_MODE, data)) + + @bind_hass def set_swing_mode(hass, swing_mode, entity_id=None): """Set new target swing mode.""" diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 6bdbc58e011..3d30a21504a 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -1,14 +1,13 @@ """The tests for the generic_thermostat.""" -import asyncio import datetime -import unittest -from unittest import mock +import pytest +from asynctest import mock import pytz import homeassistant.core as ha from homeassistant.core import ( callback, DOMAIN as HASS_DOMAIN, CoreState, State) -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.setup import async_setup_component from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -20,18 +19,18 @@ from homeassistant.const import ( ) from homeassistant import loader from homeassistant.util.unit_system import METRIC_SYSTEM -from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.components import climate, input_boolean, switch from homeassistant.components.climate import STATE_HEAT, STATE_COOL import homeassistant.components as comps -from tests.common import (assert_setup_component, get_test_home_assistant, - mock_restore_cache) +from tests.common import assert_setup_component, mock_restore_cache from tests.components.climate import common ENTITY = 'climate.test' ENT_SENSOR = 'sensor.test' ENT_SWITCH = 'switch.test' +HEAT_ENTITY = 'climate.test_heat' +COOL_ENTITY = 'climate.test_cool' ATTR_AWAY_MODE = 'away_mode' MIN_TEMP = 3.0 MAX_TEMP = 65.0 @@ -40,120 +39,101 @@ COLD_TOLERANCE = 0.5 HOT_TOLERANCE = 0.5 -class TestSetupClimateGenericThermostat(unittest.TestCase): - """Test the Generic thermostat with custom config.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_setup_missing_conf(self): - """Test set up heat_control with missing config values.""" - config = { - 'name': 'test', - 'target_sensor': ENT_SENSOR - } - with assert_setup_component(0): - setup_component(self.hass, 'climate', { - 'climate': config}) - - def test_valid_conf(self): - """Test set up generic_thermostat with valid config values.""" - assert setup_component(self.hass, 'climate', { - 'climate': { - 'platform': 'generic_thermostat', - 'name': 'test', - 'heater': ENT_SWITCH, - 'target_sensor': ENT_SENSOR - }}) +async def test_setup_missing_conf(hass): + """Test set up heat_control with missing config values.""" + config = { + 'name': 'test', + 'target_sensor': ENT_SENSOR + } + with assert_setup_component(0): + await async_setup_component(hass, 'climate', { + 'climate': config}) -class TestGenericThermostatHeaterSwitching(unittest.TestCase): - """Test the Generic thermostat heater switching. - - Different toggle type devices are tested. - """ - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.units = METRIC_SYSTEM - assert run_coroutine_threadsafe( - comps.async_setup(self.hass, {}), self.hass.loop - ).result() - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_heater_input_boolean(self): - """Test heater switching input_boolean.""" - heater_switch = 'input_boolean.test' - assert setup_component(self.hass, input_boolean.DOMAIN, - {'input_boolean': {'test': None}}) - - assert setup_component(self.hass, climate.DOMAIN, {'climate': { +async def test_valid_conf(hass): + """Test set up generic_thermostat with valid config values.""" + assert await async_setup_component(hass, 'climate', { + 'climate': { 'platform': 'generic_thermostat', 'name': 'test', - 'heater': heater_switch, + 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR }}) - assert STATE_OFF == \ - self.hass.states.get(heater_switch).state - self._setup_sensor(18) - self.hass.block_till_done() - common.set_temperature(self.hass, 23) - self.hass.block_till_done() - - assert STATE_ON == \ - self.hass.states.get(heater_switch).state - - def test_heater_switch(self): - """Test heater switching test switch.""" - platform = loader.get_component(self.hass, 'switch.test') - platform.init() - self.switch_1 = platform.DEVICES[1] - assert setup_component(self.hass, switch.DOMAIN, {'switch': { - 'platform': 'test'}}) - heater_switch = self.switch_1.entity_id - - assert setup_component(self.hass, climate.DOMAIN, {'climate': { - 'platform': 'generic_thermostat', - 'name': 'test', - 'heater': heater_switch, - 'target_sensor': ENT_SENSOR - }}) - - assert STATE_OFF == \ - self.hass.states.get(heater_switch).state - - self._setup_sensor(18) - self.hass.block_till_done() - common.set_temperature(self.hass, 23) - self.hass.block_till_done() - - assert STATE_ON == \ - self.hass.states.get(heater_switch).state - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) +@pytest.fixture +def setup_comp_1(hass): + """Initialize components.""" + hass.config.units = METRIC_SYSTEM + assert hass.loop.run_until_complete( + comps.async_setup(hass, {}) + ) -class TestClimateGenericThermostat(unittest.TestCase): - """Test the Generic thermostat.""" +async def test_heater_input_boolean(hass, setup_comp_1): + """Test heater switching input_boolean.""" + heater_switch = 'input_boolean.test' + assert await async_setup_component(hass, input_boolean.DOMAIN, { + 'input_boolean': {'test': None}}) - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.units = METRIC_SYSTEM - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + assert await async_setup_component(hass, climate.DOMAIN, {'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'heater': heater_switch, + 'target_sensor': ENT_SENSOR + }}) + + assert STATE_OFF == \ + hass.states.get(heater_switch).state + + _setup_sensor(hass, 18) + await hass.async_block_till_done() + common.async_set_temperature(hass, 23) + await hass.async_block_till_done() + + assert STATE_ON == \ + hass.states.get(heater_switch).state + + +async def test_heater_switch(hass, setup_comp_1): + """Test heater switching test switch.""" + platform = loader.get_component(hass, 'switch.test') + platform.init() + switch_1 = platform.DEVICES[1] + assert await async_setup_component(hass, switch.DOMAIN, {'switch': { + 'platform': 'test'}}) + heater_switch = switch_1.entity_id + + assert await async_setup_component(hass, climate.DOMAIN, {'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'heater': heater_switch, + 'target_sensor': ENT_SENSOR + }}) + + assert STATE_OFF == \ + hass.states.get(heater_switch).state + + _setup_sensor(hass, 18) + await hass.async_block_till_done() + common.async_set_temperature(hass, 23) + await hass.async_block_till_done() + + assert STATE_ON == \ + hass.states.get(heater_switch).state + + +def _setup_sensor(hass, temp): + """Set up the test sensor.""" + hass.states.async_set(ENT_SENSOR, temp) + + +@pytest.fixture +def setup_comp_2(hass): + """Initialize components.""" + hass.config.units = METRIC_SYSTEM + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 2, @@ -161,239 +141,250 @@ class TestClimateGenericThermostat(unittest.TestCase): 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, 'away_temp': 16 - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_setup_defaults_to_unknown(self): - """Test the setting of defaults to unknown.""" - assert STATE_IDLE == self.hass.states.get(ENTITY).state - - def test_default_setup_params(self): - """Test the setup with default parameters.""" - state = self.hass.states.get(ENTITY) - assert 7 == state.attributes.get('min_temp') - assert 35 == state.attributes.get('max_temp') - assert 7 == state.attributes.get('temperature') - - def test_get_operation_modes(self): - """Test that the operation list returns the correct modes.""" - state = self.hass.states.get(ENTITY) - modes = state.attributes.get('operation_list') - assert [climate.STATE_HEAT, STATE_OFF] == modes - - def test_set_target_temp(self): - """Test the setting of the target temperature.""" - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 30.0 == state.attributes.get('temperature') - common.set_temperature(self.hass, None) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 30.0 == state.attributes.get('temperature') - - def test_set_away_mode(self): - """Test the setting away mode.""" - common.set_temperature(self.hass, 23) - self.hass.block_till_done() - common.set_away_mode(self.hass, True) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 16 == state.attributes.get('temperature') - - def test_set_away_mode_and_restore_prev_temp(self): - """Test the setting and removing away mode. - - Verify original temperature is restored. - """ - common.set_temperature(self.hass, 23) - self.hass.block_till_done() - common.set_away_mode(self.hass, True) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 16 == state.attributes.get('temperature') - common.set_away_mode(self.hass, False) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 23 == state.attributes.get('temperature') - - def test_set_away_mode_twice_and_restore_prev_temp(self): - """Test the setting away mode twice in a row. - - Verify original temperature is restored. - """ - common.set_temperature(self.hass, 23) - self.hass.block_till_done() - common.set_away_mode(self.hass, True) - self.hass.block_till_done() - common.set_away_mode(self.hass, True) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 16 == state.attributes.get('temperature') - common.set_away_mode(self.hass, False) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 23 == state.attributes.get('temperature') - - def test_sensor_bad_value(self): - """Test sensor that have None as state.""" - state = self.hass.states.get(ENTITY) - temp = state.attributes.get('current_temperature') - - self._setup_sensor(None) - self.hass.block_till_done() - - state = self.hass.states.get(ENTITY) - assert temp == state.attributes.get('current_temperature') - - def test_set_target_temp_heater_on(self): - """Test if target temperature turn heater on.""" - self._setup_switch(False) - self._setup_sensor(25) - self.hass.block_till_done() - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_set_target_temp_heater_off(self): - """Test if target temperature turn heater off.""" - self._setup_switch(True) - self._setup_sensor(30) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - assert 2 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_heater_on_within_tolerance(self): - """Test if temperature change doesn't turn on within tolerance.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(29) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_heater_on_outside_tolerance(self): - """Test if temperature change turn heater on outside cold tolerance.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(27) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_heater_off_within_tolerance(self): - """Test if temperature change doesn't turn off within tolerance.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(33) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_heater_off_outside_tolerance(self): - """Test if temperature change turn heater off outside hot tolerance.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(35) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_running_when_operating_mode_is_off(self): - """Test that the switch turns off when enabled is set False.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_no_state_change_when_operation_mode_off(self): - """Test that the switch doesn't turn on when enabled is False.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - assert 0 == len(self.calls) - - @mock.patch('logging.Logger.error') - def test_invalid_operating_mode(self, log_mock): - """Test error handling for invalid operation mode.""" - common.set_operation_mode(self.hass, 'invalid mode') - self.hass.block_till_done() - assert log_mock.call_count == 1 - - def test_operating_mode_heat(self): - """Test change mode from OFF to HEAT. - - Switch turns on when temp below setpoint and mode changes. - """ - common.set_operation_mode(self.hass, STATE_OFF) - common.set_temperature(self.hass, 30) - self._setup_sensor(25) - self.hass.block_till_done() - self._setup_switch(False) - common.set_operation_mode(self.hass, climate.STATE_HEAT) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatACMode(unittest.TestCase): - """Test the Generic thermostat.""" +async def test_setup_defaults_to_unknown(hass, setup_comp_2): + """Test the setting of defaults to unknown.""" + assert STATE_IDLE == hass.states.get(ENTITY).state - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + +async def test_default_setup_params(hass, setup_comp_2): + """Test the setup with default parameters.""" + state = hass.states.get(ENTITY) + assert 7 == state.attributes.get('min_temp') + assert 35 == state.attributes.get('max_temp') + assert 7 == state.attributes.get('temperature') + + +async def test_get_operation_modes(hass, setup_comp_2): + """Test that the operation list returns the correct modes.""" + state = hass.states.get(ENTITY) + modes = state.attributes.get('operation_list') + assert [climate.STATE_HEAT, STATE_OFF] == modes + + +async def test_set_target_temp(hass, setup_comp_2): + """Test the setting of the target temperature.""" + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 30.0 == state.attributes.get('temperature') + common.async_set_temperature(hass, None) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 30.0 == state.attributes.get('temperature') + + +async def test_set_away_mode(hass, setup_comp_2): + """Test the setting away mode.""" + common.async_set_temperature(hass, 23) + await hass.async_block_till_done() + common.async_set_away_mode(hass, True) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 16 == state.attributes.get('temperature') + + +async def test_set_away_mode_and_restore_prev_temp(hass, setup_comp_2): + """Test the setting and removing away mode. + + Verify original temperature is restored. + """ + common.async_set_temperature(hass, 23) + await hass.async_block_till_done() + common.async_set_away_mode(hass, True) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 16 == state.attributes.get('temperature') + common.async_set_away_mode(hass, False) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 23 == state.attributes.get('temperature') + + +async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): + """Test the setting away mode twice in a row. + + Verify original temperature is restored. + """ + common.async_set_temperature(hass, 23) + await hass.async_block_till_done() + common.async_set_away_mode(hass, True) + await hass.async_block_till_done() + common.async_set_away_mode(hass, True) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 16 == state.attributes.get('temperature') + common.async_set_away_mode(hass, False) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 23 == state.attributes.get('temperature') + + +async def test_sensor_bad_value(hass, setup_comp_2): + """Test sensor that have None as state.""" + state = hass.states.get(ENTITY) + temp = state.attributes.get('current_temperature') + + _setup_sensor(hass, None) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY) + assert temp == state.attributes.get('current_temperature') + + +async def test_set_target_temp_heater_on(hass, setup_comp_2): + """Test if target temperature turn heater on.""" + calls = _setup_switch(hass, False) + _setup_sensor(hass, 25) + await hass.async_block_till_done() + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_set_target_temp_heater_off(hass, setup_comp_2): + """Test if target temperature turn heater off.""" + calls = _setup_switch(hass, True) + _setup_sensor(hass, 30) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + assert 2 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_heater_on_within_tolerance(hass, setup_comp_2): + """Test if temperature change doesn't turn on within tolerance.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 29) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_heater_on_outside_tolerance(hass, setup_comp_2): + """Test if temperature change turn heater on outside cold tolerance.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 27) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_heater_off_within_tolerance(hass, setup_comp_2): + """Test if temperature change doesn't turn off within tolerance.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 33) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_heater_off_outside_tolerance(hass, setup_comp_2): + """Test if temperature change turn heater off outside hot tolerance.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 35) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_running_when_operating_mode_is_off(hass, setup_comp_2): + """Test that the switch turns off when enabled is set False.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_no_state_change_when_operation_mode_off(hass, setup_comp_2): + """Test that the switch doesn't turn on when enabled is False.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + + +@mock.patch('logging.Logger.error') +async def test_invalid_operating_mode(log_mock, hass, setup_comp_2): + """Test error handling for invalid operation mode.""" + common.async_set_operation_mode(hass, 'invalid mode') + await hass.async_block_till_done() + assert log_mock.call_count == 1 + + +async def test_operating_mode_heat(hass, setup_comp_2): + """Test change mode from OFF to HEAT. + + Switch turns on when temp below setpoint and mode changes. + """ + common.async_set_operation_mode(hass, STATE_OFF) + common.async_set_temperature(hass, 30) + _setup_sensor(hass, 25) + await hass.async_block_till_done() + calls = _setup_switch(hass, False) + common.async_set_operation_mode(hass, climate.STATE_HEAT) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +def _setup_switch(hass, is_on): + """Set up the test switch.""" + hass.states.async_set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) + calls = [] + + @callback + def log_call(call): + """Log service calls.""" + calls.append(call) + + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_ON, log_call) + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + + return calls + + +@pytest.fixture +def setup_comp_3(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 2, @@ -402,161 +393,148 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, 'ac_mode': True - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_set_target_temp_ac_off(self): - """Test if target temperature turn ac off.""" - self._setup_switch(True) - self._setup_sensor(25) - self.hass.block_till_done() - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - assert 2 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_turn_away_mode_on_cooling(self): - """Test the setting away mode when cooling.""" - self._setup_sensor(25) - self.hass.block_till_done() - common.set_temperature(self.hass, 19) - self.hass.block_till_done() - common.set_away_mode(self.hass, True) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 30 == state.attributes.get('temperature') - - def test_operating_mode_cool(self): - """Test change mode from OFF to COOL. - - Switch turns on when temp below setpoint and mode changes. - """ - common.set_operation_mode(self.hass, STATE_OFF) - common.set_temperature(self.hass, 25) - self._setup_sensor(30) - self.hass.block_till_done() - self._setup_switch(False) - common.set_operation_mode(self.hass, climate.STATE_COOL) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_set_target_temp_ac_on(self): - """Test if target temperature turn ac on.""" - self._setup_switch(False) - self._setup_sensor(30) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_ac_off_within_tolerance(self): - """Test if temperature change doesn't turn ac off within tolerance.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(29.8) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_set_temp_change_ac_off_outside_tolerance(self): - """Test if temperature change turn ac off.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(27) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_ac_on_within_tolerance(self): - """Test if temperature change doesn't turn ac on within tolerance.""" - self._setup_switch(False) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(25.2) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_ac_on_outside_tolerance(self): - """Test if temperature change turn ac on.""" - self._setup_switch(False) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_running_when_operating_mode_is_off(self): - """Test that the switch turns off when enabled is set False.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_no_state_change_when_operation_mode_off(self): - """Test that the switch doesn't turn on when enabled is False.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - self._setup_sensor(35) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): - """Test the Generic Thermostat.""" +async def test_set_target_temp_ac_off(hass, setup_comp_3): + """Test if target temperature turn ac off.""" + calls = _setup_switch(hass, True) + _setup_sensor(hass, 25) + await hass.async_block_till_done() + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + assert 2 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + +async def test_turn_away_mode_on_cooling(hass, setup_comp_3): + """Test the setting away mode when cooling.""" + _setup_sensor(hass, 25) + await hass.async_block_till_done() + common.async_set_temperature(hass, 19) + await hass.async_block_till_done() + common.async_set_away_mode(hass, True) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert 30 == state.attributes.get('temperature') + + +async def test_operating_mode_cool(hass, setup_comp_3): + """Test change mode from OFF to COOL. + + Switch turns on when temp below setpoint and mode changes. + """ + common.async_set_operation_mode(hass, STATE_OFF) + common.async_set_temperature(hass, 25) + _setup_sensor(hass, 30) + await hass.async_block_till_done() + calls = _setup_switch(hass, False) + common.async_set_operation_mode(hass, climate.STATE_COOL) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_set_target_temp_ac_on(hass, setup_comp_3): + """Test if target temperature turn ac on.""" + calls = _setup_switch(hass, False) + _setup_sensor(hass, 30) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_ac_off_within_tolerance(hass, setup_comp_3): + """Test if temperature change doesn't turn ac off within tolerance.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 29.8) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_set_temp_change_ac_off_outside_tolerance(hass, setup_comp_3): + """Test if temperature change turn ac off.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 27) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_ac_on_within_tolerance(hass, setup_comp_3): + """Test if temperature change doesn't turn ac on within tolerance.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 25.2) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_ac_on_outside_tolerance(hass, setup_comp_3): + """Test if temperature change turn ac on.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_running_when_operating_mode_is_off_2(hass, setup_comp_3): + """Test that the switch turns off when enabled is set False.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): + """Test that the switch doesn't turn on when enabled is False.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + _setup_sensor(hass, 35) + await hass.async_block_till_done() + assert 0 == len(calls) + + +@pytest.fixture +def setup_comp_4(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 0.3, @@ -565,122 +543,214 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): 'target_sensor': ENT_SENSOR, 'ac_mode': True, 'min_cycle_duration': datetime.timedelta(minutes=10) - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_temp_change_ac_trigger_on_not_long_enough(self): - """Test if temperature change turn ac on.""" - self._setup_switch(False) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_ac_trigger_on_long_enough(self): - """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, - tzinfo=datetime.timezone.utc) - with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=fake_changed): - self._setup_switch(False) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_ac_trigger_off_not_long_enough(self): - """Test if temperature change turn ac on.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_ac_trigger_off_long_enough(self): - """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, - tzinfo=datetime.timezone.utc) - with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=fake_changed): - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_mode_change_ac_trigger_off_not_long_enough(self): - """Test if mode change turns ac off despite minimum cycle.""" - self._setup_switch(True) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) - common.set_operation_mode(self.hass, climate.STATE_OFF) - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) - - def test_mode_change_ac_trigger_on_not_long_enough(self): - """Test if mode change turns ac on despite minimum cycle.""" - self._setup_switch(False) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) - common.set_operation_mode(self.hass, climate.STATE_HEAT) - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatMinCycle(unittest.TestCase): - """Test the Generic thermostat.""" +async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): + """Test if temperature change turn ac on.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + +async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): + """Test if temperature change turn ac on.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): + """Test if temperature change turn ac on.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): + """Test if temperature change turn ac on.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_ac_trigger_off_not_long_enough(hass, setup_comp_4): + """Test if mode change turns ac off despite minimum cycle.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_OFF) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): + """Test if mode change turns ac on despite minimum cycle.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_HEAT) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +@pytest.fixture +def setup_comp_5(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'cold_tolerance': 0.3, + 'hot_tolerance': 0.3, + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR, + 'ac_mode': True, + 'min_cycle_duration': datetime.timedelta(minutes=10) + }})) + + +async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): + """Test if temperature change turn ac on.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): + """Test if temperature change turn ac on.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_ac_trigger_off_not_long_enough_2( + hass, setup_comp_5): + """Test if temperature change turn ac on.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): + """Test if temperature change turn ac on.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_ac_trigger_off_not_long_enough_2( + hass, setup_comp_5): + """Test if mode change turns ac off despite minimum cycle.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_OFF) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): + """Test if mode change turns ac on despite minimum cycle.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_HEAT) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +@pytest.fixture +def setup_comp_6(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 0.3, @@ -688,122 +758,109 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): 'heater': ENT_SWITCH, 'target_sensor': ENT_SENSOR, 'min_cycle_duration': datetime.timedelta(minutes=10) - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_temp_change_heater_trigger_off_not_long_enough(self): - """Test if temp change doesn't turn heater off because of time.""" - self._setup_switch(True) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_heater_trigger_on_not_long_enough(self): - """Test if temp change doesn't turn heater on because of time.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - assert 0 == len(self.calls) - - def test_temp_change_heater_trigger_on_long_enough(self): - """Test if temperature change turn heater on after min cycle.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, - tzinfo=datetime.timezone.utc) - with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=fake_changed): - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_heater_trigger_off_long_enough(self): - """Test if temperature change turn heater off after min cycle.""" - fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, - tzinfo=datetime.timezone.utc) - with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=fake_changed): - self._setup_switch(True) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_mode_change_heater_trigger_off_not_long_enough(self): - """Test if mode change turns heater off despite minimum cycle.""" - self._setup_switch(True) - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) - common.set_operation_mode(self.hass, climate.STATE_OFF) - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) - - def test_mode_change_heater_trigger_on_not_long_enough(self): - """Test if mode change turns heater on despite minimum cycle.""" - self._setup_switch(False) - common.set_temperature(self.hass, 30) - self.hass.block_till_done() - self._setup_sensor(25) - self.hass.block_till_done() - self.assertEqual(0, len(self.calls)) - common.set_operation_mode(self.hass, climate.STATE_HEAT) - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - call = self.calls[0] - self.assertEqual('homeassistant', call.domain) - self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ENT_SWITCH, call.data['entity_id']) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): - """Test the Generic Thermostat.""" +async def test_temp_change_heater_trigger_off_not_long_enough( + hass, setup_comp_6): + """Test if temp change doesn't turn heater off because of time.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + +async def test_temp_change_heater_trigger_on_not_long_enough( + hass, setup_comp_6): + """Test if temp change doesn't turn heater on because of time.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + + +async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): + """Test if temperature change turn heater on after min cycle.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): + """Test if temperature change turn heater off after min cycle.""" + fake_changed = datetime.datetime(1918, 11, 11, 11, 11, 11, + tzinfo=datetime.timezone.utc) + with mock.patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=fake_changed): + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_heater_trigger_off_not_long_enough( + hass, setup_comp_6): + """Test if mode change turns heater off despite minimum cycle.""" + calls = _setup_switch(hass, True) + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_OFF) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +async def test_mode_change_heater_trigger_on_not_long_enough( + hass, setup_comp_6): + """Test if mode change turns heater on despite minimum cycle.""" + calls = _setup_switch(hass, False) + common.async_set_temperature(hass, 30) + await hass.async_block_till_done() + _setup_sensor(hass, 25) + await hass.async_block_till_done() + assert 0 == len(calls) + common.async_set_operation_mode(hass, climate.STATE_HEAT) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert 'homeassistant' == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +@pytest.fixture +def setup_comp_7(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 0.3, @@ -814,88 +871,68 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase): 'ac_mode': True, 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_temp_change_ac_trigger_on_long_enough(self): - """Test if turn on signal is sent at keep-alive intervals.""" - self._setup_switch(True) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - test_time = datetime.datetime.now(pytz.UTC) - self._send_time_changed(test_time) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=5)) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_ac_trigger_off_long_enough(self): - """Test if turn on signal is sent at keep-alive intervals.""" - self._setup_switch(False) - self.hass.block_till_done() - self._setup_sensor(20) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - test_time = datetime.datetime.now(pytz.UTC) - self._send_time_changed(test_time) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=5)) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def _send_time_changed(self, now): - """Send a time changed event.""" - self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatKeepAlive(unittest.TestCase): - """Test the Generic Thermostat.""" +async def test_temp_change_ac_trigger_on_long_enough_3(hass, setup_comp_7): + """Test if turn on signal is sent at keep-alive intervals.""" + calls = _setup_switch(hass, True) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + test_time = datetime.datetime.now(pytz.UTC) + _send_time_changed(hass, test_time) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=5)) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=10)) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS - assert setup_component(self.hass, climate.DOMAIN, {'climate': { + +async def test_temp_change_ac_trigger_off_long_enough_3(hass, setup_comp_7): + """Test if turn on signal is sent at keep-alive intervals.""" + calls = _setup_switch(hass, False) + await hass.async_block_till_done() + _setup_sensor(hass, 20) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + test_time = datetime.datetime.now(pytz.UTC) + _send_time_changed(hass, test_time) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=5)) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=10)) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +def _send_time_changed(hass, now): + """Send a time changed event.""" + hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) + + +@pytest.fixture +def setup_comp_8(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'cold_tolerance': 0.3, @@ -905,90 +942,64 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase): 'target_sensor': ENT_SENSOR, 'min_cycle_duration': datetime.timedelta(minutes=15), 'keep_alive': datetime.timedelta(minutes=10) - }}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_temp_change_heater_trigger_on_long_enough(self): - """Test if turn on signal is sent at keep-alive intervals.""" - self._setup_switch(True) - self.hass.block_till_done() - self._setup_sensor(20) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - test_time = datetime.datetime.now(pytz.UTC) - self._send_time_changed(test_time) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=5)) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_ON == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def test_temp_change_heater_trigger_off_long_enough(self): - """Test if turn on signal is sent at keep-alive intervals.""" - self._setup_switch(False) - self.hass.block_till_done() - self._setup_sensor(30) - self.hass.block_till_done() - common.set_temperature(self.hass, 25) - self.hass.block_till_done() - test_time = datetime.datetime.now(pytz.UTC) - self._send_time_changed(test_time) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=5)) - self.hass.block_till_done() - assert 0 == len(self.calls) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - self.hass.block_till_done() - assert 1 == len(self.calls) - call = self.calls[0] - assert HASS_DOMAIN == call.domain - assert SERVICE_TURN_OFF == call.service - assert ENT_SWITCH == call.data['entity_id'] - - def _send_time_changed(self, now): - """Send a time changed event.""" - self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) + }})) -class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): - """Test the Generic Thermostat.""" +async def test_temp_change_heater_trigger_on_long_enough_2(hass, setup_comp_8): + """Test if turn on signal is sent at keep-alive intervals.""" + calls = _setup_switch(hass, True) + await hass.async_block_till_done() + _setup_sensor(hass, 20) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + test_time = datetime.datetime.now(pytz.UTC) + _send_time_changed(hass, test_time) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=5)) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=10)) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_ON == call.service + assert ENT_SWITCH == call.data['entity_id'] - HEAT_ENTITY = 'climate.test_heat' - COOL_ENTITY = 'climate.test_cool' - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - assert setup_component(self.hass, climate.DOMAIN, {'climate': [ +async def test_temp_change_heater_trigger_off_long_enough_2( + hass, setup_comp_8): + """Test if turn on signal is sent at keep-alive intervals.""" + calls = _setup_switch(hass, False) + await hass.async_block_till_done() + _setup_sensor(hass, 30) + await hass.async_block_till_done() + common.async_set_temperature(hass, 25) + await hass.async_block_till_done() + test_time = datetime.datetime.now(pytz.UTC) + _send_time_changed(hass, test_time) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=5)) + await hass.async_block_till_done() + assert 0 == len(calls) + _send_time_changed(hass, test_time + datetime.timedelta(minutes=10)) + await hass.async_block_till_done() + assert 1 == len(calls) + call = calls[0] + assert HASS_DOMAIN == call.domain + assert SERVICE_TURN_OFF == call.service + assert ENT_SWITCH == call.data['entity_id'] + + +@pytest.fixture +def setup_comp_9(hass): + """Initialize components.""" + hass.config.temperature_unit = TEMP_CELSIUS + assert hass.loop.run_until_complete(async_setup_component( + hass, climate.DOMAIN, {'climate': [ { 'platform': 'generic_thermostat', 'name': 'test_heat', @@ -1002,71 +1013,70 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase): 'ac_mode': True, 'target_sensor': ENT_SENSOR } - ]}) - - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() - - def test_turn_on_when_off(self): - """Test if climate.turn_on turns on a turned off device.""" - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - self.hass.services.call('climate', SERVICE_TURN_ON) - self.hass.block_till_done() - state_heat = self.hass.states.get(self.HEAT_ENTITY) - state_cool = self.hass.states.get(self.COOL_ENTITY) - assert STATE_HEAT == \ - state_heat.attributes.get('operation_mode') - assert STATE_COOL == \ - state_cool.attributes.get('operation_mode') - - def test_turn_on_when_on(self): - """Test if climate.turn_on does nothing to a turned on device.""" - common.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY) - common.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY) - self.hass.block_till_done() - self.hass.services.call('climate', SERVICE_TURN_ON) - self.hass.block_till_done() - state_heat = self.hass.states.get(self.HEAT_ENTITY) - state_cool = self.hass.states.get(self.COOL_ENTITY) - assert STATE_HEAT == \ - state_heat.attributes.get('operation_mode') - assert STATE_COOL == \ - state_cool.attributes.get('operation_mode') - - def test_turn_off_when_on(self): - """Test if climate.turn_off turns off a turned on device.""" - common.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY) - common.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY) - self.hass.block_till_done() - self.hass.services.call('climate', SERVICE_TURN_OFF) - self.hass.block_till_done() - state_heat = self.hass.states.get(self.HEAT_ENTITY) - state_cool = self.hass.states.get(self.COOL_ENTITY) - assert STATE_OFF == \ - state_heat.attributes.get('operation_mode') - assert STATE_OFF == \ - state_cool.attributes.get('operation_mode') - - def test_turn_off_when_off(self): - """Test if climate.turn_off does nothing to a turned off device.""" - common.set_operation_mode(self.hass, STATE_OFF) - self.hass.block_till_done() - self.hass.services.call('climate', SERVICE_TURN_OFF) - self.hass.block_till_done() - state_heat = self.hass.states.get(self.HEAT_ENTITY) - state_cool = self.hass.states.get(self.COOL_ENTITY) - assert STATE_OFF == \ - state_heat.attributes.get('operation_mode') - assert STATE_OFF == \ - state_cool.attributes.get('operation_mode') + ]})) -@asyncio.coroutine -def test_custom_setup_params(hass): +async def test_turn_on_when_off(hass, setup_comp_9): + """Test if climate.turn_on turns on a turned off device.""" + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + await hass.services.async_call('climate', SERVICE_TURN_ON) + await hass.async_block_till_done() + state_heat = hass.states.get(HEAT_ENTITY) + state_cool = hass.states.get(COOL_ENTITY) + assert STATE_HEAT == \ + state_heat.attributes.get('operation_mode') + assert STATE_COOL == \ + state_cool.attributes.get('operation_mode') + + +async def test_turn_on_when_on(hass, setup_comp_9): + """Test if climate.turn_on does nothing to a turned on device.""" + common.async_set_operation_mode(hass, STATE_HEAT, HEAT_ENTITY) + common.async_set_operation_mode(hass, STATE_COOL, COOL_ENTITY) + await hass.async_block_till_done() + await hass.services.async_call('climate', SERVICE_TURN_ON) + await hass.async_block_till_done() + state_heat = hass.states.get(HEAT_ENTITY) + state_cool = hass.states.get(COOL_ENTITY) + assert STATE_HEAT == \ + state_heat.attributes.get('operation_mode') + assert STATE_COOL == \ + state_cool.attributes.get('operation_mode') + + +async def test_turn_off_when_on(hass, setup_comp_9): + """Test if climate.turn_off turns off a turned on device.""" + common.async_set_operation_mode(hass, STATE_HEAT, HEAT_ENTITY) + common.async_set_operation_mode(hass, STATE_COOL, COOL_ENTITY) + await hass.async_block_till_done() + await hass.services.async_call('climate', SERVICE_TURN_OFF) + await hass.async_block_till_done() + state_heat = hass.states.get(HEAT_ENTITY) + state_cool = hass.states.get(COOL_ENTITY) + assert STATE_OFF == \ + state_heat.attributes.get('operation_mode') + assert STATE_OFF == \ + state_cool.attributes.get('operation_mode') + + +async def test_turn_off_when_off(hass, setup_comp_9): + """Test if climate.turn_off does nothing to a turned off device.""" + common.async_set_operation_mode(hass, STATE_OFF) + await hass.async_block_till_done() + await hass.services.async_call('climate', SERVICE_TURN_OFF) + await hass.async_block_till_done() + state_heat = hass.states.get(HEAT_ENTITY) + state_cool = hass.states.get(COOL_ENTITY) + assert STATE_OFF == \ + state_heat.attributes.get('operation_mode') + assert STATE_OFF == \ + state_cool.attributes.get('operation_mode') + + +async def test_custom_setup_params(hass): """Test the setup with custom parameters.""" - result = yield from async_setup_component( + result = await async_setup_component( hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', @@ -1083,8 +1093,7 @@ def test_custom_setup_params(hass): assert state.attributes.get('temperature') == TARGET_TEMP -@asyncio.coroutine -def test_restore_state(hass): +async def test_restore_state(hass): """Ensure states are restored on startup.""" mock_restore_cache(hass, ( State('climate.test_thermostat', '0', {ATTR_TEMPERATURE: "20", @@ -1093,7 +1102,7 @@ def test_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test_thermostat', @@ -1107,8 +1116,7 @@ def test_restore_state(hass): assert(state.state == STATE_OFF) -@asyncio.coroutine -def test_no_restore_state(hass): +async def test_no_restore_state(hass): """Ensure states are restored on startup if they exist. Allows for graceful reboot. @@ -1120,7 +1128,7 @@ def test_no_restore_state(hass): hass.state = CoreState.starting - yield from async_setup_component( + await async_setup_component( hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test_thermostat', @@ -1134,79 +1142,52 @@ def test_no_restore_state(hass): assert(state.state == STATE_OFF) -class TestClimateGenericThermostatRestoreState(unittest.TestCase): - """Test generic thermostat when restore state from HA startup.""" +async def test_restore_state_uncoherence_case(hass): + """ + Test restore from a strange state. - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS + - Turn the generic thermostat off + - Restart HA and restore state from DB + """ + _mock_restore_cache(hass, temperature=20) - def tearDown(self): # pylint: disable=invalid-name - """Stop down everything that was started.""" - self.hass.stop() + calls = _setup_switch(hass, False) + _setup_sensor(hass, 15) + await _setup_climate(hass, ) + await hass.async_block_till_done() - def test_restore_state_uncoherence_case(self): - """ - Test restore from a strange state. + state = hass.states.get(ENTITY) + assert 20 == state.attributes[ATTR_TEMPERATURE] + assert STATE_OFF == \ + state.attributes[climate.ATTR_OPERATION_MODE] + assert STATE_OFF == state.state + assert 0 == len(calls) - - Turn the generic thermostat off - - Restart HA and restore state from DB - """ - self._mock_restore_cache(temperature=20) + calls = _setup_switch(hass, False) + await hass.async_block_till_done() + state = hass.states.get(ENTITY) + assert STATE_OFF == \ + state.attributes[climate.ATTR_OPERATION_MODE] + assert STATE_OFF == state.state - self._setup_switch(False) - self._setup_sensor(15) - self._setup_climate() - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert 20 == state.attributes[ATTR_TEMPERATURE] - assert STATE_OFF == \ - state.attributes[climate.ATTR_OPERATION_MODE] - assert STATE_OFF == state.state - assert 0 == len(self.calls) +async def _setup_climate(hass): + assert await async_setup_component(hass, climate.DOMAIN, {'climate': { + 'platform': 'generic_thermostat', + 'name': 'test', + 'cold_tolerance': 2, + 'hot_tolerance': 4, + 'away_temp': 30, + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR, + 'ac_mode': True + }}) - self._setup_switch(False) - self.hass.block_till_done() - state = self.hass.states.get(ENTITY) - assert STATE_OFF == \ - state.attributes[climate.ATTR_OPERATION_MODE] - assert STATE_OFF == state.state - def _setup_climate(self): - assert setup_component(self.hass, climate.DOMAIN, {'climate': { - 'platform': 'generic_thermostat', - 'name': 'test', - 'cold_tolerance': 2, - 'hot_tolerance': 4, - 'away_temp': 30, - 'heater': ENT_SWITCH, - 'target_sensor': ENT_SENSOR, - 'ac_mode': True - }}) - - def _setup_sensor(self, temp): - """Set up the test sensor.""" - self.hass.states.set(ENT_SENSOR, temp) - - def _setup_switch(self, is_on): - """Set up the test switch.""" - self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) - self.calls = [] - - @callback - def log_call(call): - """Log service calls.""" - self.calls.append(call) - - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, log_call) - self.hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, log_call) - - def _mock_restore_cache(self, temperature=20, operation_mode=STATE_OFF): - mock_restore_cache(self.hass, ( - State(ENTITY, '0', { - ATTR_TEMPERATURE: str(temperature), - climate.ATTR_OPERATION_MODE: operation_mode, - ATTR_AWAY_MODE: "on"}), - )) +def _mock_restore_cache(hass, temperature=20, operation_mode=STATE_OFF): + mock_restore_cache(hass, ( + State(ENTITY, '0', { + ATTR_TEMPERATURE: str(temperature), + climate.ATTR_OPERATION_MODE: operation_mode, + ATTR_AWAY_MODE: "on"}), + )) From 1f290bad94d931796c74ab6dd859552a8e7ffb8e Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Fri, 2 Nov 2018 16:08:22 -0400 Subject: [PATCH 194/230] Update fan/demo tests to async (#18109) * Update fan/demo tests to async * Use async_create_task --- tests/components/fan/common.py | 34 +- tests/components/fan/test_demo.py | 152 ++-- tests/components/fan/test_template.py | 1155 +++++++++++++------------ 3 files changed, 678 insertions(+), 663 deletions(-) diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py index 60e1cab1ac0..f3873dd9fe0 100644 --- a/tests/components/fan/common.py +++ b/tests/components/fan/common.py @@ -9,10 +9,12 @@ from homeassistant.components.fan import ( from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) from homeassistant.loader import bind_hass +from homeassistant.core import callback +@callback @bind_hass -def turn_on(hass, entity_id: str = None, speed: str = None) -> None: +def async_turn_on(hass, entity_id: str = None, speed: str = None) -> None: """Turn all or specified fan on.""" data = { key: value for key, value in [ @@ -21,20 +23,24 @@ def turn_on(hass, entity_id: str = None, speed: str = None) -> None: ] if value is not None } - hass.services.call(DOMAIN, SERVICE_TURN_ON, data) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)) +@callback @bind_hass -def turn_off(hass, entity_id: str = None) -> None: +def async_turn_off(hass, entity_id: str = None) -> None: """Turn all or specified fan off.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)) +@callback @bind_hass -def oscillate(hass, entity_id: str = None, - should_oscillate: bool = True) -> None: +def async_oscillate(hass, entity_id: str = None, + should_oscillate: bool = True) -> None: """Set oscillation on all or specified fan.""" data = { key: value for key, value in [ @@ -43,11 +49,13 @@ def oscillate(hass, entity_id: str = None, ] if value is not None } - hass.services.call(DOMAIN, SERVICE_OSCILLATE, data) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_OSCILLATE, data)) +@callback @bind_hass -def set_speed(hass, entity_id: str = None, speed: str = None) -> None: +def async_set_speed(hass, entity_id: str = None, speed: str = None) -> None: """Set speed for all or specified fan.""" data = { key: value for key, value in [ @@ -56,11 +64,14 @@ def set_speed(hass, entity_id: str = None, speed: str = None) -> None: ] if value is not None } - hass.services.call(DOMAIN, SERVICE_SET_SPEED, data) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_SPEED, data)) +@callback @bind_hass -def set_direction(hass, entity_id: str = None, direction: str = None) -> None: +def async_set_direction( + hass, entity_id: str = None, direction: str = None) -> None: """Set direction for all or specified fan.""" data = { key: value for key, value in [ @@ -69,4 +80,5 @@ def set_direction(hass, entity_id: str = None, direction: str = None) -> None: ] if value is not None } - hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data) + hass.async_create_task( + hass.services.async_call(DOMAIN, SERVICE_SET_DIRECTION, data)) diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py index 35835ac37e5..5a819b0c5da 100644 --- a/tests/components/fan/test_demo.py +++ b/tests/components/fan/test_demo.py @@ -1,108 +1,108 @@ """Test cases around the demo fan platform.""" +import pytest -import unittest - -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component from homeassistant.components import fan from homeassistant.const import STATE_OFF, STATE_ON -from tests.common import get_test_home_assistant from tests.components.fan import common FAN_ENTITY_ID = 'fan.living_room_fan' -class TestDemoFan(unittest.TestCase): - """Test the fan demo platform.""" +def get_entity(hass): + """Get the fan entity.""" + return hass.states.get(FAN_ENTITY_ID) - def get_entity(self): - """Get the fan entity.""" - return self.hass.states.get(FAN_ENTITY_ID) - def setUp(self): - """Initialize unit test data.""" - self.hass = get_test_home_assistant() - assert setup_component(self.hass, fan.DOMAIN, {'fan': { +@pytest.fixture(autouse=True) +def setup_comp(hass): + """Initialize components.""" + hass.loop.run_until_complete(async_setup_component(hass, fan.DOMAIN, { + 'fan': { 'platform': 'demo', - }}) - self.hass.block_till_done() + } + })) - def tearDown(self): - """Tear down unit test data.""" - self.hass.stop() - def test_turn_on(self): - """Test turning on the device.""" - assert STATE_OFF == self.get_entity().state +async def test_turn_on(hass): + """Test turning on the device.""" + assert STATE_OFF == get_entity(hass).state - common.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.block_till_done() - assert STATE_OFF != self.get_entity().state + common.async_turn_on(hass, FAN_ENTITY_ID) + await hass.async_block_till_done() + assert STATE_OFF != get_entity(hass).state - common.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH) - self.hass.block_till_done() - assert STATE_ON == self.get_entity().state - assert fan.SPEED_HIGH == \ - self.get_entity().attributes[fan.ATTR_SPEED] + common.async_turn_on(hass, FAN_ENTITY_ID, fan.SPEED_HIGH) + await hass.async_block_till_done() + assert STATE_ON == get_entity(hass).state + assert fan.SPEED_HIGH == \ + get_entity(hass).attributes[fan.ATTR_SPEED] - def test_turn_off(self): - """Test turning off the device.""" - assert STATE_OFF == self.get_entity().state - common.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.block_till_done() - assert STATE_OFF != self.get_entity().state +async def test_turn_off(hass): + """Test turning off the device.""" + assert STATE_OFF == get_entity(hass).state - common.turn_off(self.hass, FAN_ENTITY_ID) - self.hass.block_till_done() - assert STATE_OFF == self.get_entity().state + common.async_turn_on(hass, FAN_ENTITY_ID) + await hass.async_block_till_done() + assert STATE_OFF != get_entity(hass).state - def test_turn_off_without_entity_id(self): - """Test turning off all fans.""" - assert STATE_OFF == self.get_entity().state + common.async_turn_off(hass, FAN_ENTITY_ID) + await hass.async_block_till_done() + assert STATE_OFF == get_entity(hass).state - common.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.block_till_done() - assert STATE_OFF != self.get_entity().state - common.turn_off(self.hass) - self.hass.block_till_done() - assert STATE_OFF == self.get_entity().state +async def test_turn_off_without_entity_id(hass): + """Test turning off all fans.""" + assert STATE_OFF == get_entity(hass).state - def test_set_direction(self): - """Test setting the direction of the device.""" - assert STATE_OFF == self.get_entity().state + common.async_turn_on(hass, FAN_ENTITY_ID) + await hass.async_block_till_done() + assert STATE_OFF != get_entity(hass).state - common.set_direction(self.hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE) - self.hass.block_till_done() - assert fan.DIRECTION_REVERSE == \ - self.get_entity().attributes.get('direction') + common.async_turn_off(hass) + await hass.async_block_till_done() + assert STATE_OFF == get_entity(hass).state - def test_set_speed(self): - """Test setting the speed of the device.""" - assert STATE_OFF == self.get_entity().state - common.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW) - self.hass.block_till_done() - assert fan.SPEED_LOW == \ - self.get_entity().attributes.get('speed') +async def test_set_direction(hass): + """Test setting the direction of the device.""" + assert STATE_OFF == get_entity(hass).state - def test_oscillate(self): - """Test oscillating the fan.""" - assert not self.get_entity().attributes.get('oscillating') + common.async_set_direction(hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE) + await hass.async_block_till_done() + assert fan.DIRECTION_REVERSE == \ + get_entity(hass).attributes.get('direction') - common.oscillate(self.hass, FAN_ENTITY_ID, True) - self.hass.block_till_done() - assert self.get_entity().attributes.get('oscillating') - common.oscillate(self.hass, FAN_ENTITY_ID, False) - self.hass.block_till_done() - assert not self.get_entity().attributes.get('oscillating') +async def test_set_speed(hass): + """Test setting the speed of the device.""" + assert STATE_OFF == get_entity(hass).state - def test_is_on(self): - """Test is on service call.""" - assert not fan.is_on(self.hass, FAN_ENTITY_ID) + common.async_set_speed(hass, FAN_ENTITY_ID, fan.SPEED_LOW) + await hass.async_block_till_done() + assert fan.SPEED_LOW == \ + get_entity(hass).attributes.get('speed') - common.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.block_till_done() - assert fan.is_on(self.hass, FAN_ENTITY_ID) + +async def test_oscillate(hass): + """Test oscillating the fan.""" + assert not get_entity(hass).attributes.get('oscillating') + + common.async_oscillate(hass, FAN_ENTITY_ID, True) + await hass.async_block_till_done() + assert get_entity(hass).attributes.get('oscillating') + + common.async_oscillate(hass, FAN_ENTITY_ID, False) + await hass.async_block_till_done() + assert not get_entity(hass).attributes.get('oscillating') + + +async def test_is_on(hass): + """Test is on service call.""" + assert not fan.is_on(hass, FAN_ENTITY_ID) + + common.async_turn_on(hass, FAN_ENTITY_ID) + await hass.async_block_till_done() + assert fan.is_on(hass, FAN_ENTITY_ID) diff --git a/tests/components/fan/test_template.py b/tests/components/fan/test_template.py index 09d3603e004..85e63025bbc 100644 --- a/tests/components/fan/test_template.py +++ b/tests/components/fan/test_template.py @@ -1,7 +1,7 @@ """The tests for the Template fan platform.""" import logging +import pytest -from homeassistant.core import callback from homeassistant import setup from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.fan import ( @@ -9,7 +9,7 @@ from homeassistant.components.fan import ( ATTR_DIRECTION, DIRECTION_FORWARD, DIRECTION_REVERSE) from tests.common import ( - get_test_home_assistant, assert_setup_component) + async_mock_service, assert_setup_component) from tests.components.fan import common _LOGGER = logging.getLogger(__name__) @@ -26,129 +26,93 @@ _OSC_INPUT = 'input_select.osc' _DIRECTION_INPUT_SELECT = 'input_select.direction' -class TestTemplateFan: - """Test the Template light.""" +@pytest.fixture +def calls(hass): + """Track calls to a mock serivce.""" + return async_mock_service(hass, 'test', 'automation') - hass = None - calls = None - # pylint: disable=invalid-name - def setup_method(self, method): - """Set up.""" - self.hass = get_test_home_assistant() +# Configuration tests # +async def test_missing_optional_config(hass, calls): + """Test: missing optional template is ok.""" + with assert_setup_component(1, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': "{{ 'on' }}", - self.calls = [] - - @callback - def record_call(service): - """Track function calls..""" - self.calls.append(service) - - self.hass.services.register('test', 'automation', record_call) - - def teardown_method(self, method): - """Stop everything that was started.""" - self.hass.stop() - - # Configuration tests # - def test_missing_optional_config(self): - """Test: missing optional template is ok.""" - with assert_setup_component(1, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': "{{ 'on' }}", - - 'turn_on': { - 'service': 'script.fan_on' - }, - 'turn_off': { - 'service': 'script.fan_off' - } + 'turn_on': { + 'service': 'script.fan_on' + }, + 'turn_off': { + 'service': 'script.fan_off' } } } - }) + } + }) - self.hass.start() - self.hass.block_till_done() + await hass.async_start() + await hass.async_block_till_done() - self._verify(STATE_ON, None, None, None) + _verify(hass, STATE_ON, None, None, None) - def test_missing_value_template_config(self): - """Test: missing 'value_template' will fail.""" - with assert_setup_component(0, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'turn_on': { - 'service': 'script.fan_on' - }, - 'turn_off': { - 'service': 'script.fan_off' - } + +async def test_missing_value_template_config(hass, calls): + """Test: missing 'value_template' will fail.""" + with assert_setup_component(0, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'turn_on': { + 'service': 'script.fan_on' + }, + 'turn_off': { + 'service': 'script.fan_off' } } } - }) + } + }) - self.hass.start() - self.hass.block_till_done() + await hass.async_start() + await hass.async_block_till_done() - assert self.hass.states.all() == [] + assert hass.states.async_all() == [] - def test_missing_turn_on_config(self): - """Test: missing 'turn_on' will fail.""" - with assert_setup_component(0, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': "{{ 'on' }}", - 'turn_off': { - 'service': 'script.fan_off' - } + +async def test_missing_turn_on_config(hass, calls): + """Test: missing 'turn_on' will fail.""" + with assert_setup_component(0, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': "{{ 'on' }}", + 'turn_off': { + 'service': 'script.fan_off' } } } - }) + } + }) - self.hass.start() - self.hass.block_till_done() + await hass.async_start() + await hass.async_block_till_done() - assert self.hass.states.all() == [] + assert hass.states.async_all() == [] - def test_missing_turn_off_config(self): - """Test: missing 'turn_off' will fail.""" - with assert_setup_component(0, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': "{{ 'on' }}", - 'turn_on': { - 'service': 'script.fan_on' - } - } - } - } - }) - self.hass.start() - self.hass.block_till_done() - - assert self.hass.states.all() == [] - - def test_invalid_config(self): - """Test: missing 'turn_off' will fail.""" - with assert_setup_component(0, 'fan'): - assert setup.setup_component(self.hass, 'fan', { +async def test_missing_turn_off_config(hass, calls): + """Test: missing 'turn_off' will fail.""" + with assert_setup_component(0, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { 'platform': 'template', 'fans': { 'test_fan': { @@ -158,488 +122,527 @@ class TestTemplateFan: } } } - }) + } + }) - self.hass.start() - self.hass.block_till_done() + await hass.async_start() + await hass.async_block_till_done() - assert self.hass.states.all() == [] + assert hass.states.async_all() == [] - # End of configuration tests # - # Template tests # - def test_templates_with_entities(self): - """Test tempalates with values from other entities.""" - value_template = """ - {% if is_state('input_boolean.state', 'True') %} - {{ 'on' }} - {% else %} - {{ 'off' }} - {% endif %} - """ - - with assert_setup_component(1, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': value_template, - 'speed_template': - "{{ states('input_select.speed') }}", - 'oscillating_template': - "{{ states('input_select.osc') }}", - 'direction_template': - "{{ states('input_select.direction') }}", - 'turn_on': { - 'service': 'script.fan_on' - }, - 'turn_off': { - 'service': 'script.fan_off' - } - } - } - } - }) - - self.hass.start() - self.hass.block_till_done() - - self._verify(STATE_OFF, None, None, None) - - self.hass.states.set(_STATE_INPUT_BOOLEAN, True) - self.hass.states.set(_SPEED_INPUT_SELECT, SPEED_MEDIUM) - self.hass.states.set(_OSC_INPUT, 'True') - self.hass.states.set(_DIRECTION_INPUT_SELECT, DIRECTION_FORWARD) - self.hass.block_till_done() - - self._verify(STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD) - - def test_templates_with_valid_values(self): - """Test templates with valid values.""" - with assert_setup_component(1, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': - "{{ 'on' }}", - 'speed_template': - "{{ 'medium' }}", - 'oscillating_template': - "{{ 1 == 1 }}", - 'direction_template': - "{{ 'forward' }}", - - 'turn_on': { - 'service': 'script.fan_on' - }, - 'turn_off': { - 'service': 'script.fan_off' - } - } - } - } - }) - - self.hass.start() - self.hass.block_till_done() - - self._verify(STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD) - - def test_templates_invalid_values(self): - """Test templates with invalid values.""" - with assert_setup_component(1, 'fan'): - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': { - 'value_template': - "{{ 'abc' }}", - 'speed_template': - "{{ '0' }}", - 'oscillating_template': - "{{ 'xyz' }}", - 'direction_template': - "{{ 'right' }}", - - 'turn_on': { - 'service': 'script.fan_on' - }, - 'turn_off': { - 'service': 'script.fan_off' - } - } - } - } - }) - - self.hass.start() - self.hass.block_till_done() - - self._verify(STATE_OFF, None, None, None) - - # End of template tests # - - # Function tests # - def test_on_off(self): - """Test turn on and turn off.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON - self._verify(STATE_ON, None, None, None) - - # Turn off fan - common.turn_off(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_OFF - self._verify(STATE_OFF, None, None, None) - - def test_on_with_speed(self): - """Test turn on with speed.""" - self._register_components() - - # Turn on fan with high speed - common.turn_on(self.hass, _TEST_FAN, SPEED_HIGH) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH - self._verify(STATE_ON, SPEED_HIGH, None, None) - - def test_set_speed(self): - """Test set valid speed.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's speed to high - common.set_speed(self.hass, _TEST_FAN, SPEED_HIGH) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH - self._verify(STATE_ON, SPEED_HIGH, None, None) - - # Set fan's speed to medium - common.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_MEDIUM - self._verify(STATE_ON, SPEED_MEDIUM, None, None) - - def test_set_invalid_speed_from_initial_stage(self): - """Test set invalid speed when fan is in initial state.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's speed to 'invalid' - common.set_speed(self.hass, _TEST_FAN, 'invalid') - self.hass.block_till_done() - - # verify speed is unchanged - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == '' - self._verify(STATE_ON, None, None, None) - - def test_set_invalid_speed(self): - """Test set invalid speed when fan has valid speed.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's speed to high - common.set_speed(self.hass, _TEST_FAN, SPEED_HIGH) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH - self._verify(STATE_ON, SPEED_HIGH, None, None) - - # Set fan's speed to 'invalid' - common.set_speed(self.hass, _TEST_FAN, 'invalid') - self.hass.block_till_done() - - # verify speed is unchanged - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH - self._verify(STATE_ON, SPEED_HIGH, None, None) - - def test_custom_speed_list(self): - """Test set custom speed list.""" - self._register_components(['1', '2', '3']) - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's speed to '1' - common.set_speed(self.hass, _TEST_FAN, '1') - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == '1' - self._verify(STATE_ON, '1', None, None) - - # Set fan's speed to 'medium' which is invalid - common.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM) - self.hass.block_till_done() - - # verify that speed is unchanged - assert self.hass.states.get(_SPEED_INPUT_SELECT).state == '1' - self._verify(STATE_ON, '1', None, None) - - def test_set_osc(self): - """Test set oscillating.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's osc to True - common.oscillate(self.hass, _TEST_FAN, True) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_OSC_INPUT).state == 'True' - self._verify(STATE_ON, None, True, None) - - # Set fan's osc to False - common.oscillate(self.hass, _TEST_FAN, False) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_OSC_INPUT).state == 'False' - self._verify(STATE_ON, None, False, None) - - def test_set_invalid_osc_from_initial_state(self): - """Test set invalid oscillating when fan is in initial state.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's osc to 'invalid' - common.oscillate(self.hass, _TEST_FAN, 'invalid') - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_OSC_INPUT).state == '' - self._verify(STATE_ON, None, None, None) - - def test_set_invalid_osc(self): - """Test set invalid oscillating when fan has valid osc.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's osc to True - common.oscillate(self.hass, _TEST_FAN, True) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_OSC_INPUT).state == 'True' - self._verify(STATE_ON, None, True, None) - - # Set fan's osc to False - common.oscillate(self.hass, _TEST_FAN, None) - self.hass.block_till_done() - - # verify osc is unchanged - assert self.hass.states.get(_OSC_INPUT).state == 'True' - self._verify(STATE_ON, None, True, None) - - def test_set_direction(self): - """Test set valid direction.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's direction to forward - common.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_DIRECTION_INPUT_SELECT).state \ - == DIRECTION_FORWARD - self._verify(STATE_ON, None, None, DIRECTION_FORWARD) - - # Set fan's direction to reverse - common.set_direction(self.hass, _TEST_FAN, DIRECTION_REVERSE) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_DIRECTION_INPUT_SELECT).state \ - == DIRECTION_REVERSE - self._verify(STATE_ON, None, None, DIRECTION_REVERSE) - - def test_set_invalid_direction_from_initial_stage(self): - """Test set invalid direction when fan is in initial state.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's direction to 'invalid' - common.set_direction(self.hass, _TEST_FAN, 'invalid') - self.hass.block_till_done() - - # verify direction is unchanged - assert self.hass.states.get(_DIRECTION_INPUT_SELECT).state == '' - self._verify(STATE_ON, None, None, None) - - def test_set_invalid_direction(self): - """Test set invalid direction when fan has valid direction.""" - self._register_components() - - # Turn on fan - common.turn_on(self.hass, _TEST_FAN) - self.hass.block_till_done() - - # Set fan's direction to forward - common.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD) - self.hass.block_till_done() - - # verify - assert self.hass.states.get(_DIRECTION_INPUT_SELECT).state == \ - DIRECTION_FORWARD - self._verify(STATE_ON, None, None, DIRECTION_FORWARD) - - # Set fan's direction to 'invalid' - common.set_direction(self.hass, _TEST_FAN, 'invalid') - self.hass.block_till_done() - - # verify direction is unchanged - assert self.hass.states.get(_DIRECTION_INPUT_SELECT).state == \ - DIRECTION_FORWARD - self._verify(STATE_ON, None, None, DIRECTION_FORWARD) - - def _verify(self, expected_state, expected_speed, expected_oscillating, - expected_direction): - """Verify fan's state, speed and osc.""" - state = self.hass.states.get(_TEST_FAN) - attributes = state.attributes - assert state.state == expected_state - assert attributes.get(ATTR_SPEED, None) == expected_speed - assert attributes.get(ATTR_OSCILLATING, None) == expected_oscillating - assert attributes.get(ATTR_DIRECTION, None) == expected_direction - - def _register_components(self, speed_list=None): - """Register basic components for testing.""" - with assert_setup_component(1, 'input_boolean'): - assert setup.setup_component( - self.hass, - 'input_boolean', - {'input_boolean': {'state': None}} - ) - - with assert_setup_component(3, 'input_select'): - assert setup.setup_component(self.hass, 'input_select', { - 'input_select': { - 'speed': { - 'name': 'Speed', - 'options': ['', SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, - '1', '2', '3'] - }, - - 'osc': { - 'name': 'oscillating', - 'options': ['', 'True', 'False'] - }, - - 'direction': { - 'name': 'Direction', - 'options': ['', DIRECTION_FORWARD, DIRECTION_REVERSE] - }, - } - }) - - with assert_setup_component(1, 'fan'): - value_template = """ - {% if is_state('input_boolean.state', 'on') %} - {{ 'on' }} - {% else %} - {{ 'off' }} - {% endif %} - """ - - test_fan_config = { - 'value_template': value_template, - 'speed_template': - "{{ states('input_select.speed') }}", - 'oscillating_template': - "{{ states('input_select.osc') }}", - 'direction_template': - "{{ states('input_select.direction') }}", - - 'turn_on': { - 'service': 'input_boolean.turn_on', - 'entity_id': _STATE_INPUT_BOOLEAN - }, - 'turn_off': { - 'service': 'input_boolean.turn_off', - 'entity_id': _STATE_INPUT_BOOLEAN - }, - 'set_speed': { - 'service': 'input_select.select_option', - - 'data_template': { - 'entity_id': _SPEED_INPUT_SELECT, - 'option': '{{ speed }}' - } - }, - 'set_oscillating': { - 'service': 'input_select.select_option', - - 'data_template': { - 'entity_id': _OSC_INPUT, - 'option': '{{ oscillating }}' - } - }, - 'set_direction': { - 'service': 'input_select.select_option', - - 'data_template': { - 'entity_id': _DIRECTION_INPUT_SELECT, - 'option': '{{ direction }}' +async def test_invalid_config(hass, calls): + """Test: missing 'turn_off' will fail.""" + with assert_setup_component(0, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': "{{ 'on' }}", + 'turn_on': { + 'service': 'script.fan_on' } } } + }) - if speed_list: - test_fan_config['speeds'] = speed_list + await hass.async_start() + await hass.async_block_till_done() - assert setup.setup_component(self.hass, 'fan', { - 'fan': { - 'platform': 'template', - 'fans': { - 'test_fan': test_fan_config + assert hass.states.async_all() == [] + +# End of configuration tests # + + +# Template tests # +async def test_templates_with_entities(hass, calls): + """Test tempalates with values from other entities.""" + value_template = """ + {% if is_state('input_boolean.state', 'True') %} + {{ 'on' }} + {% else %} + {{ 'off' }} + {% endif %} + """ + + with assert_setup_component(1, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': value_template, + 'speed_template': + "{{ states('input_select.speed') }}", + 'oscillating_template': + "{{ states('input_select.osc') }}", + 'direction_template': + "{{ states('input_select.direction') }}", + 'turn_on': { + 'service': 'script.fan_on' + }, + 'turn_off': { + 'service': 'script.fan_off' + } } } - }) + } + }) - self.hass.start() - self.hass.block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + _verify(hass, STATE_OFF, None, None, None) + + hass.states.async_set(_STATE_INPUT_BOOLEAN, True) + hass.states.async_set(_SPEED_INPUT_SELECT, SPEED_MEDIUM) + hass.states.async_set(_OSC_INPUT, 'True') + hass.states.async_set(_DIRECTION_INPUT_SELECT, DIRECTION_FORWARD) + await hass.async_block_till_done() + + _verify(hass, STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD) + + +async def test_templates_with_valid_values(hass, calls): + """Test templates with valid values.""" + with assert_setup_component(1, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': + "{{ 'on' }}", + 'speed_template': + "{{ 'medium' }}", + 'oscillating_template': + "{{ 1 == 1 }}", + 'direction_template': + "{{ 'forward' }}", + + 'turn_on': { + 'service': 'script.fan_on' + }, + 'turn_off': { + 'service': 'script.fan_off' + } + } + } + } + }) + + await hass.async_start() + await hass.async_block_till_done() + + _verify(hass, STATE_ON, SPEED_MEDIUM, True, DIRECTION_FORWARD) + + +async def test_templates_invalid_values(hass, calls): + """Test templates with invalid values.""" + with assert_setup_component(1, 'fan'): + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': { + 'value_template': + "{{ 'abc' }}", + 'speed_template': + "{{ '0' }}", + 'oscillating_template': + "{{ 'xyz' }}", + 'direction_template': + "{{ 'right' }}", + + 'turn_on': { + 'service': 'script.fan_on' + }, + 'turn_off': { + 'service': 'script.fan_off' + } + } + } + } + }) + + await hass.async_start() + await hass.async_block_till_done() + + _verify(hass, STATE_OFF, None, None, None) + +# End of template tests # + + +# Function tests # +async def test_on_off(hass, calls): + """Test turn on and turn off.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON + _verify(hass, STATE_ON, None, None, None) + + # Turn off fan + common.async_turn_off(hass, _TEST_FAN) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_OFF + _verify(hass, STATE_OFF, None, None, None) + + +async def test_on_with_speed(hass, calls): + """Test turn on with speed.""" + await _register_components(hass) + + # Turn on fan with high speed + common.async_turn_on(hass, _TEST_FAN, SPEED_HIGH) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_STATE_INPUT_BOOLEAN).state == STATE_ON + assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH + _verify(hass, STATE_ON, SPEED_HIGH, None, None) + + +async def test_set_speed(hass, calls): + """Test set valid speed.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's speed to high + common.async_set_speed(hass, _TEST_FAN, SPEED_HIGH) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH + _verify(hass, STATE_ON, SPEED_HIGH, None, None) + + # Set fan's speed to medium + common.async_set_speed(hass, _TEST_FAN, SPEED_MEDIUM) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_MEDIUM + _verify(hass, STATE_ON, SPEED_MEDIUM, None, None) + + +async def test_set_invalid_speed_from_initial_stage(hass, calls): + """Test set invalid speed when fan is in initial state.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's speed to 'invalid' + common.async_set_speed(hass, _TEST_FAN, 'invalid') + await hass.async_block_till_done() + + # verify speed is unchanged + assert hass.states.get(_SPEED_INPUT_SELECT).state == '' + _verify(hass, STATE_ON, None, None, None) + + +async def test_set_invalid_speed(hass, calls): + """Test set invalid speed when fan has valid speed.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's speed to high + common.async_set_speed(hass, _TEST_FAN, SPEED_HIGH) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH + _verify(hass, STATE_ON, SPEED_HIGH, None, None) + + # Set fan's speed to 'invalid' + common.async_set_speed(hass, _TEST_FAN, 'invalid') + await hass.async_block_till_done() + + # verify speed is unchanged + assert hass.states.get(_SPEED_INPUT_SELECT).state == SPEED_HIGH + _verify(hass, STATE_ON, SPEED_HIGH, None, None) + + +async def test_custom_speed_list(hass, calls): + """Test set custom speed list.""" + await _register_components(hass, ['1', '2', '3']) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's speed to '1' + common.async_set_speed(hass, _TEST_FAN, '1') + await hass.async_block_till_done() + + # verify + assert hass.states.get(_SPEED_INPUT_SELECT).state == '1' + _verify(hass, STATE_ON, '1', None, None) + + # Set fan's speed to 'medium' which is invalid + common.async_set_speed(hass, _TEST_FAN, SPEED_MEDIUM) + await hass.async_block_till_done() + + # verify that speed is unchanged + assert hass.states.get(_SPEED_INPUT_SELECT).state == '1' + _verify(hass, STATE_ON, '1', None, None) + + +async def test_set_osc(hass, calls): + """Test set oscillating.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's osc to True + common.async_oscillate(hass, _TEST_FAN, True) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_OSC_INPUT).state == 'True' + _verify(hass, STATE_ON, None, True, None) + + # Set fan's osc to False + common.async_oscillate(hass, _TEST_FAN, False) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_OSC_INPUT).state == 'False' + _verify(hass, STATE_ON, None, False, None) + + +async def test_set_invalid_osc_from_initial_state(hass, calls): + """Test set invalid oscillating when fan is in initial state.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's osc to 'invalid' + common.async_oscillate(hass, _TEST_FAN, 'invalid') + await hass.async_block_till_done() + + # verify + assert hass.states.get(_OSC_INPUT).state == '' + _verify(hass, STATE_ON, None, None, None) + + +async def test_set_invalid_osc(hass, calls): + """Test set invalid oscillating when fan has valid osc.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's osc to True + common.async_oscillate(hass, _TEST_FAN, True) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_OSC_INPUT).state == 'True' + _verify(hass, STATE_ON, None, True, None) + + # Set fan's osc to False + common.async_oscillate(hass, _TEST_FAN, None) + await hass.async_block_till_done() + + # verify osc is unchanged + assert hass.states.get(_OSC_INPUT).state == 'True' + _verify(hass, STATE_ON, None, True, None) + + +async def test_set_direction(hass, calls): + """Test set valid direction.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's direction to forward + common.async_set_direction(hass, _TEST_FAN, DIRECTION_FORWARD) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_DIRECTION_INPUT_SELECT).state \ + == DIRECTION_FORWARD + _verify(hass, STATE_ON, None, None, DIRECTION_FORWARD) + + # Set fan's direction to reverse + common.async_set_direction(hass, _TEST_FAN, DIRECTION_REVERSE) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_DIRECTION_INPUT_SELECT).state \ + == DIRECTION_REVERSE + _verify(hass, STATE_ON, None, None, DIRECTION_REVERSE) + + +async def test_set_invalid_direction_from_initial_stage(hass, calls): + """Test set invalid direction when fan is in initial state.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's direction to 'invalid' + common.async_set_direction(hass, _TEST_FAN, 'invalid') + await hass.async_block_till_done() + + # verify direction is unchanged + assert hass.states.get(_DIRECTION_INPUT_SELECT).state == '' + _verify(hass, STATE_ON, None, None, None) + + +async def test_set_invalid_direction(hass, calls): + """Test set invalid direction when fan has valid direction.""" + await _register_components(hass) + + # Turn on fan + common.async_turn_on(hass, _TEST_FAN) + await hass.async_block_till_done() + + # Set fan's direction to forward + common.async_set_direction(hass, _TEST_FAN, DIRECTION_FORWARD) + await hass.async_block_till_done() + + # verify + assert hass.states.get(_DIRECTION_INPUT_SELECT).state == \ + DIRECTION_FORWARD + _verify(hass, STATE_ON, None, None, DIRECTION_FORWARD) + + # Set fan's direction to 'invalid' + common.async_set_direction(hass, _TEST_FAN, 'invalid') + await hass.async_block_till_done() + + # verify direction is unchanged + assert hass.states.get(_DIRECTION_INPUT_SELECT).state == \ + DIRECTION_FORWARD + _verify(hass, STATE_ON, None, None, DIRECTION_FORWARD) + + +def _verify(hass, expected_state, expected_speed, expected_oscillating, + expected_direction): + """Verify fan's state, speed and osc.""" + state = hass.states.get(_TEST_FAN) + attributes = state.attributes + assert state.state == expected_state + assert attributes.get(ATTR_SPEED, None) == expected_speed + assert attributes.get(ATTR_OSCILLATING, None) == expected_oscillating + assert attributes.get(ATTR_DIRECTION, None) == expected_direction + + +async def _register_components(hass, speed_list=None): + """Register basic components for testing.""" + with assert_setup_component(1, 'input_boolean'): + assert await setup.async_setup_component( + hass, + 'input_boolean', + {'input_boolean': {'state': None}} + ) + + with assert_setup_component(3, 'input_select'): + assert await setup.async_setup_component(hass, 'input_select', { + 'input_select': { + 'speed': { + 'name': 'Speed', + 'options': ['', SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, + '1', '2', '3'] + }, + + 'osc': { + 'name': 'oscillating', + 'options': ['', 'True', 'False'] + }, + + 'direction': { + 'name': 'Direction', + 'options': ['', DIRECTION_FORWARD, DIRECTION_REVERSE] + }, + } + }) + + with assert_setup_component(1, 'fan'): + value_template = """ + {% if is_state('input_boolean.state', 'on') %} + {{ 'on' }} + {% else %} + {{ 'off' }} + {% endif %} + """ + + test_fan_config = { + 'value_template': value_template, + 'speed_template': + "{{ states('input_select.speed') }}", + 'oscillating_template': + "{{ states('input_select.osc') }}", + 'direction_template': + "{{ states('input_select.direction') }}", + + 'turn_on': { + 'service': 'input_boolean.turn_on', + 'entity_id': _STATE_INPUT_BOOLEAN + }, + 'turn_off': { + 'service': 'input_boolean.turn_off', + 'entity_id': _STATE_INPUT_BOOLEAN + }, + 'set_speed': { + 'service': 'input_select.select_option', + + 'data_template': { + 'entity_id': _SPEED_INPUT_SELECT, + 'option': '{{ speed }}' + } + }, + 'set_oscillating': { + 'service': 'input_select.select_option', + + 'data_template': { + 'entity_id': _OSC_INPUT, + 'option': '{{ oscillating }}' + } + }, + 'set_direction': { + 'service': 'input_select.select_option', + + 'data_template': { + 'entity_id': _DIRECTION_INPUT_SELECT, + 'option': '{{ direction }}' + } + } + } + + if speed_list: + test_fan_config['speeds'] = speed_list + + assert await setup.async_setup_component(hass, 'fan', { + 'fan': { + 'platform': 'template', + 'fans': { + 'test_fan': test_fan_config + } + } + }) + + await hass.async_start() + await hass.async_block_till_done() From 92c536ec0ee12917dd9a1203d6ade4b90ca02bdc Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 2 Nov 2018 21:09:16 +0100 Subject: [PATCH 195/230] Don't create a switch for POE device if said device is Cloud key (#18117) --- homeassistant/components/switch/unifi.py | 3 ++- tests/components/switch/test_unifi.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/switch/unifi.py b/homeassistant/components/switch/unifi.py index dc02068c4a8..c1f8a96946f 100644 --- a/homeassistant/components/switch/unifi.py +++ b/homeassistant/components/switch/unifi.py @@ -129,7 +129,8 @@ async def async_update_items(controller, async_add_entities, # Network device with active POE if not client.is_wired or client.sw_mac not in devices or \ not devices[client.sw_mac].ports[client.sw_port].port_poe or \ - not devices[client.sw_mac].ports[client.sw_port].poe_enable: + not devices[client.sw_mac].ports[client.sw_port].poe_enable or \ + controller.mac == client.mac: continue # Multiple POE-devices on same port means non UniFi POE driven switch diff --git a/tests/components/switch/test_unifi.py b/tests/components/switch/test_unifi.py index f50bda34883..0513b5724db 100644 --- a/tests/components/switch/test_unifi.py +++ b/tests/components/switch/test_unifi.py @@ -64,6 +64,18 @@ CLIENT_4 = { 'wired-rx_bytes': 1234000000, 'wired-tx_bytes': 5678000000 } +CLOUDKEY = { + 'hostname': 'client_1', + 'ip': 'mock-host', + 'is_wired': True, + 'mac': '10:00:00:00:00:01', + 'name': 'Cloud key', + 'oui': 'Producer', + 'sw_mac': '00:00:00:00:01:01', + 'sw_port': 1, + 'wired-rx_bytes': 1234000000, + 'wired-tx_bytes': 5678000000 +} POE_SWITCH_CLIENTS = [ { 'hostname': 'client_1', @@ -179,6 +191,7 @@ def mock_controller(hass): api=Mock(), spec=unifi.UniFiController ) + controller.mac = '10:00:00:00:00:01' controller.mock_requests = [] controller.mock_client_responses = deque() @@ -230,6 +243,17 @@ async def test_no_clients(hass, mock_controller): assert not hass.states.async_all() +async def test_controller_not_client(hass, mock_controller): + """Test that the controller doesn't become a switch.""" + mock_controller.mock_client_responses.append([CLOUDKEY]) + mock_controller.mock_device_responses.append([DEVICE_1]) + await setup_controller(hass, mock_controller) + assert len(mock_controller.mock_requests) == 2 + assert not hass.states.async_all() + cloudkey = hass.states.get('switch.cloud_key') + assert cloudkey is None + + async def test_switches(hass, mock_controller): """Test the update_items function with some lights.""" mock_controller.mock_client_responses.append([CLIENT_1, CLIENT_4]) From 45484ba5697aa058a329aaf661b00e86487afb00 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 2 Nov 2018 16:57:03 -0400 Subject: [PATCH 196/230] TensorFlow image_processing component (#17795) * initial tensorflow image_processing component * linting fixes * make displayed attribute a summary of objects * fix missed merge conflict and add warning supression back in for CPU type * restructure tensorflow component to install on the fly, remove from Docker * add both matches and summary as attributes * address review comments * do not use deps folder as default, as it should only be managed by HA. Update to have tensorflow in root config directory --- .coveragerc | 1 + .../components/image_processing/tensorflow.py | 347 ++++++++++++++++++ requirements_all.txt | 8 + requirements_test_all.txt | 1 + 4 files changed, 357 insertions(+) create mode 100644 homeassistant/components/image_processing/tensorflow.py diff --git a/.coveragerc b/.coveragerc index 54927f74c45..b64699f685f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -510,6 +510,7 @@ omit = homeassistant/components/image_processing/dlib_face_detect.py homeassistant/components/image_processing/dlib_face_identify.py homeassistant/components/image_processing/seven_segments.py + homeassistant/components/image_processing/tensorflow.py homeassistant/components/keyboard_remote.py homeassistant/components/keyboard.py homeassistant/components/light/avion.py diff --git a/homeassistant/components/image_processing/tensorflow.py b/homeassistant/components/image_processing/tensorflow.py new file mode 100644 index 00000000000..f333aa1767c --- /dev/null +++ b/homeassistant/components/image_processing/tensorflow.py @@ -0,0 +1,347 @@ +""" +Component that performs TensorFlow classification on images. + +For a quick start, pick a pre-trained COCO model from: +https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/image_processing.tensorflow/ +""" +import logging +import sys +import os + +import voluptuous as vol + +from homeassistant.components.image_processing import ( + CONF_CONFIDENCE, CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE, PLATFORM_SCHEMA, + ImageProcessingEntity) +from homeassistant.core import split_entity_id +from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['numpy==1.15.3', 'pillow==5.2.0', + 'protobuf==3.6.1', 'tensorflow==1.11.0'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_MATCHES = 'matches' +ATTR_SUMMARY = 'summary' +ATTR_TOTAL_MATCHES = 'total_matches' + +CONF_FILE_OUT = 'file_out' +CONF_MODEL = 'model' +CONF_GRAPH = 'graph' +CONF_LABELS = 'labels' +CONF_MODEL_DIR = 'model_dir' +CONF_CATEGORIES = 'categories' +CONF_CATEGORY = 'category' +CONF_AREA = 'area' +CONF_TOP = 'top' +CONF_LEFT = 'left' +CONF_BOTTOM = 'bottom' +CONF_RIGHT = 'right' + +AREA_SCHEMA = vol.Schema({ + vol.Optional(CONF_TOP, default=0): cv.small_float, + vol.Optional(CONF_LEFT, default=0): cv.small_float, + vol.Optional(CONF_BOTTOM, default=1): cv.small_float, + vol.Optional(CONF_RIGHT, default=1): cv.small_float +}) + +CATEGORY_SCHEMA = vol.Schema({ + vol.Required(CONF_CATEGORY): cv.string, + vol.Optional(CONF_AREA): AREA_SCHEMA +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_FILE_OUT, default=[]): + vol.All(cv.ensure_list, [cv.template]), + vol.Required(CONF_MODEL): vol.Schema({ + vol.Required(CONF_GRAPH): cv.isfile, + vol.Optional(CONF_LABELS): cv.isfile, + vol.Optional(CONF_MODEL_DIR): cv.isdir, + vol.Optional(CONF_AREA): AREA_SCHEMA, + vol.Optional(CONF_CATEGORIES, default=[]): + vol.All(cv.ensure_list, [vol.Any( + cv.string, + CATEGORY_SCHEMA + )]) + }) +}) + + +def draw_box(draw, box, img_width, + img_height, text='', color=(255, 255, 0)): + """Draw bounding box on image.""" + ymin, xmin, ymax, xmax = box + (left, right, top, bottom) = (xmin * img_width, xmax * img_width, + ymin * img_height, ymax * img_height) + draw.line([(left, top), (left, bottom), (right, bottom), + (right, top), (left, top)], width=5, fill=color) + if text: + draw.text((left, abs(top-15)), text, fill=color) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the TensorFlow image processing platform.""" + model_config = config.get(CONF_MODEL) + model_dir = model_config.get(CONF_MODEL_DIR) \ + or hass.config.path('tensorflow') + labels = model_config.get(CONF_LABELS) \ + or hass.config.path('tensorflow', 'object_detection', + 'data', 'mscoco_label_map.pbtxt') + + # Make sure locations exist + if not os.path.isdir(model_dir) or not os.path.exists(labels): + _LOGGER.error("Unable to locate tensorflow models or label map.") + return + + # append custom model path to sys.path + sys.path.append(model_dir) + + try: + # Verify that the TensorFlow Object Detection API is pre-installed + # pylint: disable=unused-import,unused-variable + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + import tensorflow as tf # noqa + from object_detection.utils import label_map_util # noqa + except ImportError: + # pylint: disable=line-too-long + _LOGGER.error( + "No TensorFlow Object Detection library found! Install or compile " + "for your system following instructions here: " + "https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/installation.md") # noqa + return + + try: + # Display warning that PIL will be used if no OpenCV is found. + # pylint: disable=unused-import,unused-variable + import cv2 # noqa + except ImportError: + _LOGGER.warning("No OpenCV library found. " + "TensorFlow will process image with " + "PIL at reduced resolution.") + + # setup tensorflow graph, session, and label map to pass to processor + # pylint: disable=no-member + detection_graph = tf.Graph() + with detection_graph.as_default(): + od_graph_def = tf.GraphDef() + with tf.gfile.GFile(model_config.get(CONF_GRAPH), 'rb') as fid: + serialized_graph = fid.read() + od_graph_def.ParseFromString(serialized_graph) + tf.import_graph_def(od_graph_def, name='') + + session = tf.Session(graph=detection_graph) + label_map = label_map_util.load_labelmap(labels) + categories = label_map_util.convert_label_map_to_categories( + label_map, max_num_classes=90, use_display_name=True) + category_index = label_map_util.create_category_index(categories) + + entities = [] + + for camera in config[CONF_SOURCE]: + entities.append(TensorFlowImageProcessor( + hass, camera[CONF_ENTITY_ID], camera.get(CONF_NAME), + session, detection_graph, category_index, config)) + + add_entities(entities) + + +class TensorFlowImageProcessor(ImageProcessingEntity): + """Representation of an TensorFlow image processor.""" + + def __init__(self, hass, camera_entity, name, session, detection_graph, + category_index, config): + """Initialize the TensorFlow entity.""" + model_config = config.get(CONF_MODEL) + self.hass = hass + self._camera_entity = camera_entity + if name: + self._name = name + else: + self._name = "TensorFlow {0}".format( + split_entity_id(camera_entity)[1]) + self._session = session + self._graph = detection_graph + self._category_index = category_index + self._min_confidence = config.get(CONF_CONFIDENCE) + self._file_out = config.get(CONF_FILE_OUT) + + # handle categories and specific detection areas + categories = model_config.get(CONF_CATEGORIES) + self._include_categories = [] + self._category_areas = {} + for category in categories: + if isinstance(category, dict): + category_name = category.get(CONF_CATEGORY) + category_area = category.get(CONF_AREA) + self._include_categories.append(category_name) + self._category_areas[category_name] = [0, 0, 1, 1] + if category_area: + self._category_areas[category_name] = [ + category_area.get(CONF_TOP), + category_area.get(CONF_LEFT), + category_area.get(CONF_BOTTOM), + category_area.get(CONF_RIGHT) + ] + else: + self._include_categories.append(category) + self._category_areas[category] = [0, 0, 1, 1] + + # Handle global detection area + self._area = [0, 0, 1, 1] + area_config = model_config.get(CONF_AREA) + if area_config: + self._area = [ + area_config.get(CONF_TOP), + area_config.get(CONF_LEFT), + area_config.get(CONF_BOTTOM), + area_config.get(CONF_RIGHT) + ] + + template.attach(hass, self._file_out) + + self._matches = {} + self._total_matches = 0 + self._last_image = None + + @property + def camera_entity(self): + """Return camera entity id from process pictures.""" + return self._camera_entity + + @property + def name(self): + """Return the name of the image processor.""" + return self._name + + @property + def state(self): + """Return the state of the entity.""" + return self._total_matches + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + ATTR_MATCHES: self._matches, + ATTR_SUMMARY: {category: len(values) + for category, values in self._matches.items()}, + ATTR_TOTAL_MATCHES: self._total_matches + } + + def _save_image(self, image, matches, paths): + from PIL import Image, ImageDraw + import io + img = Image.open(io.BytesIO(bytearray(image))).convert('RGB') + img_width, img_height = img.size + draw = ImageDraw.Draw(img) + + # Draw custom global region/area + if self._area != [0, 0, 1, 1]: + draw_box(draw, self._area, + img_width, img_height, + "Detection Area", (0, 255, 255)) + + for category, values in matches.items(): + # Draw custom category regions/areas + if self._category_areas[category] != [0, 0, 1, 1]: + label = "{} Detection Area".format(category.capitalize()) + draw_box(draw, self._category_areas[category], img_width, + img_height, label, (0, 255, 0)) + + # Draw detected objects + for instance in values: + label = "{0} {1:.1f}%".format(category, instance['score']) + draw_box(draw, instance['box'], + img_width, img_height, + label, (255, 255, 0)) + + for path in paths: + _LOGGER.info("Saving results image to %s", path) + img.save(path) + + def process_image(self, image): + """Process the image.""" + import numpy as np + + try: + import cv2 # pylint: disable=import-error + img = cv2.imdecode( + np.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) + inp = img[:, :, [2, 1, 0]] # BGR->RGB + inp_expanded = inp.reshape(1, inp.shape[0], inp.shape[1], 3) + except ImportError: + from PIL import Image + import io + img = Image.open(io.BytesIO(bytearray(image))).convert('RGB') + img.thumbnail((460, 460), Image.ANTIALIAS) + img_width, img_height = img.size + inp = np.array(img.getdata()).reshape( + (img_height, img_width, 3)).astype(np.uint8) + inp_expanded = np.expand_dims(inp, axis=0) + + image_tensor = self._graph.get_tensor_by_name('image_tensor:0') + boxes = self._graph.get_tensor_by_name('detection_boxes:0') + scores = self._graph.get_tensor_by_name('detection_scores:0') + classes = self._graph.get_tensor_by_name('detection_classes:0') + boxes, scores, classes = self._session.run( + [boxes, scores, classes], + feed_dict={image_tensor: inp_expanded}) + boxes, scores, classes = map(np.squeeze, [boxes, scores, classes]) + classes = classes.astype(int) + + matches = {} + total_matches = 0 + for box, score, obj_class in zip(boxes, scores, classes): + score = score * 100 + boxes = box.tolist() + + # Exclude matches below min confidence value + if score < self._min_confidence: + continue + + # Exclude matches outside global area definition + if (boxes[0] < self._area[0] or boxes[1] < self._area[1] + or boxes[2] > self._area[2] or boxes[3] > self._area[3]): + continue + + category = self._category_index[obj_class]['name'] + + # Exclude unlisted categories + if (self._include_categories + and category not in self._include_categories): + continue + + # Exclude matches outside category specific area definition + if (self._category_areas + and (boxes[0] < self._category_areas[category][0] + or boxes[1] < self._category_areas[category][1] + or boxes[2] > self._category_areas[category][2] + or boxes[3] > self._category_areas[category][3])): + continue + + # If we got here, we should include it + if category not in matches.keys(): + matches[category] = [] + matches[category].append({ + 'score': float(score), + 'box': boxes + }) + total_matches += 1 + + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append(path_template.render( + camera_entity=self._camera_entity)) + else: + paths.append(path_template) + self._save_image(image, matches, paths) + + self._matches = matches + self._total_matches = total_matches diff --git a/requirements_all.txt b/requirements_all.txt index 42aa2c08cc8..4d4a9020b98 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -669,6 +669,7 @@ nuheat==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv +# homeassistant.components.image_processing.tensorflow # homeassistant.components.sensor.pollen numpy==1.15.3 @@ -722,6 +723,7 @@ piglow==1.2.4 pilight==0.1.1 # homeassistant.components.camera.proxy +# homeassistant.components.image_processing.tensorflow pillow==5.2.0 # homeassistant.components.dominos @@ -747,6 +749,9 @@ proliphix==0.4.1 # homeassistant.components.prometheus prometheus_client==0.2.0 +# homeassistant.components.image_processing.tensorflow +protobuf==3.6.1 + # homeassistant.components.sensor.systemmonitor psutil==5.4.8 @@ -1464,6 +1469,9 @@ temescal==0.1 # homeassistant.components.sensor.temper temperusb==1.5.3 +# homeassistant.components.image_processing.tensorflow +tensorflow==1.11.0 + # homeassistant.components.tesla teslajsonpy==0.0.23 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6059917892c..8bc7f98a8ea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -118,6 +118,7 @@ mficlient==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv +# homeassistant.components.image_processing.tensorflow # homeassistant.components.sensor.pollen numpy==1.15.3 From 03d94df3cd8d72cbcdaff7e1cfd4fe30a22bc640 Mon Sep 17 00:00:00 2001 From: mtl010957 Date: Fri, 2 Nov 2018 17:48:17 -0400 Subject: [PATCH 197/230] Fix DTE Energy Bridge V2 scaling issue. (#18124) (#18129) --- homeassistant/components/sensor/dte_energy_bridge.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/dte_energy_bridge.py b/homeassistant/components/sensor/dte_energy_bridge.py index 629b21e4944..5de2fc4a4ee 100644 --- a/homeassistant/components/sensor/dte_energy_bridge.py +++ b/homeassistant/components/sensor/dte_energy_bridge.py @@ -109,4 +109,10 @@ class DteEnergyBridgeSensor(Entity): # A workaround for a bug in the DTE energy bridge. # The returned value can randomly be in W or kW. Checking for a # a decimal seems to be a reliable way to determine the units. - self._state = val if '.' in response_split[0] else val / 1000 + # Limiting to version 1 because version 2 apparently always returns + # values in the format 000000.000 kW, but the scaling is Watts + # NOT kWatts + if self._version == 1 and '.' in response_split[0]: + self._state = val + else: + self._state = val / 1000 From 6a5f9faa33ed4511b2ed99919f012c5563bf9783 Mon Sep 17 00:00:00 2001 From: Pascal de Ladurantaye Date: Fri, 2 Nov 2018 23:41:26 -0400 Subject: [PATCH 198/230] Add optional ttl config to route53 component (#18135) * Add optional ttl config to route53 component * linting :) --- homeassistant/components/route53.py | 32 ++++++++++++++++++++++++----- homeassistant/const.py | 1 + 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/route53.py b/homeassistant/components/route53.py index f88a15b72b8..4c35983feed 100644 --- a/homeassistant/components/route53.py +++ b/homeassistant/components/route53.py @@ -6,10 +6,11 @@ https://home-assistant.io/components/route53/ """ from datetime import timedelta import logging +from typing import List import voluptuous as vol -from homeassistant.const import CONF_DOMAIN, CONF_ZONE +from homeassistant.const import CONF_DOMAIN, CONF_TTL, CONF_ZONE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_time_interval @@ -24,6 +25,7 @@ CONF_RECORDS = 'records' DOMAIN = 'route53' INTERVAL = timedelta(minutes=60) +DEFAULT_TTL = 300 CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -32,6 +34,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_RECORDS): vol.All(cv.ensure_list, [cv.string]), vol.Required(CONF_SECRET_ACCESS_KEY): cv.string, vol.Required(CONF_ZONE): cv.string, + vol.Optional(CONF_TTL, default=DEFAULT_TTL): cv.positive_int, }) }, extra=vol.ALLOW_EXTRA) @@ -43,16 +46,29 @@ def setup(hass, config): zone = config[DOMAIN][CONF_ZONE] aws_access_key_id = config[DOMAIN][CONF_ACCESS_KEY_ID] aws_secret_access_key = config[DOMAIN][CONF_SECRET_ACCESS_KEY] + ttl = config[DOMAIN][CONF_TTL] def update_records_interval(now): """Set up recurring update.""" _update_route53( - aws_access_key_id, aws_secret_access_key, zone, domain, records) + aws_access_key_id, + aws_secret_access_key, + zone, + domain, + records, + ttl + ) def update_records_service(now): """Set up service for manual trigger.""" _update_route53( - aws_access_key_id, aws_secret_access_key, zone, domain, records) + aws_access_key_id, + aws_secret_access_key, + zone, + domain, + records, + ttl + ) track_time_interval(hass, update_records_interval, INTERVAL) @@ -61,7 +77,13 @@ def setup(hass, config): def _update_route53( - aws_access_key_id, aws_secret_access_key, zone, domain, records): + aws_access_key_id: str, + aws_secret_access_key: str, + zone: str, + domain: str, + records: List[str], + ttl: int, +): import boto3 from ipify import get_ip from ipify import exceptions @@ -95,7 +117,7 @@ def _update_route53( 'ResourceRecordSet': { 'Name': '{}.{}'.format(record, domain), 'Type': 'A', - 'TTL': 300, + 'TTL': ttl, 'ResourceRecords': [ {'Value': ipaddress}, ], diff --git a/homeassistant/const.py b/homeassistant/const.py index 9dbb4d2e53c..ffbba575a14 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -140,6 +140,7 @@ CONF_TIME_ZONE = 'time_zone' CONF_TIMEOUT = 'timeout' CONF_TOKEN = 'token' CONF_TRIGGER_TIME = 'trigger_time' +CONF_TTL = 'ttl' CONF_TYPE = 'type' CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement' CONF_UNIT_SYSTEM = 'unit_system' From 5c9986287856354e6ecd98dcb85a448f3de5d9a9 Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Fri, 2 Nov 2018 23:42:24 -0400 Subject: [PATCH 199/230] Allow disabling WeMo Discovery (#18079) * WeMo - Disable Discovery - New config option * WeMo - Disable Discovery - Change log level to debug * WeMo - Disable Discovery - Change logging level --- homeassistant/components/wemo.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 1da37ac38b0..55e45bc210a 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -58,12 +58,17 @@ def coerce_host_port(value): CONF_STATIC = 'static' +CONF_DISABLE_DISCOVERY = 'disable_discovery' + +DEFAULT_DISABLE_DISCOVERY = False CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_STATIC, default=[]): vol.Schema([ vol.All(cv.string, coerce_host_port) - ]) + ]), + vol.Optional(CONF_DISABLE_DISCOVERY, + default=DEFAULT_DISABLE_DISCOVERY): cv.boolean }), }, extra=vol.ALLOW_EXTRA) @@ -78,7 +83,7 @@ def setup(hass, config): def stop_wemo(event): """Shutdown Wemo subscriptions and subscription thread on exit.""" - _LOGGER.info("Shutting down subscriptions.") + _LOGGER.debug("Shutting down subscriptions.") SUBSCRIPTION_REGISTRY.stop() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo) @@ -136,13 +141,16 @@ def setup(hass, config): devices.append((url, device)) - _LOGGER.info("Scanning for WeMo devices.") - devices.extend( - (setup_url_for_device(device), device) - for device in pywemo.discover_devices()) + disable_discovery = config.get(DOMAIN, {}).get(CONF_DISABLE_DISCOVERY) + + if not disable_discovery: + _LOGGER.debug("Scanning for WeMo devices.") + devices.extend( + (setup_url_for_device(device), device) + for device in pywemo.discover_devices()) for url, device in devices: - _LOGGER.info('Adding wemo at %s:%i', device.host, device.port) + _LOGGER.debug('Adding wemo at %s:%i', device.host, device.port) discovery_info = { 'model_name': device.model_name, From 7caddd48cd2083e26d5b3960728404519595a285 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 3 Nov 2018 10:24:02 +0100 Subject: [PATCH 200/230] Fix typos and update docstrings (#18137) --- homeassistant/components/lovelace/__init__.py | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 540fb601a90..a8cde6a2b93 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,16 +1,22 @@ -"""Lovelace UI.""" -import logging -import uuid +""" +Support for the Lovelace UI. + +For more details about this component, please refer to the documentation +at https://www.home-assistant.io/lovelace/ +""" from functools import wraps +import logging from typing import Dict, List, Union +import uuid import voluptuous as vol -import homeassistant.util.ruamel_yaml as yaml from homeassistant.components import websocket_api from homeassistant.exceptions import HomeAssistantError +import homeassistant.util.ruamel_yaml as yaml _LOGGER = logging.getLogger(__name__) + DOMAIN = 'lovelace' LOVELACE_CONFIG_FILE = 'ui-lovelace.yaml' @@ -36,8 +42,8 @@ WS_TYPE_MOVE_VIEW = 'lovelace/config/view/move' WS_TYPE_DELETE_VIEW = 'lovelace/config/view/delete' SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI, - OLD_WS_TYPE_GET_LOVELACE_UI), + vol.Required('type'): + vol.Any(WS_TYPE_GET_LOVELACE_UI, OLD_WS_TYPE_GET_LOVELACE_UI), }) SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ @@ -47,16 +53,16 @@ SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_CARD, vol.Required('card_id'): str, - vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, - FORMAT_YAML), + vol.Optional('format', default=FORMAT_YAML): + vol.Any(FORMAT_JSON, FORMAT_YAML), }) SCHEMA_UPDATE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_UPDATE_CARD, vol.Required('card_id'): str, vol.Required('card_config'): vol.Any(str, Dict), - vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, - FORMAT_YAML), + vol.Optional('format', default=FORMAT_YAML): + vol.Any(FORMAT_JSON, FORMAT_YAML), }) SCHEMA_ADD_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ @@ -64,8 +70,8 @@ SCHEMA_ADD_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('view_id'): str, vol.Required('card_config'): vol.Any(str, Dict), vol.Optional('position'): int, - vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON, - FORMAT_YAML), + vol.Optional('format', default=FORMAT_YAML): + vol.Any(FORMAT_JSON, FORMAT_YAML), }) SCHEMA_MOVE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ @@ -143,12 +149,12 @@ def migrate_config(fname: str) -> None: view_id = str(view.get('id', '')) if not view_id: updated = True - view.insert(0, 'id', index, - comment="Automatically created id") + view.insert(0, 'id', index, comment="Automatically created id") else: if view_id in seen_view_ids: raise DuplicateIdError( - 'ID `{}` has multiple occurances in views'.format(view_id)) + 'ID `{}` has multiple occurrences in views'.format( + view_id)) seen_view_ids.add(view_id) for card in view.get('cards', []): card_id = str(card.get('id', '')) @@ -159,7 +165,7 @@ def migrate_config(fname: str) -> None: else: if card_id in seen_card_ids: raise DuplicateIdError( - 'ID `{}` has multiple occurances in cards' + 'ID `{}` has multiple occurrences in cards' .format(card_id)) seen_card_ids.add(card_id) index += 1 @@ -229,8 +235,8 @@ def add_card(fname: str, view_id: str, card_config: str, def move_card(fname: str, card_id: str, position: int = None) -> None: """Move a card to a different position.""" if position is None: - raise HomeAssistantError('Position is required if view is not\ - specified.') + raise HomeAssistantError( + 'Position is required if view is not specified.') config = yaml.load_yaml(fname, True) for view in config.get('views', []): for card in view.get('cards', []): @@ -386,40 +392,34 @@ async def async_setup(hass, config): SCHEMA_GET_LOVELACE_UI) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_CARD, websocket_lovelace_get_card, - SCHEMA_GET_CARD) + WS_TYPE_GET_CARD, websocket_lovelace_get_card, SCHEMA_GET_CARD) hass.components.websocket_api.async_register_command( WS_TYPE_UPDATE_CARD, websocket_lovelace_update_card, SCHEMA_UPDATE_CARD) hass.components.websocket_api.async_register_command( - WS_TYPE_ADD_CARD, websocket_lovelace_add_card, - SCHEMA_ADD_CARD) + WS_TYPE_ADD_CARD, websocket_lovelace_add_card, SCHEMA_ADD_CARD) hass.components.websocket_api.async_register_command( - WS_TYPE_MOVE_CARD, websocket_lovelace_move_card, - SCHEMA_MOVE_CARD) + WS_TYPE_MOVE_CARD, websocket_lovelace_move_card, SCHEMA_MOVE_CARD) hass.components.websocket_api.async_register_command( WS_TYPE_DELETE_CARD, websocket_lovelace_delete_card, SCHEMA_DELETE_CARD) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_VIEW, websocket_lovelace_get_view, - SCHEMA_GET_VIEW) + WS_TYPE_GET_VIEW, websocket_lovelace_get_view, SCHEMA_GET_VIEW) hass.components.websocket_api.async_register_command( WS_TYPE_UPDATE_VIEW, websocket_lovelace_update_view, SCHEMA_UPDATE_VIEW) hass.components.websocket_api.async_register_command( - WS_TYPE_ADD_VIEW, websocket_lovelace_add_view, - SCHEMA_ADD_VIEW) + WS_TYPE_ADD_VIEW, websocket_lovelace_add_view, SCHEMA_ADD_VIEW) hass.components.websocket_api.async_register_command( - WS_TYPE_MOVE_VIEW, websocket_lovelace_move_view, - SCHEMA_MOVE_VIEW) + WS_TYPE_MOVE_VIEW, websocket_lovelace_move_view, SCHEMA_MOVE_VIEW) hass.components.websocket_api.async_register_command( WS_TYPE_DELETE_VIEW, websocket_lovelace_delete_view, @@ -429,7 +429,7 @@ async def async_setup(hass, config): def handle_yaml_errors(func): - """Handle error with websocket calls.""" + """Handle error with WebSocket calls.""" @wraps(func) async def send_with_error_handling(hass, connection, msg): error = None @@ -463,7 +463,7 @@ def handle_yaml_errors(func): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_config(hass, connection, msg): - """Send lovelace UI config over websocket config.""" + """Send Lovelace UI config over WebSocket configuration.""" return await hass.async_add_executor_job( load_config, hass.config.path(LOVELACE_CONFIG_FILE)) @@ -471,7 +471,7 @@ async def websocket_lovelace_config(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_migrate_config(hass, connection, msg): - """Migrate lovelace UI config.""" + """Migrate Lovelace UI configuration.""" return await hass.async_add_executor_job( migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) @@ -479,7 +479,7 @@ async def websocket_lovelace_migrate_config(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_get_card(hass, connection, msg): - """Send lovelace card config over websocket config.""" + """Send Lovelace card config over WebSocket configuration.""" return await hass.async_add_executor_job( get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], msg.get('format', FORMAT_YAML)) @@ -488,7 +488,7 @@ async def websocket_lovelace_get_card(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_update_card(hass, connection, msg): - """Receive lovelace card config over websocket and save.""" + """Receive Lovelace card configuration over WebSocket and save.""" return await hass.async_add_executor_job( update_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML)) @@ -497,7 +497,7 @@ async def websocket_lovelace_update_card(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_add_card(hass, connection, msg): - """Add new card to view over websocket and save.""" + """Add new card to view over WebSocket and save.""" return await hass.async_add_executor_job( add_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id'], msg['card_config'], msg.get('position'), @@ -507,7 +507,7 @@ async def websocket_lovelace_add_card(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_move_card(hass, connection, msg): - """Move card to different position over websocket and save.""" + """Move card to different position over WebSocket and save.""" if 'new_view_id' in msg: return await hass.async_add_executor_job( move_card_view, hass.config.path(LOVELACE_CONFIG_FILE), @@ -521,16 +521,15 @@ async def websocket_lovelace_move_card(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_delete_card(hass, connection, msg): - """Delete card from lovelace over websocket and save.""" + """Delete card from Lovelace over WebSocket and save.""" return await hass.async_add_executor_job( - delete_card, hass.config.path(LOVELACE_CONFIG_FILE), - msg['card_id']) + delete_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id']) @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_get_view(hass, connection, msg): - """Send lovelace view config over websocket config.""" + """Send Lovelace view config over WebSocket config.""" return await hass.async_add_executor_job( get_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id'], msg.get('format', FORMAT_YAML)) @@ -539,7 +538,7 @@ async def websocket_lovelace_get_view(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_update_view(hass, connection, msg): - """Receive lovelace card config over websocket and save.""" + """Receive Lovelace card config over WebSocket and save.""" return await hass.async_add_executor_job( update_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id'], msg['view_config'], msg.get('format', FORMAT_YAML)) @@ -548,7 +547,7 @@ async def websocket_lovelace_update_view(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_add_view(hass, connection, msg): - """Add new view over websocket and save.""" + """Add new view over WebSocket and save.""" return await hass.async_add_executor_job( add_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_config'], msg.get('position'), @@ -558,7 +557,7 @@ async def websocket_lovelace_add_view(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_move_view(hass, connection, msg): - """Move view to different position over websocket and save.""" + """Move view to different position over WebSocket and save.""" return await hass.async_add_executor_job( move_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id'], msg['new_position']) @@ -567,7 +566,6 @@ async def websocket_lovelace_move_view(hass, connection, msg): @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_delete_view(hass, connection, msg): - """Delete card from lovelace over websocket and save.""" + """Delete card from Lovelace over WebSocket and save.""" return await hass.async_add_executor_job( - delete_view, hass.config.path(LOVELACE_CONFIG_FILE), - msg['view_id']) + delete_view, hass.config.path(LOVELACE_CONFIG_FILE), msg['view_id']) From 782a90a535713f29afb0943a55394b1709486294 Mon Sep 17 00:00:00 2001 From: alex9446 Date: Sat, 3 Nov 2018 11:04:18 +0100 Subject: [PATCH 201/230] Fix hassio command timeout (#17567) * fix hassio command timeout * Increased command timeout --- homeassistant/components/hassio/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 91019776eeb..c33125d840e 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -105,7 +105,7 @@ class HassIO: This method return a coroutine. """ - return self.send_command("/homeassistant/check", timeout=300) + return self.send_command("/homeassistant/check", timeout=600) @_api_data def retrieve_discovery_messages(self): From 9807ba1a5d2885d5ccf5d66277fb1db92fe1d0b6 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sat, 3 Nov 2018 06:36:22 -0500 Subject: [PATCH 202/230] Remove FFmpeg input tests (#18131) * Remove FFmpeg input tests * Not needed here * Removing tests for removed functionality * Minor lint * Fix tests to reflect removed config option * Remove async service registration by request * More lint * Unused imports * Make it a non-breaking change * Update ffmpeg.py --- .../components/binary_sensor/ffmpeg_motion.py | 4 - .../components/binary_sensor/ffmpeg_noise.py | 4 - homeassistant/components/camera/ffmpeg.py | 2 - homeassistant/components/camera/onvif.py | 7 +- homeassistant/components/ffmpeg.py | 31 +------ tests/components/binary_sensor/test_ffmpeg.py | 6 -- tests/components/test_ffmpeg.py | 88 +++---------------- 7 files changed, 15 insertions(+), 127 deletions(-) diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/binary_sensor/ffmpeg_motion.py index df811d47e56..d0e597e13c0 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_motion.py +++ b/homeassistant/components/binary_sensor/ffmpeg_motion.py @@ -49,10 +49,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the FFmpeg binary motion sensor.""" manager = hass.data[DATA_FFMPEG] - - if not await manager.async_run_test(config.get(CONF_INPUT)): - return - entity = FFmpegMotion(hass, manager, config) async_add_entities([entity]) diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/binary_sensor/ffmpeg_noise.py index a2625c3de8d..3c2397d692b 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_noise.py +++ b/homeassistant/components/binary_sensor/ffmpeg_noise.py @@ -46,10 +46,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the FFmpeg noise binary sensor.""" manager = hass.data[DATA_FFMPEG] - - if not await manager.async_run_test(config.get(CONF_INPUT)): - return - entity = FFmpegNoise(hass, manager, config) async_add_entities([entity]) diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index dfbcc4d70bc..6bd68b05bb5 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -32,8 +32,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a FFmpeg camera.""" - if not await hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)): - return async_add_entities([FFmpegCamera(hass, config)]) diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py index c418f68a260..d1afd39ca7b 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -74,9 +74,6 @@ SERVICE_PTZ_SCHEMA = vol.Schema({ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a ONVIF camera.""" - if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)): - return - def handle_ptz(service): """Handle PTZ service call.""" pan = service.data.get(ATTR_PAN, None) @@ -93,8 +90,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for camera in target_cameras: camera.perform_ptz(pan, tilt, zoom) - hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz, - schema=SERVICE_PTZ_SCHEMA) + hass.services.register(DOMAIN, SERVICE_PTZ, handle_ptz, + schema=SERVICE_PTZ_SCHEMA) add_entities([ONVIFHassCamera(hass, config)]) diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index f28dbd52336..791f6d29175 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -40,12 +40,11 @@ CONF_OUTPUT = 'output' CONF_RUN_TEST = 'run_test' DEFAULT_BINARY = 'ffmpeg' -DEFAULT_RUN_TEST = True CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, - vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, + vol.Optional(CONF_RUN_TEST): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) @@ -60,8 +59,7 @@ async def async_setup(hass, config): manager = FFmpegManager( hass, - conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY), - conf.get(CONF_RUN_TEST, DEFAULT_RUN_TEST) + conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY) ) # Register service @@ -95,40 +93,17 @@ async def async_setup(hass, config): class FFmpegManager: """Helper for ha-ffmpeg.""" - def __init__(self, hass, ffmpeg_bin, run_test): + def __init__(self, hass, ffmpeg_bin): """Initialize helper.""" self.hass = hass self._cache = {} self._bin = ffmpeg_bin - self._run_test = run_test @property def binary(self): """Return ffmpeg binary from config.""" return self._bin - async def async_run_test(self, input_source): - """Run test on this input. TRUE is deactivate or run correct. - - This method must be run in the event loop. - """ - from haffmpeg import Test - - if self._run_test: - # if in cache - if input_source in self._cache: - return self._cache[input_source] - - # run test - ffmpeg_test = Test(self.binary, loop=self.hass.loop) - success = await ffmpeg_test.run_test(input_source) - if not success: - _LOGGER.error("FFmpeg '%s' test fails!", input_source) - self._cache[input_source] = False - return False - self._cache[input_source] = True - return True - class FFmpegBase(Entity): """Interface object for FFmpeg.""" diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/binary_sensor/test_ffmpeg.py index 4e6629c0afd..2c17207af32 100644 --- a/tests/components/binary_sensor/test_ffmpeg.py +++ b/tests/components/binary_sensor/test_ffmpeg.py @@ -15,9 +15,6 @@ class TestFFmpegNoiseSetup: self.hass = get_test_home_assistant() self.config = { - 'ffmpeg': { - 'run_test': False, - }, 'binary_sensor': { 'platform': 'ffmpeg_noise', 'input': 'testinputvideo', @@ -80,9 +77,6 @@ class TestFFmpegMotionSetup: self.hass = get_test_home_assistant() self.config = { - 'ffmpeg': { - 'run_test': False, - }, 'binary_sensor': { 'platform': 'ffmpeg_motion', 'input': 'testinputvideo', diff --git a/tests/components/test_ffmpeg.py b/tests/components/test_ffmpeg.py index 774bb471f57..f11611ece75 100644 --- a/tests/components/test_ffmpeg.py +++ b/tests/components/test_ffmpeg.py @@ -1,6 +1,6 @@ """The tests for Home Assistant ffmpeg.""" import asyncio -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock import homeassistant.components.ffmpeg as ffmpeg from homeassistant.components.ffmpeg import ( @@ -10,7 +10,7 @@ from homeassistant.core import callback from homeassistant.setup import setup_component, async_setup_component from tests.common import ( - get_test_home_assistant, assert_setup_component, mock_coro) + get_test_home_assistant, assert_setup_component) @callback @@ -85,14 +85,14 @@ class TestFFmpegSetup: def test_setup_component(self): """Set up ffmpeg component.""" - with assert_setup_component(2): + with assert_setup_component(1): setup_component(self.hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) assert self.hass.data[ffmpeg.DATA_FFMPEG].binary == 'ffmpeg' def test_setup_component_test_service(self): """Set up ffmpeg component test services.""" - with assert_setup_component(2): + with assert_setup_component(1): setup_component(self.hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) assert self.hass.services.has_service(ffmpeg.DOMAIN, 'start') @@ -103,7 +103,7 @@ class TestFFmpegSetup: @asyncio.coroutine def test_setup_component_test_register(hass): """Set up ffmpeg component test register.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -118,7 +118,7 @@ def test_setup_component_test_register(hass): @asyncio.coroutine def test_setup_component_test_register_no_startup(hass): """Set up ffmpeg component test register without startup.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -133,7 +133,7 @@ def test_setup_component_test_register_no_startup(hass): @asyncio.coroutine def test_setup_component_test_service_start(hass): """Set up ffmpeg component test service start.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -149,7 +149,7 @@ def test_setup_component_test_service_start(hass): @asyncio.coroutine def test_setup_component_test_service_stop(hass): """Set up ffmpeg component test service stop.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -165,7 +165,7 @@ def test_setup_component_test_service_stop(hass): @asyncio.coroutine def test_setup_component_test_service_restart(hass): """Set up ffmpeg component test service restart.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -182,7 +182,7 @@ def test_setup_component_test_service_restart(hass): @asyncio.coroutine def test_setup_component_test_service_start_with_entity(hass): """Set up ffmpeg component test service start.""" - with assert_setup_component(2): + with assert_setup_component(1): yield from async_setup_component( hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) @@ -194,71 +194,3 @@ def test_setup_component_test_service_start_with_entity(hass): assert ffmpeg_dev.called_start assert ffmpeg_dev.called_entities == ['test.ffmpeg_device'] - - -@asyncio.coroutine -def test_setup_component_test_run_test_false(hass): - """Set up ffmpeg component test run_test false.""" - with assert_setup_component(2): - yield from async_setup_component( - hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: { - 'run_test': False, - }}) - - manager = hass.data[ffmpeg.DATA_FFMPEG] - with patch('haffmpeg.Test.run_test', return_value=mock_coro(False)): - yield from manager.async_run_test("blabalblabla") - - assert len(manager._cache) == 0 - - -@asyncio.coroutine -def test_setup_component_test_run_test(hass): - """Set up ffmpeg component test run_test.""" - with assert_setup_component(2): - yield from async_setup_component( - hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) - - manager = hass.data[ffmpeg.DATA_FFMPEG] - - with patch('haffmpeg.Test.run_test', return_value=mock_coro(True)) \ - as mock_test: - yield from manager.async_run_test("blabalblabla") - - assert mock_test.called - assert mock_test.call_count == 1 - assert len(manager._cache) == 1 - assert manager._cache['blabalblabla'] - - yield from manager.async_run_test("blabalblabla") - - assert mock_test.called - assert mock_test.call_count == 1 - assert len(manager._cache) == 1 - assert manager._cache['blabalblabla'] - - -@asyncio.coroutine -def test_setup_component_test_run_test_test_fail(hass): - """Set up ffmpeg component test run_test.""" - with assert_setup_component(2): - yield from async_setup_component( - hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) - - manager = hass.data[ffmpeg.DATA_FFMPEG] - - with patch('haffmpeg.Test.run_test', return_value=mock_coro(False)) \ - as mock_test: - yield from manager.async_run_test("blabalblabla") - - assert mock_test.called - assert mock_test.call_count == 1 - assert len(manager._cache) == 1 - assert not manager._cache['blabalblabla'] - - yield from manager.async_run_test("blabalblabla") - - assert mock_test.called - assert mock_test.call_count == 1 - assert len(manager._cache) == 1 - assert not manager._cache['blabalblabla'] From 63e53fdf153eed8f9ed968adebbada037420c85f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Nov 2018 13:47:13 +0100 Subject: [PATCH 203/230] Bump frontend to 20181103.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 541af46192a..108dcf74d9f 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181030.0'] +REQUIREMENTS = ['home-assistant-frontend==20181103.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 4d4a9020b98..22221a57e28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181030.0 +home-assistant-frontend==20181103.0 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8bc7f98a8ea..91219023f9c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181030.0 +home-assistant-frontend==20181103.0 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From e8e135fd2520d79379f2fc9a577689d77d0b008b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Nov 2018 13:47:36 +0100 Subject: [PATCH 204/230] Update translations --- .../components/auth/.translations/pt.json | 10 +++++----- .../components/deconz/.translations/pt.json | 2 +- .../dialogflow/.translations/pl.json | 18 ++++++++++++++++++ .../dialogflow/.translations/pt.json | 18 ++++++++++++++++++ .../dialogflow/.translations/zh-Hant.json | 18 ++++++++++++++++++ .../homematicip_cloud/.translations/pt.json | 4 ++-- .../components/ifttt/.translations/pt.json | 4 ++-- .../components/mailgun/.translations/en.json | 2 +- .../components/mailgun/.translations/pt.json | 4 ++-- .../components/tradfri/.translations/pt.json | 2 +- .../components/twilio/.translations/pt.json | 18 ++++++++++++++++++ .../components/upnp/.translations/ca.json | 1 + .../components/upnp/.translations/en.json | 1 + .../components/upnp/.translations/ko.json | 1 + .../components/upnp/.translations/ru.json | 1 + .../components/upnp/.translations/zh-Hant.json | 1 + .../components/zwave/.translations/pt.json | 2 +- 17 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/dialogflow/.translations/pl.json create mode 100644 homeassistant/components/dialogflow/.translations/pt.json create mode 100644 homeassistant/components/dialogflow/.translations/zh-Hant.json create mode 100644 homeassistant/components/twilio/.translations/pt.json diff --git a/homeassistant/components/auth/.translations/pt.json b/homeassistant/components/auth/.translations/pt.json index 5401c0117e6..e25fe3139a4 100644 --- a/homeassistant/components/auth/.translations/pt.json +++ b/homeassistant/components/auth/.translations/pt.json @@ -10,22 +10,22 @@ "step": { "init": { "description": "Por favor, selecione um dos servi\u00e7os de notifica\u00e7\u00e3o:", - "title": "Configurar uma palavra passe entregue pela componente de notifica\u00e7\u00e3o" + "title": "Configurar uma palavra-passe entregue pela componente de notifica\u00e7\u00e3o" }, "setup": { - "description": "Foi enviada uma palavra passe atrav\u00e9s de **notify.{notify_service}**. Por favor, insira-a:", + "description": "Foi enviada uma palavra-passe atrav\u00e9s de **notify.{notify_service}**. Por favor, insira-a:", "title": "Verificar a configura\u00e7\u00e3o" } }, - "title": "Notificar palavra passe de uso \u00fanico" + "title": "Notificar palavra-passe de uso \u00fanico" }, "totp": { "error": { - "invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso." + "invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistant \u00e9 preciso." }, "step": { "init": { - "description": "Para ativar a autentica\u00e7\u00e3o com dois fatores utilizando passwords unicas temporais (OTP), ler o c\u00f3digo QR com a sua aplica\u00e7\u00e3o de autentica\u00e7\u00e3o. Se voc\u00ea n\u00e3o tiver uma, recomendamos [Google Authenticator](https://support.google.com/accounts/answer/1066447) ou [Authy](https://authy.com/).\n\n{qr_code}\n\nDepois de ler o c\u00f3digo, introduza o c\u00f3digo de seis d\u00edgitos fornecido pela sua aplica\u00e7\u00e3o para verificar a configura\u00e7\u00e3o. Se tiver problemas a ler o c\u00f3digo QR, fa\u00e7a uma configura\u00e7\u00e3o manual com o c\u00f3digo **`{c\u00f3digo}`**.", + "description": "Para ativar a autentica\u00e7\u00e3o com dois fatores utilizando palavras-passe de uso \u00fanico (OTP), ler o c\u00f3digo QR com a sua aplica\u00e7\u00e3o de autentica\u00e7\u00e3o. Se n\u00e3o tiver uma, recomendamos [Google Authenticator](https://support.google.com/accounts/answer/1066447) ou [Authy](https://authy.com/).\n\n{qr_code}\n\nDepois de ler o c\u00f3digo, introduza o c\u00f3digo de seis d\u00edgitos fornecido pela sua aplica\u00e7\u00e3o para verificar a configura\u00e7\u00e3o. Se tiver problemas a ler o c\u00f3digo QR, fa\u00e7a uma configura\u00e7\u00e3o manual com o c\u00f3digo **`{code}`**.", "title": "Configurar autentica\u00e7\u00e3o com dois fatores usando TOTP" } }, diff --git a/homeassistant/components/deconz/.translations/pt.json b/homeassistant/components/deconz/.translations/pt.json index eef2d5ce946..a0419b8baa4 100644 --- a/homeassistant/components/deconz/.translations/pt.json +++ b/homeassistant/components/deconz/.translations/pt.json @@ -25,7 +25,7 @@ "allow_clip_sensor": "Permitir a importa\u00e7\u00e3o de sensores virtuais", "allow_deconz_groups": "Permitir a importa\u00e7\u00e3o de grupos deCONZ" }, - "title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ" + "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o extra para deCONZ" } }, "title": "Gateway Zigbee deCONZ" diff --git a/homeassistant/components/dialogflow/.translations/pl.json b/homeassistant/components/dialogflow/.translations/pl.json new file mode 100644 index 00000000000..9a8e1c1eb11 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty Dialogflow.", + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "create_entry": { + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) by pozna\u0107 szczeg\u00f3\u0142y." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Dialogflow?", + "title": "Konfiguracja Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/pt.json b/homeassistant/components/dialogflow/.translations/pt.json new file mode 100644 index 00000000000..de754080f17 --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/pt.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "A sua inst\u00e2ncia Home Assistant precisa de ser acess\u00edvel a partir da internet para receber mensagens Dialogflow.", + "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." + }, + "create_entry": { + "default": "Para enviar eventos para o Home Assistant, \u00e9 necess\u00e1rio configurar o [Dialogflow Webhook] ({dialogflow_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/json\n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) para obter mais detalhes." + }, + "step": { + "user": { + "description": "Tem certeza de que deseja configurar o Dialogflow?", + "title": "Configurar o Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/.translations/zh-Hant.json b/homeassistant/components/dialogflow/.translations/zh-Hant.json new file mode 100644 index 00000000000..18d3d92e16b --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/zh-Hant.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Home Assistant \u5be6\u4f8b\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Dialogflow \u8a0a\u606f\u3002", + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + }, + "create_entry": { + "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [webhook integration of Dialogflow]({dialogflow_url})\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" + }, + "step": { + "user": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Dialogflow\uff1f", + "title": "\u8a2d\u5b9a Dialogflow Webhook" + } + }, + "title": "Dialogflow" + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/.translations/pt.json b/homeassistant/components/homematicip_cloud/.translations/pt.json index 18377490a5f..8b431125ef0 100644 --- a/homeassistant/components/homematicip_cloud/.translations/pt.json +++ b/homeassistant/components/homematicip_cloud/.translations/pt.json @@ -6,9 +6,9 @@ "unknown": "Ocorreu um erro desconhecido." }, "error": { - "invalid_pin": "PIN inv\u00e1lido, por favor, tente novamente.", + "invalid_pin": "PIN inv\u00e1lido. Por favor, tente novamente.", "press_the_button": "Por favor, pressione o bot\u00e3o azul.", - "register_failed": "Falha ao registrar, por favor, tente novamente.", + "register_failed": "Falha ao registar. Por favor, tente novamente.", "timeout_button": "Tempo limite ultrapassado para carregar bot\u00e3o azul, por favor, tente de novo." }, "step": { diff --git a/homeassistant/components/ifttt/.translations/pt.json b/homeassistant/components/ifttt/.translations/pt.json index 08b7aee6a08..e18541fcab9 100644 --- a/homeassistant/components/ifttt/.translations/pt.json +++ b/homeassistant/components/ifttt/.translations/pt.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": "A sua inst\u00e2ncia Home Assistent precisa de ser acess\u00edvel a partir da internet para receber mensagens IFTTT.", + "not_internet_accessible": "A sua inst\u00e2ncia Home Assistant precisa de ser acess\u00edvel a partir da internet para receber mensagens IFTTT.", "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." }, "create_entry": { - "default": "Para enviar eventos para o Home Assistente, precisa de utilizar a a\u00e7\u00e3o \"Make a web request\" no [IFTTT Webhook applet]({applet_url}).\n\nPreencha com a seguinte informa\u00e7\u00e3o:\n\n- URL: `{webhook_url}`\n- Method: POST \n- Content Type: application/json \n\nConsulte [a documenta\u00e7\u00e3o]({docs_url}) sobre como configurar automa\u00e7\u00f5es para lidar com dados de entrada." + "default": "Para enviar eventos para o Home Assistant, precisa de utilizar a a\u00e7\u00e3o \"Make a web request\" no [IFTTT Webhook applet]({applet_url}).\n\nPreencha com a seguinte informa\u00e7\u00e3o:\n\n- URL: `{webhook_url}`\n- Method: POST \n- Content Type: application/json \n\nConsulte [a documenta\u00e7\u00e3o]({docs_url}) sobre como configurar automa\u00e7\u00f5es para lidar com dados de entrada." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/.translations/en.json b/homeassistant/components/mailgun/.translations/en.json index 3abb8aba726..98529a33815 100644 --- a/homeassistant/components/mailgun/.translations/en.json +++ b/homeassistant/components/mailgun/.translations/en.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Only a single instance is necessary." }, "create_entry": { - "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/.translations/pt.json b/homeassistant/components/mailgun/.translations/pt.json index 963d3322d84..72255c695ac 100644 --- a/homeassistant/components/mailgun/.translations/pt.json +++ b/homeassistant/components/mailgun/.translations/pt.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "not_internet_accessible": "A sua inst\u00e2ncia Home Assistent precisa de ser acess\u00edvel a partir da internet para receber mensagens Mailgun.", + "not_internet_accessible": "A sua inst\u00e2ncia Home Assistant precisa de ser acess\u00edvel a partir da internet para receber mensagens Mailgun.", "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." }, "create_entry": { - "default": "Para enviar eventos para o Home Assistant, voc\u00ea precisar\u00e1 configurar [Webhooks with Mailgun] ({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." + "default": "Para enviar eventos para o Home Assistant, \u00e9 necess\u00e1rio configurar [Webhooks with Mailgun] ({mailgun_url}). \n\n Preencha as seguintes informa\u00e7\u00f5es: \n\n - URL: `{webhook_url}`\n - M\u00e9todo: POST \n - Tipo de Conte\u00fado: application/x-www-form-urlencoded \n\n Veja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." }, "step": { "user": { diff --git a/homeassistant/components/tradfri/.translations/pt.json b/homeassistant/components/tradfri/.translations/pt.json index d3cb32b5d5f..d728bc32f0b 100644 --- a/homeassistant/components/tradfri/.translations/pt.json +++ b/homeassistant/components/tradfri/.translations/pt.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel ligar \u00e0 gateway.", - "invalid_key": "Falha ao registrar-se com a chave fornecida. Se o problema persistir, tente reiniciar a gateway.", + "invalid_key": "Falha ao registar-se com a chave fornecida. Se o problema persistir, tente reiniciar a gateway.", "timeout": "Tempo excedido a validar o c\u00f3digo." }, "step": { diff --git a/homeassistant/components/twilio/.translations/pt.json b/homeassistant/components/twilio/.translations/pt.json new file mode 100644 index 00000000000..30495e5854f --- /dev/null +++ b/homeassistant/components/twilio/.translations/pt.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "A sua inst\u00e2ncia Home Assistant precisa de ser acess\u00edvel a partir da internet para receber mensagens Twilio.", + "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." + }, + "create_entry": { + "default": "Para enviar eventos para o Home Assistant, \u00e9 necess\u00e1rio configurar [Webhooks with Twilio] ({twilio_url}). \n\nPreencha as seguintes informa\u00e7\u00f5es: \n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST \n- Tipo de Conte\u00fado: application/x-www-form-urlencoded \n\nVeja [a documenta\u00e7\u00e3o] ({docs_url}) sobre como configurar automa\u00e7\u00f5es para manipular dados de entrada." + }, + "step": { + "user": { + "description": "Tem certeza de que deseja configurar o Twilio?", + "title": "Configurar o Twilio Webhook" + } + }, + "title": "Twilio" + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/.translations/ca.json b/homeassistant/components/upnp/.translations/ca.json index 5dba9d1e16e..ab09dbc5bda 100644 --- a/homeassistant/components/upnp/.translations/ca.json +++ b/homeassistant/components/upnp/.translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "UPnP/IGD ja est\u00e0 configurat", + "incomplete_device": "Ignorant el dispositiu incomplet UPnP", "no_devices_discovered": "No s'ha trobat cap UPnP/IGD", "no_sensors_or_port_mapping": "Activa, com a m\u00ednim, els sensors o l'assignaci\u00f3 de ports" }, diff --git a/homeassistant/components/upnp/.translations/en.json b/homeassistant/components/upnp/.translations/en.json index 93e1db62f8e..76384beac71 100644 --- a/homeassistant/components/upnp/.translations/en.json +++ b/homeassistant/components/upnp/.translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "UPnP/IGD is already configured", + "incomplete_device": "Ignoring incomplete UPnP device", "no_devices_discovered": "No UPnP/IGDs discovered", "no_sensors_or_port_mapping": "Enable at least sensors or port mapping" }, diff --git a/homeassistant/components/upnp/.translations/ko.json b/homeassistant/components/upnp/.translations/ko.json index 0dd7a16de0b..9e10ae1d67c 100644 --- a/homeassistant/components/upnp/.translations/ko.json +++ b/homeassistant/components/upnp/.translations/ko.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "UPnP/IGD \uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4", + "incomplete_device": "\ubd88\uc644\uc804\ud55c UPnP \uc7a5\uce58 \ubb34\uc2dc\ud558\uae30", "no_devices_discovered": "\ubc1c\uacac\ub41c UPnP/IGD \uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "no_sensors_or_port_mapping": "\ucd5c\uc18c\ud55c \uc13c\uc11c \ud639\uc740 \ud3ec\ud2b8 \ub9e4\ud551\uc744 \ud65c\uc131\ud654 \ud574\uc57c \ud569\ub2c8\ub2e4" }, diff --git a/homeassistant/components/upnp/.translations/ru.json b/homeassistant/components/upnp/.translations/ru.json index 9b7d358da0a..5cb9a3f4a27 100644 --- a/homeassistant/components/upnp/.translations/ru.json +++ b/homeassistant/components/upnp/.translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP", "no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD", "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 " }, diff --git a/homeassistant/components/upnp/.translations/zh-Hant.json b/homeassistant/components/upnp/.translations/zh-Hant.json index 22db0f26482..2c1fe82c523 100644 --- a/homeassistant/components/upnp/.translations/zh-Hant.json +++ b/homeassistant/components/upnp/.translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "incomplete_device": "\u5ffd\u7565\u4e0d\u76f8\u5bb9 UPnP \u88dd\u7f6e", "no_devices_discovered": "\u672a\u641c\u5c0b\u5230 UPnP/IGD", "no_sensors_or_port_mapping": "\u81f3\u5c11\u958b\u555f\u611f\u61c9\u5668\u6216\u901a\u8a0a\u57e0\u8f49\u767c" }, diff --git a/homeassistant/components/zwave/.translations/pt.json b/homeassistant/components/zwave/.translations/pt.json index 6962f077498..23c653d02fc 100644 --- a/homeassistant/components/zwave/.translations/pt.json +++ b/homeassistant/components/zwave/.translations/pt.json @@ -5,7 +5,7 @@ "one_instance_only": "Componente suporta apenas uma inst\u00e2ncia Z-Wave" }, "error": { - "option_error": "A valida\u00e7\u00e3o Z-Wave falhou. O caminho para o stick USB est\u00e1 correto?" + "option_error": "A valida\u00e7\u00e3o Z-Wave falhou. O caminho para o dispositivo USB est\u00e1 correto?" }, "step": { "user": { From 9bf824bf00004c19718ca75f1be6a8ff67073af5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Nov 2018 13:48:45 +0100 Subject: [PATCH 205/230] Bumped version to 0.82.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ffbba575a14..bb14259ce0d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0.dev0' +PATCH_VERSION = '0b0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 461e6acf5cfb491ca5efbce8b7b9fc70ef4e1bd3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Nov 2018 19:18:20 +0100 Subject: [PATCH 206/230] Bump frontend to 20181103.1 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 108dcf74d9f..450daf5eb4c 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181103.0'] +REQUIREMENTS = ['home-assistant-frontend==20181103.1'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 22221a57e28..a6524fee2d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.0 +home-assistant-frontend==20181103.1 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 91219023f9c..75e21b33c38 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.0 +home-assistant-frontend==20181103.1 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From b2bdf05caecbecadf27aa06aabc5002161c7518c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Nov 2018 19:18:38 +0100 Subject: [PATCH 207/230] Bumped version to 0.82.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bb14259ce0d..2e0ef79d8f8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0b0' +PATCH_VERSION = '0b1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From dbf6b01a603aca5b7cd7d1e8281cd1286eb8206a Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 4 Nov 2018 19:09:14 +0200 Subject: [PATCH 208/230] SMA: Optional import in schema & backoff fix (#18099) --- homeassistant/components/sensor/sma.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index 4b0c33191dc..3f17b4971ec 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -35,7 +35,10 @@ GROUPS = ['user', 'installer'] def _check_sensor_schema(conf): """Check sensors and attributes are valid.""" - import pysma + try: + import pysma + except ImportError: + return conf valid = list(conf[CONF_CUSTOM].keys()) valid.extend([s.name for s in pysma.SENSORS]) @@ -73,6 +76,9 @@ async def async_setup_platform( """Set up SMA WebConnect sensor.""" import pysma + # Check config again during load - dependency available + config = _check_sensor_schema(config) + # Sensor_defs from the custom config for name, prop in config[CONF_CUSTOM].items(): n_s = pysma.Sensor(name, prop['key'], prop['unit'], prop['factor']) @@ -107,18 +113,24 @@ async def async_setup_platform( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session) backoff = 0 + backoff_step = 0 async def async_sma(event): """Update all the SMA sensors.""" - nonlocal backoff + nonlocal backoff, backoff_step if backoff > 1: backoff -= 1 return values = await sma.read(used_sensors) - if values is None: - backoff = 10 + if not values: + try: + backoff = [1, 1, 1, 6, 30][backoff_step] + backoff_step += 1 + except IndexError: + backoff = 60 return + backoff_step = 0 tasks = [] for sensor in hass_sensors: From 31737c5100a371b25201cde2bd19a288156e533c Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sun, 4 Nov 2018 08:20:32 -0600 Subject: [PATCH 209/230] Remove config (breaking change) (#18153) --- homeassistant/components/ffmpeg.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index 791f6d29175..a2f0ca19231 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -37,14 +37,12 @@ CONF_INPUT = 'input' CONF_FFMPEG_BIN = 'ffmpeg_bin' CONF_EXTRA_ARGUMENTS = 'extra_arguments' CONF_OUTPUT = 'output' -CONF_RUN_TEST = 'run_test' DEFAULT_BINARY = 'ffmpeg' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, - vol.Optional(CONF_RUN_TEST): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) From b3bd59efb00d90e13d2c52adb611436586d977d0 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 4 Nov 2018 15:15:14 +0100 Subject: [PATCH 210/230] Handle TensorFlow like OpenCV (#18185) * Handle TensorFlow like OpenCV * Update requirements_all.txt --- homeassistant/components/image_processing/tensorflow.py | 3 +-- requirements_all.txt | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/image_processing/tensorflow.py b/homeassistant/components/image_processing/tensorflow.py index f333aa1767c..a2cd997bb76 100644 --- a/homeassistant/components/image_processing/tensorflow.py +++ b/homeassistant/components/image_processing/tensorflow.py @@ -20,8 +20,7 @@ from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['numpy==1.15.3', 'pillow==5.2.0', - 'protobuf==3.6.1', 'tensorflow==1.11.0'] +REQUIREMENTS = ['numpy==1.15.3', 'pillow==5.2.0', 'protobuf==3.6.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index a6524fee2d7..48673ee31ca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1469,9 +1469,6 @@ temescal==0.1 # homeassistant.components.sensor.temper temperusb==1.5.3 -# homeassistant.components.image_processing.tensorflow -tensorflow==1.11.0 - # homeassistant.components.tesla teslajsonpy==0.0.23 From de79c42b8a099424406223599722781c4ab2c0ea Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 4 Nov 2018 15:19:48 +0100 Subject: [PATCH 211/230] Add support for TensorFlow in official docker (#18191) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 92b85c29325..4cd4f3e8871 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ COPY requirements_all.txt requirements_all.txt # Uninstall enum34 because some dependencies install it but breaks Python 3.4+. # See PR #8103 for more info. RUN pip3 install --no-cache-dir -r requirements_all.txt && \ - pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython + pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython tensorflow # Copy source COPY . . From be3800d9a5f44a369e825f5324964316429e6821 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Nov 2018 13:21:03 +0100 Subject: [PATCH 212/230] Cloud conf (#18216) * Add original config to entityfilter * Add alexa/google config to cloud status call * Lint --- homeassistant/components/cloud/__init__.py | 4 +-- homeassistant/components/cloud/http_api.py | 6 ++++ homeassistant/helpers/entityfilter.py | 28 +++++++++------ tests/components/cloud/test_http_api.py | 40 +++++++++++++++++++--- tests/helpers/test_entityfilter.py | 16 +++++++-- 5 files changed, 74 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index ba2d41a9feb..bc486eb7ead 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -122,7 +122,7 @@ class Cloud: self.hass = hass self.mode = mode self.alexa_config = alexa - self._google_actions = google_actions + self.google_actions_user_conf = google_actions self._gactions_config = None self._prefs = None self.id_token = None @@ -180,7 +180,7 @@ class Cloud: def gactions_config(self): """Return the Google Assistant config.""" if self._gactions_config is None: - conf = self._google_actions + conf = self.google_actions_user_conf def should_expose(entity): """If an entity should be exposed.""" diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 0df4a39406e..cb62d773dfd 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -11,6 +11,8 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import ( RequestDataValidator) from homeassistant.components import websocket_api +from homeassistant.components.alexa import smart_home as alexa_sh +from homeassistant.components.google_assistant import smart_home as google_sh from . import auth_api from .const import DOMAIN, REQUEST_TIMEOUT @@ -307,5 +309,9 @@ def _account_data(cloud): 'email': claims['email'], 'cloud': cloud.iot.state, 'google_enabled': cloud.google_enabled, + 'google_entities': cloud.google_actions_user_conf['filter'].config, + 'google_domains': list(google_sh.DOMAIN_TO_GOOGLE_TYPES), 'alexa_enabled': cloud.alexa_enabled, + 'alexa_entities': cloud.alexa_config.should_expose.config, + 'alexa_domains': list(alexa_sh.ENTITY_ADAPTERS), } diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index c9554488aa7..141fc912275 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -10,6 +10,18 @@ CONF_INCLUDE_ENTITIES = 'include_entities' CONF_EXCLUDE_DOMAINS = 'exclude_domains' CONF_EXCLUDE_ENTITIES = 'exclude_entities' + +def _convert_filter(config): + filt = generate_filter( + config[CONF_INCLUDE_DOMAINS], + config[CONF_INCLUDE_ENTITIES], + config[CONF_EXCLUDE_DOMAINS], + config[CONF_EXCLUDE_ENTITIES], + ) + filt.config = config + return filt + + FILTER_SCHEMA = vol.All( vol.Schema({ vol.Optional(CONF_EXCLUDE_DOMAINS, default=[]): @@ -18,13 +30,7 @@ FILTER_SCHEMA = vol.All( vol.Optional(CONF_INCLUDE_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_INCLUDE_ENTITIES, default=[]): cv.entity_ids, - }), - lambda config: generate_filter( - config[CONF_INCLUDE_DOMAINS], - config[CONF_INCLUDE_ENTITIES], - config[CONF_EXCLUDE_DOMAINS], - config[CONF_EXCLUDE_ENTITIES], - )) + }), _convert_filter) def generate_filter(include_domains, include_entities, @@ -64,8 +70,8 @@ def generate_filter(include_domains, include_entities, # Case 4 - both includes and excludes specified # Case 4a - include domain specified - # - if domain is included, and entity not excluded, pass - # - if domain is not included, and entity not included, fail + # - if domain is included, pass if entity not excluded + # - if domain is not included, pass if entity is included # note: if both include and exclude domains specified, # the exclude domains are ignored if include_d: @@ -79,8 +85,8 @@ def generate_filter(include_domains, include_entities, return entity_filter_4a # Case 4b - exclude domain specified - # - if domain is excluded, and entity not included, fail - # - if domain is not excluded, and entity not excluded, pass + # - if domain is excluded, pass if entity is included + # - if domain is not excluded, pass if entity not excluded if exclude_d: def entity_filter_4b(entity_id): """Return filter function for case 4b.""" diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index e27760bd6ed..a8128c8d3e0 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -35,6 +35,16 @@ def setup_api(hass): 'relayer': 'relayer', 'google_actions_sync_url': GOOGLE_ACTIONS_SYNC_URL, 'subscription_info_url': SUBSCRIPTION_INFO_URL, + 'google_actions': { + 'filter': { + 'include_domains': 'light' + } + }, + 'alexa': { + 'filter': { + 'include_entities': ['light.kitchen', 'switch.ac'] + } + } }) return mock_cloud_prefs(hass) @@ -325,17 +335,37 @@ async def test_websocket_status(hass, hass_ws_client, mock_cloud_fixture): }, 'test') hass.data[DOMAIN].iot.state = iot.STATE_CONNECTED client = await hass_ws_client(hass) - await client.send_json({ - 'id': 5, - 'type': 'cloud/status' - }) - response = await client.receive_json() + + with patch.dict( + 'homeassistant.components.google_assistant.smart_home.' + 'DOMAIN_TO_GOOGLE_TYPES', {'light': None}, clear=True + ), patch.dict('homeassistant.components.alexa.smart_home.ENTITY_ADAPTERS', + {'switch': None}, clear=True): + await client.send_json({ + 'id': 5, + 'type': 'cloud/status' + }) + response = await client.receive_json() assert response['result'] == { 'logged_in': True, 'email': 'hello@home-assistant.io', 'cloud': 'connected', 'alexa_enabled': True, + 'alexa_entities': { + 'include_domains': [], + 'include_entities': ['light.kitchen', 'switch.ac'], + 'exclude_domains': [], + 'exclude_entities': [], + }, + 'alexa_domains': ['switch'], 'google_enabled': True, + 'google_entities': { + 'include_domains': ['light'], + 'include_entities': [], + 'exclude_domains': [], + 'exclude_entities': [], + }, + 'google_domains': ['light'], } diff --git a/tests/helpers/test_entityfilter.py b/tests/helpers/test_entityfilter.py index 944224a34d1..13e5bc1d273 100644 --- a/tests/helpers/test_entityfilter.py +++ b/tests/helpers/test_entityfilter.py @@ -1,5 +1,5 @@ """The tests for the EntityFilter component.""" -from homeassistant.helpers.entityfilter import generate_filter +from homeassistant.helpers.entityfilter import generate_filter, FILTER_SCHEMA def test_no_filters_case_1(): @@ -78,7 +78,7 @@ def test_exclude_domain_case4b(): assert testfilter("sun.sun") is True -def testno_domain_case4c(): +def test_no_domain_case4c(): """Test case 4c - include and exclude specified, with no domains.""" incl_dom = {} incl_ent = {'binary_sensor.working'} @@ -93,3 +93,15 @@ def testno_domain_case4c(): assert testfilter("binary_sensor.working") assert testfilter("binary_sensor.another") is False assert testfilter("sun.sun") is False + + +def test_filter_schema(): + """Test filter schema.""" + conf = { + 'include_domains': ['light'], + 'include_entities': ['switch.kitchen'], + 'exclude_domains': ['cover'], + 'exclude_entities': ['light.kitchen'] + } + filt = FILTER_SCHEMA(conf) + assert filt.config == conf From fb947288ad82ef15559a147f992964f065c1dca8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Nov 2018 21:41:19 +0100 Subject: [PATCH 213/230] Check if os has chown (#18229) --- homeassistant/util/ruamel_yaml.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index c3035811344..eb3e935c6ce 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -109,10 +109,11 @@ def save_yaml(fname: str, data: JSON_TYPE) -> None: as temp_file: yaml.dump(data, temp_file) os.replace(tmp_fname, fname) - try: - os.chown(fname, file_stat.st_uid, file_stat.st_gid) - except OSError: - pass + if hasattr(os, 'chown'): + try: + os.chown(fname, file_stat.st_uid, file_stat.st_gid) + except OSError: + pass except YAMLError as exc: _LOGGER.error(str(exc)) raise HomeAssistantError(exc) From 30fccc696e2e0a590824514614876d363797ee0b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Nov 2018 21:41:48 +0100 Subject: [PATCH 214/230] Bumped version to 0.82.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2e0ef79d8f8..e960739ff12 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0b1' +PATCH_VERSION = '0b2' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 782f5c7d190b99906110aab7ef18140aa3e2e4f8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Nov 2018 10:36:15 +0100 Subject: [PATCH 215/230] Bump frontend to 20181103.2 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 450daf5eb4c..e8ae0df06ee 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181103.1'] +REQUIREMENTS = ['home-assistant-frontend==20181103.2'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 48673ee31ca..9b417f103de 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.1 +home-assistant-frontend==20181103.2 # homeassistant.components.homekit_controller # homekit==0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 75e21b33c38..d5f3602faa5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.1 +home-assistant-frontend==20181103.2 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 0b6aa38b1316bdcfc18ea17bb7f56c4920088caa Mon Sep 17 00:00:00 2001 From: Mikko Tapionlinna Date: Tue, 6 Nov 2018 02:00:46 +0200 Subject: [PATCH 216/230] Update pynetgear to 0.5.1 (#18238) --- homeassistant/components/device_tracker/netgear.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index 12d026a35cd..99d379fb4d3 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -15,7 +15,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL, CONF_DEVICES, CONF_EXCLUDE) -REQUIREMENTS = ['pynetgear==0.5.0'] +REQUIREMENTS = ['pynetgear==0.5.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 9b417f103de..fd9f959efb9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1026,7 +1026,7 @@ pymysensors==0.17.0 pynello==1.5.1 # homeassistant.components.device_tracker.netgear -pynetgear==0.5.0 +pynetgear==0.5.1 # homeassistant.components.switch.netio pynetio==0.1.9.1 From 17f04c1736d50d1fba091c6bd4f25f7aea5b7ea7 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 6 Nov 2018 22:08:04 +0100 Subject: [PATCH 217/230] Migrate python-openzwave to homeassistant-pyozw (#18268) * Migrate python-openzwave to homeassistant-pyozw * Update requirements_all.txt * Fix requirements --- homeassistant/components/zwave/__init__.py | 2 +- requirements_all.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index a4f8dcd1b3f..a27d2112dcd 100644 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -42,7 +42,7 @@ from .discovery_schemas import DISCOVERY_SCHEMAS from .util import (check_node_schema, check_value_schema, node_name, check_has_unique_id, is_node_parsed) -REQUIREMENTS = ['pydispatcher==2.0.5', 'python_openzwave==0.4.10'] +REQUIREMENTS = ['pydispatcher==2.0.5', 'homeassistant-pyozw==0.1.0'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index fd9f959efb9..a2efa7e269a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -478,6 +478,9 @@ holidays==0.9.8 # homeassistant.components.frontend home-assistant-frontend==20181103.2 +# homeassistant.components.zwave +homeassistant-pyozw==0.1.0 + # homeassistant.components.homekit_controller # homekit==0.10 @@ -1227,9 +1230,6 @@ python-wink==1.10.1 # homeassistant.components.sensor.swiss_public_transport python_opendata_transport==0.1.4 -# homeassistant.components.zwave -python_openzwave==0.4.10 - # homeassistant.components.egardia pythonegardia==1.0.39 From cff475570841515cfb38f821fc77a02b55082974 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Wed, 7 Nov 2018 08:52:51 +0200 Subject: [PATCH 218/230] SMA Guard against older pysma (#18278) --- homeassistant/components/sensor/sma.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index 3f17b4971ec..acf1ead186c 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -37,11 +37,11 @@ def _check_sensor_schema(conf): """Check sensors and attributes are valid.""" try: import pysma - except ImportError: + valid = [s.name for s in pysma.SENSORS] + except (ImportError, AttributeError): return conf - valid = list(conf[CONF_CUSTOM].keys()) - valid.extend([s.name for s in pysma.SENSORS]) + valid.extend(conf[CONF_CUSTOM].keys()) for sname, attrs in conf[CONF_SENSORS].items(): if sname not in valid: raise vol.Invalid("{} does not exist".format(sname)) From 06b9600069e22bfedfa9b976b674bc9405056d93 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Wed, 7 Nov 2018 09:55:22 +0100 Subject: [PATCH 219/230] Bump pyotgw to 0.3b1 (#18286) * Bump pyotgw to 0.3b1 * Update requirements_all.txt --- homeassistant/components/opentherm_gw/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 3cf66c72a3a..06dcd0e19b0 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -104,7 +104,7 @@ CONFIG_SCHEMA = vol.Schema({ }), }, extra=vol.ALLOW_EXTRA) -REQUIREMENTS = ['pyotgw==0.3b0'] +REQUIREMENTS = ['pyotgw==0.3b1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index a2efa7e269a..e84950765d1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1054,7 +1054,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==0.3b0 +pyotgw==0.3b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From e9b8b290fc83a1b3edb7478511904e2f072f9fb6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Nov 2018 10:37:57 +0100 Subject: [PATCH 220/230] Bumped version to 0.82.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e960739ff12..6b907e9ccde 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0b2' +PATCH_VERSION = '0b3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From f541b101c9fd32879b4df3bb646130da7796b187 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 4 Nov 2018 12:19:04 +0100 Subject: [PATCH 221/230] Bugfix discovery (delete/mqtt) call for Hass.io (#18159) * Bugfix discovery delete call for Hass.io * Fix host * fix tests --- homeassistant/components/hassio/discovery.py | 2 +- homeassistant/components/mqtt/config_flow.py | 6 +++--- tests/components/mqtt/test_config_flow.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 4c7c5a6597f..a5f62b9e1a1 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -71,7 +71,7 @@ class HassIODiscovery(HomeAssistantView): async def delete(self, request, uuid): """Handle remove discovery requests.""" - data = request.json() + data = await request.json() await self.async_process_del(data) return web.Response() diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index e0d1e692c60..aee825d06de 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_PROTOCOL) + CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_PROTOCOL, CONF_HOST) from .const import CONF_BROKER, CONF_DISCOVERY, DEFAULT_DISCOVERY @@ -80,7 +80,7 @@ class FlowHandler(config_entries.ConfigFlow): data = self._hassio_discovery can_connect = await self.hass.async_add_executor_job( try_connection, - data[CONF_BROKER], + data[CONF_HOST], data[CONF_PORT], data.get(CONF_USERNAME), data.get(CONF_PASSWORD), @@ -90,7 +90,7 @@ class FlowHandler(config_entries.ConfigFlow): if can_connect: return self.async_create_entry( title=data['addon'], data={ - CONF_BROKER: data[CONF_BROKER], + CONF_BROKER: data[CONF_HOST], CONF_PORT: data[CONF_PORT], CONF_USERNAME: data.get(CONF_USERNAME), CONF_PASSWORD: data.get(CONF_PASSWORD), diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 08bb4e54a39..66bf9b97807 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -119,7 +119,7 @@ async def test_hassio_confirm(hass, mock_try_connection, 'mqtt', data={ 'addon': 'Mock Addon', - 'broker': 'mock-broker', + 'host': 'mock-broker', 'port': 1883, 'username': 'mock-user', 'password': 'mock-pass', From f24979c7cf19d5dbf0f4ec41d95fa8c75edc88d1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 7 Nov 2018 21:58:18 +0100 Subject: [PATCH 222/230] Bumped version to 0.82.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6b907e9ccde..9a8678072f3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0b3' +PATCH_VERSION = '0b4' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From 878e369c4a99e40dd162493020a022636d7ff84a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 8 Nov 2018 00:33:51 +0100 Subject: [PATCH 223/230] Fix log error message (#18305) * Fix log error message * Update __init__.py --- homeassistant/components/hassio/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 9516675480a..8523bb5ea64 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -143,9 +143,10 @@ async def async_check_config(hass): result = await hassio.check_homeassistant_config() except HassioAPIError as err: _LOGGER.error("Error on Hass.io API: %s", err) + else: + if result['result'] == "error": + return result['message'] - if result['result'] == "error": - return result['message'] return None From a016dd21405f7adbb7a188c466fc229499d55899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 8 Nov 2018 09:27:51 +0100 Subject: [PATCH 224/230] Bump pyhaversion to 2.0.2 (#18318) --- homeassistant/components/sensor/version.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/version.py b/homeassistant/components/sensor/version.py index b71ae158181..11cb6832e40 100644 --- a/homeassistant/components/sensor/version.py +++ b/homeassistant/components/sensor/version.py @@ -16,7 +16,7 @@ from homeassistant.const import CONF_NAME, CONF_SOURCE from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['pyhaversion==2.0.1'] +REQUIREMENTS = ['pyhaversion==2.0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index e84950765d1..583707513ee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -932,7 +932,7 @@ pygtfs-homeassistant==0.1.3.dev0 pyharmony==1.0.20 # homeassistant.components.sensor.version -pyhaversion==2.0.1 +pyhaversion==2.0.2 # homeassistant.components.binary_sensor.hikvision pyhik==0.1.8 From de37fc90c0022dfc574fb23d146159f9dd69dc41 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 9 Nov 2018 15:40:46 +0100 Subject: [PATCH 225/230] Bump frontend to 20181103.3 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index e8ae0df06ee..1d6721306fd 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -24,7 +24,7 @@ from homeassistant.core import callback from homeassistant.helpers.translation import async_get_translations from homeassistant.loader import bind_hass -REQUIREMENTS = ['home-assistant-frontend==20181103.2'] +REQUIREMENTS = ['home-assistant-frontend==20181103.3'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/requirements_all.txt b/requirements_all.txt index 583707513ee..411fbae9911 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -476,7 +476,7 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.2 +home-assistant-frontend==20181103.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d5f3602faa5..2b0e151feba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181103.2 +home-assistant-frontend==20181103.3 # homeassistant.components.homematicip_cloud homematicip==0.9.8 From 8ab2f669d2ea8e875db9bb0531864ffc4f593b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 9 Nov 2018 11:36:00 +0100 Subject: [PATCH 226/230] Fix xiaomi binary_sensor warning (#18280) * Fix xiaomi binary_sensor warning * Fix xiaomi binary_sensor warning --- homeassistant/components/binary_sensor/xiaomi_aqara.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/binary_sensor/xiaomi_aqara.py index e082c886f03..45217c42c1d 100644 --- a/homeassistant/components/binary_sensor/xiaomi_aqara.py +++ b/homeassistant/components/binary_sensor/xiaomi_aqara.py @@ -357,6 +357,9 @@ class XiaomiVibration(XiaomiBinarySensor): def parse_data(self, data, raw_data): """Parse data sent by gateway.""" value = data.get(self._data_key) + if value is None: + return False + if value not in ('vibrate', 'tilt', 'free_fall'): _LOGGER.warning("Unsupported movement_type detected: %s", value) From f4c35a389d841ace6a21223ffa2905a033ff4e53 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Nov 2018 11:17:44 +0100 Subject: [PATCH 227/230] Remove Velbus climate platform (#18319) --- homeassistant/components/climate/velbus.py | 81 ---------------------- homeassistant/components/velbus.py | 3 - 2 files changed, 84 deletions(-) delete mode 100644 homeassistant/components/climate/velbus.py diff --git a/homeassistant/components/climate/velbus.py b/homeassistant/components/climate/velbus.py deleted file mode 100644 index ab8542541c8..00000000000 --- a/homeassistant/components/climate/velbus.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Support for Velbus thermostat. - -For more details about this platform, please refer to the documentation -https://home-assistant.io/components/climate.velbus/ -""" -import logging - -from homeassistant.components.climate import ( - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) -from homeassistant.const import ATTR_TEMPERATURE - -_LOGGER = logging.getLogger(__name__) - -DEPENDENCIES = ['velbus'] - -OPERATION_LIST = ['comfort', 'day', 'night', 'safe'] - -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) - - -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): - """Set up the Velbus thermostat platform.""" - if discovery_info is None: - return - - sensors = [] - for sensor in discovery_info: - module = hass.data[VELBUS_DOMAIN].get_module(sensor[0]) - channel = sensor[1] - sensors.append(VelbusClimate(module, channel)) - - async_add_entities(sensors) - - -class VelbusClimate(VelbusEntity, ClimateDevice): - """Representation of a Velbus thermostat.""" - - @property - def supported_features(self): - """Return the list off supported features.""" - return SUPPORT_FLAGS - - @property - def temperature_unit(self): - """Return the unit this state is expressed in.""" - return self._module.get_unit(self._channel) - - @property - def current_temperature(self): - """Return the current temperature.""" - return self._module.get_state(self._channel) - - @property - def current_operation(self): - """Return current operation ie. heat, cool, idle.""" - return self._module.get_climate_mode() - - @property - def operation_list(self): - """Return the list of available operation modes.""" - return OPERATION_LIST - - @property - def target_temperature(self): - """Return the temperature we try to reach.""" - return self._module.get_climate_target() - - def set_operation_mode(self, operation_mode): - """Set new target operation mode.""" - self._module.set_mode(operation_mode) - self.schedule_update_ha_state() - - def set_temperature(self, **kwargs): - """Set new target temperatures.""" - if kwargs.get(ATTR_TEMPERATURE) is not None: - self._module.set_temp(kwargs.get(ATTR_TEMPERATURE)) - self.schedule_update_ha_state() diff --git a/homeassistant/components/velbus.py b/homeassistant/components/velbus.py index 294061527f8..a7b385297a8 100644 --- a/homeassistant/components/velbus.py +++ b/homeassistant/components/velbus.py @@ -47,7 +47,6 @@ async def async_setup(hass, config): modules = controller.get_modules() discovery_info = { 'switch': [], - 'climate': [], 'binary_sensor': [], 'sensor': [] } @@ -61,8 +60,6 @@ async def async_setup(hass, config): )) load_platform(hass, 'switch', DOMAIN, discovery_info['switch'], config) - load_platform(hass, 'climate', DOMAIN, - discovery_info['climate'], config) load_platform(hass, 'binary_sensor', DOMAIN, discovery_info['binary_sensor'], config) load_platform(hass, 'sensor', DOMAIN, From 7f48a280eefd4882bc1f25c7a4df32a4f6e37826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 9 Nov 2018 09:41:08 +0000 Subject: [PATCH 228/230] fix last device ignored (#18329) --- homeassistant/components/device_tracker/huawei_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/huawei_router.py b/homeassistant/components/device_tracker/huawei_router.py index f5e4fa8a714..18f3c0b8c62 100644 --- a/homeassistant/components/device_tracker/huawei_router.py +++ b/homeassistant/components/device_tracker/huawei_router.py @@ -39,7 +39,7 @@ Device = namedtuple('Device', ['name', 'ip', 'mac', 'state']) class HuaweiDeviceScanner(DeviceScanner): """This class queries a router running HUAWEI firmware.""" - ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);') + ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*)null\);') DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),') DEVICE_ATTR_REGEX = re.compile( '"(?P.*?)","(?P.*?)",' From 4324d8767319527edc08a7329bb0d9b878146bb0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 9 Nov 2018 15:42:17 +0100 Subject: [PATCH 229/230] Bumped version to 0.82.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 9a8678072f3..5bd6b7637ff 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 82 -PATCH_VERSION = '0b4' +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) From f7c99ada9daa65f79c38f5ca7bca0c2054b231e9 Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Sat, 10 Nov 2018 02:17:24 -0500 Subject: [PATCH 230/230] WeMo - Change name of discovery option (#18348) --- homeassistant/components/wemo.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 55e45bc210a..3804b019ad5 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -58,17 +58,17 @@ def coerce_host_port(value): CONF_STATIC = 'static' -CONF_DISABLE_DISCOVERY = 'disable_discovery' +CONF_DISCOVERY = 'discovery' -DEFAULT_DISABLE_DISCOVERY = False +DEFAULT_DISCOVERY = True CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_STATIC, default=[]): vol.Schema([ vol.All(cv.string, coerce_host_port) ]), - vol.Optional(CONF_DISABLE_DISCOVERY, - default=DEFAULT_DISABLE_DISCOVERY): cv.boolean + vol.Optional(CONF_DISCOVERY, + default=DEFAULT_DISCOVERY): cv.boolean }), }, extra=vol.ALLOW_EXTRA) @@ -141,9 +141,7 @@ def setup(hass, config): devices.append((url, device)) - disable_discovery = config.get(DOMAIN, {}).get(CONF_DISABLE_DISCOVERY) - - if not disable_discovery: + if config.get(DOMAIN, {}).get(CONF_DISCOVERY): _LOGGER.debug("Scanning for WeMo devices.") devices.extend( (setup_url_for_device(device), device)