diff --git a/.coveragerc b/.coveragerc index 0049349cfff..b64699f685f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -76,16 +76,13 @@ 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 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 @@ -129,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 @@ -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 @@ -248,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 @@ -290,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 @@ -334,7 +336,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 @@ -467,6 +468,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 @@ -500,6 +502,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 @@ -507,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 @@ -722,6 +726,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 @@ -759,7 +764,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/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000000..923a03f03dd --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,10 @@ +# .readthedocs.yml + +build: + image: latest + +python: + version: 3.6 + setup_py_install: true + +requirements_file: requirements_docs.txt diff --git a/CODEOWNERS b/CODEOWNERS index c49af4864a9..bf4c342b474 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -56,17 +56,21 @@ 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 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 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 @@ -86,6 +90,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 @@ -234,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/Dockerfile b/Dockerfile index c84e6162d04..4cd4f3e8871 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 @@ -27,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 . . 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/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/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/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/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/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/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index f88d81ab851..6b747689057 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1,26 +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, 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_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, - 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__) @@ -36,15 +43,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_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' @@ -71,12 +82,18 @@ 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" # 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" @@ -154,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. @@ -209,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 @@ -283,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' @@ -302,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' @@ -323,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 @@ -332,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' @@ -350,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 @@ -414,7 +570,68 @@ 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 + + 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 _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): + """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 @@ -625,6 +842,39 @@ class _SensorCapabilities(_AlexaEntity): yield _AlexaTemperatureSensor(self.hass, self.entity) +@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.""" + attrs = self.entity.attributes + if attrs.get(ATTR_DEVICE_CLASS) in ( + 'door', + 'garage_door', + 'opening', + 'window', + ): + return self.TYPE_CONTACT + if attrs.get(ATTR_DEVICE_CLASS) == 'motion': + return self.TYPE_MOTION + + class _Cause: """Possible causes for property changes. @@ -712,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. @@ -851,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 @@ -909,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: @@ -946,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, { @@ -964,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)) @@ -997,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)) @@ -1015,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, { @@ -1033,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, @@ -1042,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, { @@ -1056,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, @@ -1065,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} @@ -1091,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} @@ -1138,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, @@ -1188,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 [] @@ -1209,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, @@ -1221,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 @@ -1249,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, @@ -1275,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, @@ -1294,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 } @@ -1309,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 } @@ -1324,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 } @@ -1339,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 } @@ -1354,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 } @@ -1369,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): @@ -1415,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) @@ -1494,41 +1832,27 @@ 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, 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 api_message(request) + return 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/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/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/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/auth/__init__.py b/homeassistant/components/auth/__init__.py index 58be53d4122..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) @@ -424,54 +438,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 +500,28 @@ 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'], {})) + connection.send_message( + websocket_api.result_message(msg['id'], {})) - hass.async_create_task( - async_delete_refresh_token(connection.user, msg['refresh_token_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/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/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/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/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/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) 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 }) 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/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/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/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) 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/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.""" 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 722577fed39..d1afd39ca7b 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -218,10 +218,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() 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) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 258699ff90a..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 @@ -380,15 +385,19 @@ 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 - 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): """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() + await self._async_control_heating(force=True) await self.async_update_ha_state() 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/climate/mill.py b/homeassistant/components/climate/mill.py index 763e239689b..a533cc37fd3 100644 --- a/homeassistant/components/climate/mill.py +++ b/homeassistant/components/climate/mill.py @@ -8,29 +8,45 @@ 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, DOMAIN, PLATFORM_SCHEMA, STATE_HEAT, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, + SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE) 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.1.2'] +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) + SUPPORT_FAN_MODE | SUPPORT_ON_OFF | + SUPPORT_OPERATION_MODE) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, 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): @@ -43,13 +59,27 @@ async def async_setup_platform(hass, config, async_add_entities, _LOGGER.error("Failed to connect to Mill") return - await mill_data_connection.update_heaters() + await mill_data_connection.find_all_heaters() dev = [] for heater in mill_data_connection.heaters.values(): 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.""" @@ -79,6 +109,20 @@ 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, + "controlled_by_tibber": self._heater.tibber_control, + } + @property def temperature_unit(self): """Return the unit of measurement which this thermostat uses.""" @@ -124,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) @@ -151,3 +205,12 @@ 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) 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/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): 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/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/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/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/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/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/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/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/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 56b03c89a37..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'] @@ -43,11 +37,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' @@ -80,68 +74,34 @@ 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. - 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,128 +111,69 @@ 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] + gateway = 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 = 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/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/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/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/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 710a07f77d3..561d41562de 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.1.2'] _LOGGER = logging.getLogger(__name__) @@ -44,345 +40,53 @@ 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] - - 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) + from aioasuswrt.asuswrt import AsusWrt 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.""" # 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/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(): 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/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/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/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.*?)",' 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/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/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/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/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 new file mode 100644 index 00000000000..9e1cbbb636e --- /dev/null +++ b/homeassistant/components/dialogflow/.translations/en.json @@ -0,0 +1,18 @@ +{ + "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/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/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/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/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/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 = { 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/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 new file mode 100644 index 00000000000..0c570465f4d --- /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.async_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/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/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) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 17fa7a249ee..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==20181026.4'] +REQUIREMENTS = ['home-assistant-frontend==20181103.3'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 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/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 485b98e8e22..2f54ee33f77 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', 'vacuum', ] CLIMATE_MODE_HEATCOOL = 'heatcool' CLIMATE_SUPPORTED_MODES = {'heat', 'cool', 'off', 'on', CLIMATE_MODE_HEATCOOL} @@ -22,7 +23,9 @@ 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_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 1cb4bf4cb32..633e6258c03 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, TYPE_FAN, CONF_ALIASES, CONF_ROOM_HINT, ERR_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, ERR_UNKNOWN_ERROR @@ -36,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, @@ -44,6 +46,7 @@ DOMAIN_TO_GOOGLE_TYPES = { scene.DOMAIN: TYPE_SCENE, script.DOMAIN: TYPE_SCENE, switch.DOMAIN: TYPE_SWITCH, + vacuum.DOMAIN: TYPE_VACUUM, } @@ -213,7 +216,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/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/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/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 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): 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/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/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/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/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/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/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/homeassistant/components/image_processing/tensorflow.py b/homeassistant/components/image_processing/tensorflow.py new file mode 100644 index 00000000000..a2cd997bb76 --- /dev/null +++ b/homeassistant/components/image_processing/tensorflow.py @@ -0,0 +1,346 @@ +""" +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'] + +_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/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, 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 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/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/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/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/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/light/yeelight.py b/homeassistant/components/light/yeelight.py index 7efd62e3de5..15f2d24fa8a 100644 --- a/homeassistant/components/light/yeelight.py +++ b/homeassistant/components/light/yeelight.py @@ -276,10 +276,13 @@ 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 - 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/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.""" diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index c45791160ac..a8cde6a2b93 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,19 +1,23 @@ -"""Lovelace UI.""" +""" +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 -import uuid -import os -from os import O_CREAT, O_TRUNC, O_WRONLY -from collections import OrderedDict from typing import Dict, List, Union +import uuid import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.exceptions import HomeAssistantError +import homeassistant.util.ruamel_yaml as yaml _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 @@ -23,15 +27,23 @@ 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), + 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({ @@ -41,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({ @@ -58,13 +70,55 @@ 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), +}) + +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, +}) + +SCHEMA_DELETE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_DELETE_CARD, + 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), +}) -class WriteError(HomeAssistantError): - """Error writing the data.""" +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): @@ -75,132 +129,63 @@ class ViewNotFoundError(HomeAssistantError): """View 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 - 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) +class DuplicateIdError(HomeAssistantError): + """Duplicate ID's.""" 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 an 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', []): - if 'id' not in view: + 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 occurrences in views'.format( + view_id)) + seen_view_ids.add(view_id) for card in view.get('cards', []): - if 'id' not in card: + card_id = str(card.get('id', '')) + if not card_id: 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 occurrences in cards' + .format(card_id)) + 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 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( @@ -208,17 +193,18 @@ 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 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.clear() card.update(card_config) - save_yaml(fname, config) + yaml.save_yaml(fname, config) return raise CardNotFoundError( @@ -226,20 +212,164 @@ 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 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) -> None: + """Move a card to a different position.""" + if position is None: + 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', []): + if str(card.get('id', '')) != card_id: + continue + cards = view.get('cards') + cards.insert(position, cards.pop(cards.index(card))) + yaml.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) -> None: + """Move a card to a different view.""" + config = yaml.load_yaml(fname, True) + 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) + + yaml.save_yaml(fname, config) + + +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', []): + for card in view.get('cards', []): + if str(card.get('id', '')) != card_id: + continue + cards = view.get('cards') + cards.pop(cards.index(card)) + yaml.save_yaml(fname, config) + return + + raise CardNotFoundError( + "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( @@ -262,145 +392,180 @@ 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) + + 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) + + 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 +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) + """Send Lovelace UI config over WebSocket configuration.""" + 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) + """Migrate Lovelace UI configuration.""" + 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) + """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)) @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'], 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 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) + """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)) @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'], 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) + """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'), + msg.get('format', FORMAT_YAML)) - if error is not None: - message = websocket_api.error_message(msg['id'], *error) - connection.send_message(message) +@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.""" + 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')) + + 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.""" + 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/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/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/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/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/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/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py new file mode 100644 index 00000000000..e78dc0aa479 --- /dev/null +++ b/homeassistant/components/mailgun/__init__.py @@ -0,0 +1,100 @@ +""" +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 + +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' +_LOGGER = logging.getLogger(__name__) +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, + }), +}, 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.""" + 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): + """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://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 new file mode 100644 index 00000000000..c72ec747b30 --- /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/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." + } + } +} 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/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) 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/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.""" 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/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 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/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/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/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() 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 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 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( 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/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) 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..06dcd0e19b0 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.3b1'] _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/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index 1dc158bc2ab..bc29910a196 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -216,7 +216,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] = {} 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/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/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: 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/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( 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/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/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/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/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/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 diff --git a/homeassistant/components/sensor/darksky.py b/homeassistant/components/sensor/darksky.py index a6c602602f4..9a3ba45dfa1 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'] @@ -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, @@ -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: @@ -358,11 +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 @@ -371,7 +368,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 +377,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 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/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 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/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/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py index e1225b8f25d..2c917ba0f3b 100644 --- a/homeassistant/components/sensor/jewish_calendar.py +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -10,9 +10,11 @@ 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 import homeassistant.util.dt as dt_util REQUIREMENTS = ['hdate==0.6.5'] @@ -107,8 +109,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, SUN_EVENT_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/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() 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/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/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: 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() diff --git a/homeassistant/components/sensor/pollen.py b/homeassistant/components/sensor/pollen.py index 6df7047b353..818404aa3fe 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 = ['numpy==1.15.3', '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 = [{ @@ -82,12 +97,12 @@ RATING_MAPPING = [{ 'maximum': 12 }] -TREND_FLAT = 'Flat' 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 +115,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 +139,48 @@ def calculate_average_rating(indices): return max(set(ratings), key=ratings.count) -class PollencomSensor(Entity): - """Define a Pollen.com sensor.""" +def calculate_trend(indices): + """Calculate the "moving average" of a set of indices.""" + import numpy as np - def __init__(self, pollencom, zip_code, kind, category, name, icon, unit): + 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.""" + + 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 +205,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 +232,85 @@ 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 - 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: calculate_trend(indices), + 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) + + self._attrs.update({ + ATTR_CITY: data['City'].title(), + ATTR_RATING: calculate_average_rating(indices), + ATTR_STATE: data['State'], + ATTR_TREND: calculate_trend(indices), + 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/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 = [] 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)]), }) 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/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py index dd5209a4e0a..acf1ead186c 100644 --- a/homeassistant/components/sensor/sma.py +++ b/homeassistant/components/sensor/sma.py @@ -5,100 +5,100 @@ 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, 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__) -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'] +GROUPS = ['user', 'installer'] def _check_sensor_schema(conf): """Check sensors and attributes are valid.""" - 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)) + try: + import pysma + valid = [s.name for s in pysma.SENSORS] + except (ImportError, AttributeError): + return conf + + 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)) 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=15)), + 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.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}), - 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 - # 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)])) + # 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(): - 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) - grp = {GROUP_INSTALLER: pysma.GROUP_INSTALLER, - GROUP_USER: pysma.GROUP_USER}[config[CONF_GROUP]] + session = async_get_clientsession(hass, verify_ssl=config[CONF_VERIFY_SSL]) + grp = config[CONF_GROUP] url = "http{}://{}".format( "s" if config[CONF_SSL] else "", config[CONF_HOST]) @@ -112,30 +112,29 @@ async def async_setup_platform(hass, config, async_add_entities, 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 + 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(keys_to_query) - if values is None: - backoff = 3 + values = await sma.read(used_sensors) + if not values: + try: + backoff = [1, 1, 1, 6, 30][backoff_step] + backoff_step += 1 + except IndexError: + backoff = 60 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) + backoff_step = 0 + tasks = [] for sensor in hass_sensors: - task = sensor.async_update_values(res) + task = sensor.async_update_values() if task: tasks.append(task) if tasks: @@ -146,20 +145,20 @@ 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): + 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): @@ -169,7 +168,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): @@ -181,19 +180,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/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/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/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/sensor/version.py b/homeassistant/components/sensor/version.py index eba4b1b8350..11cb6832e40 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.2'] _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/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() 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/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/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/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/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/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/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/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/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/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/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 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/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py index b7585588f0a..ea7aded3e16 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 @@ -202,7 +202,7 @@ class FluxSwitch(SwitchDevice): now = as_local(utcnow) - 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) @@ -285,7 +285,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/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/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/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/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/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 diff --git a/homeassistant/components/tradfri/.translations/pt.json b/homeassistant/components/tradfri/.translations/pt.json index e89cb6ac620..d728bc32f0b 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 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/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' 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', diff --git a/homeassistant/components/twilio.py b/homeassistant/components/twilio.py deleted file mode 100644 index b32f9ee8efc..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 Client - conf = config[DOMAIN] - hass.data[DATA_TWILIO] = Client( - 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/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/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/twilio/__init__.py b/homeassistant/components/twilio/__init__.py new file mode 100644 index 00000000000..c28f56a4b6c --- /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 Client + if DOMAIN not in config: + return True + + conf = config[DOMAIN] + hass.data[DATA_TWILIO] = Client( + 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/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/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(), } 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/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 07c8d5f748e..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.components.discovery import DOMAIN as DISCOVERY_DOMAIN from .const import ( CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS, @@ -26,12 +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'] -DEPENDENCIES = ['http'] +REQUIREMENTS = ['async-upnp-client==0.13.0'] NOTIFICATION_ID = 'upnp_notification' NOTIFICATION_TITLE = 'UPnP/IGD Setup' @@ -50,18 +48,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 @@ -69,18 +86,14 @@ def _substitute_hass_ports(ports, hass_port): # 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: 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] @@ -104,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 @@ -119,13 +132,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 +150,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/config_flow.py b/homeassistant/components/upnp/config_flow.py index f695e3ada75..1a6526638ba 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,10 +1,12 @@ """Config flow for UPNP.""" +import logging from collections import OrderedDict 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 +15,10 @@ from .const import ( from .const import DOMAIN -def ensure_domain_data(hass): +_LOGGER = logging.getLogger(__name__) + + +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 +29,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,16 +72,27 @@ 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) + + 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? @@ -91,7 +110,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 +151,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/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/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/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 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) 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/homeassistant/components/velbus.py b/homeassistant/components/velbus.py index 2304054c404..a7b385297a8 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__) 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: 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/components/wemo.py b/homeassistant/components/wemo.py index 9664ca9419a..3804b019ad5 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', @@ -57,12 +58,17 @@ def coerce_host_port(value): CONF_STATIC = 'static' +CONF_DISCOVERY = 'discovery' + +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_DISCOVERY, + default=DEFAULT_DISCOVERY): cv.boolean }), }, extra=vol.ALLOW_EXTRA) @@ -77,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) @@ -135,13 +141,14 @@ 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()) + if config.get(DOMAIN, {}).get(CONF_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, 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' 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 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 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": { diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 35703d64974..a27d2112dcd 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, @@ -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__) @@ -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: @@ -387,7 +393,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( @@ -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/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/config_entries.py b/homeassistant/config_entries.py index c1c0fbbf775..eaef97011fc 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -137,12 +137,14 @@ HANDLERS = Registry() FLOWS = [ 'cast', 'deconz', + 'dialogflow', 'hangouts', 'homematicip_cloud', 'hue', 'ifttt', 'ios', 'lifx', + 'mailgun', 'mqtt', 'nest', 'openuv', @@ -150,6 +152,7 @@ FLOWS = [ 'smhi', 'sonos', 'tradfri', + 'twilio', 'unifi', 'upnp', 'zone', diff --git a/homeassistant/const.py b/homeassistant/const.py index 8d37a48c0a3..5bd6b7637ff 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,8 +1,8 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 81 -PATCH_VERSION = '6' +MINOR_VERSION = 82 +PATCH_VERSION = '0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) @@ -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' 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_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/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/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/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/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/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/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/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 4a3f915e810..61dc49ce2ec 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 @@ -40,13 +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. @@ -102,43 +106,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 +193,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 +205,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 +217,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/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/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 4650a4d92c2..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, @@ -31,6 +32,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 +61,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) @@ -614,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/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/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 193bb42dba0..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 @@ -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..eb3e935c6ce --- /dev/null +++ b/homeassistant/util/ruamel_yaml.py @@ -0,0 +1,135 @@ +"""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) + 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) + 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 2ffd2ad6b21..411fbae9911 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 @@ -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 @@ -30,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 @@ -87,6 +85,9 @@ abodepy==0.14.0 # homeassistant.components.media_player.frontier_silicon afsapi==0.0.4 +# homeassistant.components.device_tracker.asuswrt +aioasuswrt==1.1.2 + # homeassistant.components.device_tracker.automatic aioautomatic==0.6.5 @@ -94,7 +95,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 @@ -139,6 +140,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 @@ -153,13 +157,10 @@ asterisk_mbox==0.5.0 # homeassistant.components.upnp # homeassistant.components.media_player.dlna_dmr -async-upnp-client==0.12.7 - -# homeassistant.components.light.avion -# avion==0.7 +async-upnp-client==0.13.0 # homeassistant.components.axis -axis==14 +axis==16 # homeassistant.components.tts.baidu baidu-aip==1.6.6 @@ -228,6 +229,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 @@ -306,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 @@ -403,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 @@ -426,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 @@ -466,7 +476,10 @@ hole==0.3.0 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.4 +home-assistant-frontend==20181103.3 + +# homeassistant.components.zwave +homeassistant-pyozw==0.1.0 # homeassistant.components.homekit_controller # homekit==0.10 @@ -572,7 +585,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 @@ -598,6 +611,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 @@ -606,7 +622,7 @@ mficlient==0.3.0 miflora==0.4.0 # homeassistant.components.climate.mill -millheater==0.1.2 +millheater==0.2.2 # homeassistant.components.sensor.mitemp_bt mitemp_bt==0.0.1 @@ -634,7 +650,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 @@ -656,7 +672,9 @@ nuheat==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.15.2 +# homeassistant.components.image_processing.tensorflow +# homeassistant.components.sensor.pollen +numpy==1.15.3 # homeassistant.components.google oauth2client==4.0.0 @@ -690,14 +708,13 @@ 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 pexpect==4.6.0 # homeassistant.components.rpi_pfio -pifacecommon==4.1.2 +pifacecommon==4.2.2 # homeassistant.components.rpi_pfio pifacedigitalio==3.0.5 @@ -709,6 +726,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 @@ -734,8 +752,11 @@ 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.7 +psutil==5.4.8 # homeassistant.components.wink pubnubsub-handler==1.0.2 @@ -760,7 +781,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 @@ -910,6 +931,9 @@ pygtfs-homeassistant==0.1.3.dev0 # homeassistant.components.remote.harmony pyharmony==1.0.20 +# homeassistant.components.sensor.version +pyhaversion==2.0.2 + # homeassistant.components.binary_sensor.hikvision pyhik==0.1.8 @@ -1005,7 +1029,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 @@ -1030,7 +1054,7 @@ pyoppleio==1.0.5 pyota==2.0.5 # homeassistant.components.opentherm_gw -pyotgw==0.2b1 +pyotgw==0.3b1 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp @@ -1045,7 +1069,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 @@ -1075,7 +1099,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 @@ -1152,7 +1176,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 @@ -1195,7 +1219,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 @@ -1206,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 @@ -1285,13 +1306,13 @@ 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 # homeassistant.components.ring -ring_doorbell==0.2.1 +ring_doorbell==0.2.2 # homeassistant.components.device_tracker.ritassist ritassist==0.9.2 @@ -1305,9 +1326,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 @@ -1336,8 +1354,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 @@ -1349,7 +1367,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 @@ -1404,7 +1422,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 @@ -1552,7 +1570,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 @@ -1562,9 +1580,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 @@ -1579,7 +1594,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 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 diff --git a/requirements_test.txt b/requirements_test.txt index 492708cd904..68248a47cdb 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,14 +4,14 @@ 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.630 +mypy==0.641 pydocstyle==2.1.1 pylint==2.1.1 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.3 requests_mock==1.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e7d00f75799..2b0e151feba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -5,16 +5,16 @@ 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.630 +mypy==0.641 pydocstyle==2.1.1 pylint==2.1.1 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.3 requests_mock==1.5.2 @@ -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 @@ -97,7 +97,7 @@ hdate==0.6.5 holidays==0.9.8 # homeassistant.components.frontend -home-assistant-frontend==20181026.4 +home-assistant-frontend==20181103.3 # homeassistant.components.homematicip_cloud homematicip==0.9.8 @@ -118,14 +118,15 @@ mficlient==0.3.0 # homeassistant.components.binary_sensor.trend # homeassistant.components.image_processing.opencv -numpy==1.15.2 +# homeassistant.components.image_processing.tensorflow +# homeassistant.components.sensor.pollen +numpy==1.15.3 # homeassistant.components.mqtt # homeassistant.components.shiftr 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 @@ -207,22 +208,19 @@ pyunifi==2.13 pywebpush==1.6.0 # homeassistant.components.python_script -restrictedpython==4.0b5 +restrictedpython==4.0b6 # homeassistant.components.rflink rflink==0.0.37 # homeassistant.components.ring -ring_doorbell==0.2.1 - -# homeassistant.components.lovelace -ruamel.yaml==0.15.72 +ring_doorbell==0.2.2 # homeassistant.components.media_player.yamaha rxv==0.5.1 # homeassistant.components.simplisafe -simplisafe-python==3.1.12 +simplisafe-python==3.1.13 # homeassistant.components.sleepiq sleepyq==0.6 @@ -236,7 +234,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 @@ -255,6 +253,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 491531ee12b..97711b5e893 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', @@ -106,7 +106,6 @@ TEST_REQUIREMENTS = ( 'statsd', 'uvcclient', 'warrant', - 'yahoo-finance', 'pythonwhois', 'wakeonlan', 'vultr', diff --git a/setup.py b/setup.py index 5bca2cc43db..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', @@ -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/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 diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 02085a44b47..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' @@ -35,11 +34,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 +46,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 +69,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 +96,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 +104,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 +127,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 +150,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 +176,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 +203,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 +211,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 +234,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 +257,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 +287,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 +300,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 +341,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 +355,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 +383,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 +395,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 +418,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 +441,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 +477,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 +489,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 +521,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 +533,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 +565,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 +577,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 +618,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 +633,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 +674,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 +683,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 +699,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 +713,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 +729,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 +743,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 +759,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 +775,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 +791,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 +800,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 +813,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 +832,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 +848,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 +874,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 +893,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 +908,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 +927,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 +940,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 +965,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 +978,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 +1003,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 +1018,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 +1055,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 +1068,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 +1093,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 +1107,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 +1145,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 +1168,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 +1196,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 +1204,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 +1228,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 +1244,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 +1264,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 +1298,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 +1317,50 @@ 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 + + +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 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/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 88c69194407..186a35c19ec 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 @@ -58,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' @@ -79,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' @@ -105,25 +106,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 +155,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 +211,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 +223,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 +235,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 +253,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 +267,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 +289,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 +331,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 +349,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 +363,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 +375,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 +391,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 +413,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 +423,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 +438,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 +498,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 +586,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 +598,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 +620,7 @@ def test_cover(hass): 'position') -@asyncio.coroutine -def assert_percentage_changes( +async def assert_percentage_changes( hass, adjustments, namespace, @@ -657,15 +639,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 +656,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,20 +670,79 @@ 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_unknown_sensor(hass): +async def test_contact_sensor(hass): + """Test contact sensor discovery.""" + device = ( + 'binary_sensor.test_contact', + 'on', + { + 'friendly_name': "Test Contact Sensor", + 'device_class': 'door', + } + ) + appliance = await 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 = await reported_properties(hass, + 'binary_sensor#test_contact') + properties.assert_equal('Alexa.ContactSensor', 'detectionState', + 'DETECTED') + + +async def test_motion_sensor(hass): + """Test motion sensor discovery.""" + device = ( + 'binary_sensor.test_motion', + 'on', + { + 'friendly_name': "Test Motion Sensor", + 'device_class': 'motion', + } + ) + appliance = await 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 = await reported_properties(hass, + 'binary_sensor#test_motion') + properties.assert_equal('Alexa.MotionSensor', 'detectionState', + 'DETECTED') + + +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): @@ -747,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', @@ -763,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, @@ -776,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', @@ -799,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', @@ -815,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', @@ -840,9 +917,16 @@ 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): + +async def test_exclude_filters(hass): """Test exclusion filters.""" request = get_new_request('Alexa.Discovery', 'Discover') @@ -863,16 +947,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') @@ -896,24 +979,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'] @@ -924,11 +1006,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 @@ -939,8 +1020,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, @@ -955,9 +1035,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 @@ -966,8 +1046,7 @@ def assert_request_fails( return msg -@asyncio.coroutine -def assert_request_calls_service( +async def assert_request_calls_service( namespace, name, endpoint, @@ -984,9 +1063,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] @@ -998,26 +1077,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') @@ -1025,7 +1107,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') @@ -1034,10 +1116,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') @@ -1053,9 +1134,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'] @@ -1066,8 +1147,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') @@ -1088,9 +1168,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'] @@ -1101,8 +1181,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', @@ -1117,9 +1196,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'] @@ -1130,9 +1209,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', @@ -1147,9 +1225,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'] @@ -1160,9 +1238,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', @@ -1177,9 +1254,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'] @@ -1190,8 +1267,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, {}) @@ -1200,18 +1276,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", @@ -1220,24 +1295,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']) @@ -1259,8 +1333,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') @@ -1278,7 +1351,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 @@ -1296,15 +1369,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 @@ -1313,22 +1385,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': { @@ -1336,26 +1406,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), @@ -1364,7 +1432,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', { @@ -1375,14 +1443,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, @@ -1438,3 +1506,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' 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/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 09d237013b0..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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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 130cdeef99c..946c9a8abc6 100644 --- a/tests/components/automation/test_geo_location.py +++ b/tests/components/automation/test_geo_location.py @@ -1,271 +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() - 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']) + 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() - self.assertEqual(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() - self.assertEqual(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() - 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']) + 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 3bcbc7da04f..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() - self.assertEqual(1, len(self.calls)) - self.assertEqual(['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() - self.assertEqual(1, len(self.calls)) - self.assertEqual(['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() - self.assertEqual(1, len(self.calls)) - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - self.assertEqual(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() - self.assertEqual(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)) - - self.hass.states.set(entity_id, 151) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - self.assertEqual(1, len(self.calls)) - - def test_automation_list_setting(self): - """Event is not a valid condition.""" - self.assertTrue(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() - self.assertEqual(1, len(self.calls)) - - self.hass.bus.fire('test_event_2') - self.hass.block_till_done() - self.assertEqual(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, { - 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 29a53467c4f..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() - self.assertEqual(1, len(self.calls)) - self.assertEqual('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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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 183d1f4a5f9..92a5f3b8b92 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,878 +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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - self.hass.states.set('test.entity_2', 9) - self.hass.block_till_done() - self.assertEqual(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() - self.assertEqual(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)) - - # 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)) - - 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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)) - - 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - 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']) - - 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() - self.assertEqual(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() - - self.assertEqual(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)) - - self.hass.states.set(entity_id, 9) - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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)) - - 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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - '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 15c6353b234..abe02638f26 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,621 +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() - self.assertEqual(1, len(self.calls)) - assert self.calls[0].context is context - self.assertEqual( - '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)) - - 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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) - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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)) - - 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() - self.assertEqual(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)) - - 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() - self.assertEqual(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() - self.assertEqual(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)) - - 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() - self.assertEqual(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() - self.assertEqual(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)) - - 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() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - '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 ad8709fdf36..030662da05c 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -1,322 +1,320 @@ """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.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 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': 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': 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': 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - self.assertEqual('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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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': 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() - self.assertEqual(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() - self.assertEqual(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': SUN_EVENT_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() - self.assertEqual(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() - self.assertEqual(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': SUN_EVENT_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() - self.assertEqual(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() - self.assertEqual(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': SUN_EVENT_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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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': SUN_EVENT_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': SUN_EVENT_SUNRISE, + 'before': SUN_EVENT_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 4fec0e707a9..c326c7f03f4 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': 'true', + }, + '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,390 +382,55 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - self.assertEqual(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)) - - 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - - # Trigger again - self.hass.states.set('test.entity', 'world') - self.hass.block_till_done() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - assert self.calls[0].context is context - self.assertEqual( - '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() - self.assertEqual(0, len(self.calls)) - - # Different state - self.hass.states.set('test.entity', 'hello') - self.hass.block_till_done() - self.assertEqual(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() - self.assertEqual(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)) - - # Condition is true and event is triggered - self.hass.bus.fire('test_event') - self.hass.block_till_done() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - self.assertEqual( - '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 dcb723d725e..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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) - self.assertEqual('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() - self.assertEqual(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() - self.assertEqual(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() - - self.assertEqual(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)) - - 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() - - self.assertEqual(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)) - - 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() - - self.assertEqual(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)) - - 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() - - self.assertEqual(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)) - - 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(calls) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 795f55a3e0b..04ffeaf13aa 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,219 +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() - self.assertEqual(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 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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(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() - self.assertEqual(1, len(self.calls)) + hass.bus.async_fire('test_event') + await hass.async_block_till_done() + assert 1 == len(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_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/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..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( @@ -168,18 +192,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/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_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 47ec621aeb5..3d30a21504a 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -1,13 +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, CoreState, State -from homeassistant.setup import setup_component, async_setup_component +from homeassistant.core import ( + callback, DOMAIN as HASS_DOMAIN, CoreState, State) +from homeassistant.setup import async_setup_component from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -19,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 @@ -39,122 +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.""" - self.assertTrue( - 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 - self.assertTrue(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 }}) - self.assertEqual(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) - - 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 - }}) - - self.assertEqual(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) - - 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, @@ -162,221 +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.""" - self.assertEqual(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')) - - 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) - - 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')) - 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')) - - 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) - self.assertEqual(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) - 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) - temp = state.attributes.get('current_temperature') - - self._setup_sensor(None) - self.hass.block_till_done() - - state = self.hass.states.get(ENTITY) - self.assertEqual(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() - 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 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() - self.assertEqual(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']) - - 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() - self.assertEqual(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() - 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 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() - self.assertEqual(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() - 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_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() - 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_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() - self.assertEqual(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) - - 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() - 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 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, @@ -385,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() - self.assertEqual(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']) - - 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) - self.assertEqual(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() - 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 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() - 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 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() - self.assertEqual(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() - 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_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() - self.assertEqual(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() - 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 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() - 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_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() - self.assertEqual(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, @@ -548,90 +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() - self.assertEqual(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() - 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 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() - self.assertEqual(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() - 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 _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, @@ -639,90 +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() - self.assertEqual(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() - self.assertEqual(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() - 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 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() - 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 _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, @@ -731,89 +869,70 @@ 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) - }}) - - 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() - self.assertEqual(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)) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - 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 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() - self.assertEqual(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)) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - 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 _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, @@ -821,91 +940,66 @@ 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) - }}) - - 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() - self.assertEqual(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)) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - 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 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() - self.assertEqual(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)) - self._send_time_changed(test_time + datetime.timedelta(minutes=10)) - 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 _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', @@ -919,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) - self.assertEqual(STATE_HEAT, - state_heat.attributes.get('operation_mode')) - self.assertEqual(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) - self.assertEqual(STATE_HEAT, - state_heat.attributes.get('operation_mode')) - self.assertEqual(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) - self.assertEqual(STATE_OFF, - state_heat.attributes.get('operation_mode')) - self.assertEqual(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) - self.assertEqual(STATE_OFF, - state_heat.attributes.get('operation_mode')) - self.assertEqual(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', @@ -1000,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", @@ -1010,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', @@ -1024,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. @@ -1037,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', @@ -1051,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) - 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)) +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) - self.assertEqual(STATE_OFF, - state.attributes[climate.ATTR_OPERATION_MODE]) - self.assertEqual(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"}), + )) 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..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,251 +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.""" - self.assertEqual("Melissa 12345678", self.thermostat.name) - def test_is_on(self): - """Test name property.""" - self.assertTrue(self.thermostat.is_on) - self.thermostat._cur_settings = None - self.assertFalse(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() - self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) - self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.current_fan_mode) - def test_current_temperature(self): - """Test current temperature.""" - self.assertEqual(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 - self.assertIsNone(self.thermostat.current_temperature) + thermostat._cur_settings = None + assert not thermostat.is_on - def test_target_temperature_step(self): - """Test current target_temperature_step.""" - self.assertEqual(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) - self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.current_operation) +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.""" - self.assertEqual( - [STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT], - self.thermostat.operation_list - ) + thermostat._cur_settings = None + assert thermostat.current_fan_mode is None - def test_fan_list(self): - """Test the fan list.""" - self.assertEqual( - [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) - self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.target_temperature) +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 - def test_state(self): - """Test state.""" - self.assertEqual(STATE_ON, self.thermostat.state) - self.thermostat._cur_settings = None - self.assertEqual(None, self.thermostat.state) - def test_temperature_unit(self): - """Test temperature unit.""" - self.assertEqual(TEMP_CELSIUS, self.thermostat.temperature_unit) +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 - def test_min_temp(self): - """Test min temp.""" - self.assertEqual(16, self.thermostat.min_temp) - def test_max_temp(self): - """Test max temp.""" - self.assertEqual(30, self.thermostat.max_temp) +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 - def test_supported_features(self): - """Test supported_features property.""" + +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] == \ + thermostat.operation_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] == \ + thermostat.fan_list + + +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 + + thermostat._cur_settings = None + assert thermostat.target_temperature is None + + +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 + + 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) - self.assertEqual(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}) - self.assertEqual(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) +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) - self.assertEqual(STATE_COOL, self.thermostat.current_operation) - def test_turn_on(self): - """Test turn_on.""" - self.thermostat.turn_on() - self.assertTrue(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() - self.assertEqual(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) - 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) +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() - self.assertEqual(SPEED_LOW, self.thermostat.current_fan_mode) - self.assertEqual(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.""" - 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)) +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.""" - 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)) - 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)) +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.""" - 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)) - 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.""" - 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)) - 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/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/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/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_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/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index 282b1d2873f..81c0848c4c5 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,65 +44,119 @@ 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)) - - 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) - - 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) - - 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_UNKNOWN == state.state + assert not state.attributes.get(ATTR_ASSUMED_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_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.""" - 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', 'qos': 0, - 'value_template': '{{ (value | multiply(0.01)) | int }}', + 'value_template': '\ + {% if (value | multiply(0.01) | int) == 0 %}\ + closed\ + {% else %}\ + open\ + {% endif %}' + } + }) + + state = self.hass.states.get('cover.test') + 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') + 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') + 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, 'state-topic', '10000') + 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, 'state-topic', '99') + 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') @@ -110,18 +164,18 @@ class TestCoverMQTT(unittest.TestCase): 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 +186,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 +196,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 +208,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 +221,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 +233,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 +246,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 +258,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,101 +271,106 @@ 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', - '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' } - })) + }) 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') + 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'] - self.assertEqual(0, current_cover_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'] - self.assertEqual(50, current_cover_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'] - self.assertEqual(50, current_cover_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'] - 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', - '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' } - })) + }) 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) + assert 4 & self.hass.states.get( + 'cover.test').attributes['supported_features'] == 4 - self.assertTrue(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 - 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', - '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', 'payload_stop': 'STOP' } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_POSITION, @@ -319,22 +378,22 @@ 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.""" - 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', + 'position_topic': 'state-topic', 'command_topic': 'command-topic', 'set_position_topic': 'position-topic', 'payload_open': 'OPEN', 'payload_close': 'CLOSE', 'payload_stop': 'STOP' } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_POSITION, @@ -346,7 +405,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 +416,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 +435,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 +455,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 +480,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 +501,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 +516,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 +537,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 +552,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 +587,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 +627,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 +640,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 +657,7 @@ class TestCoverMQTT(unittest.TestCase): 'tilt_min': 0, 'tilt_max': 50 } - })) + }) self.hass.services.call( cover.DOMAIN, SERVICE_SET_COVER_TILT_POSITION, @@ -612,108 +671,228 @@ 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) - self.assertEqual(44, mqtt_cover.find_percentage_in_range(44)) + 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) - self.assertEqual(40, mqtt_cover.find_percentage_in_range(120)) + 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) - self.assertEqual(56, mqtt_cover.find_percentage_in_range(44)) + 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) - self.assertEqual(60, mqtt_cover.find_percentage_in_range(120)) + 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) - self.assertEqual(44, mqtt_cover.find_in_range_from_percent(44)) + 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) - self.assertEqual(120, mqtt_cover.find_in_range_from_percent(40)) + 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) - self.assertEqual(44, mqtt_cover.find_in_range_from_percent(56)) + 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) - self.assertEqual(120, mqtt_cover.find_in_range_from_percent(60)) + 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.""" - 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 +900,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 +929,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/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 cfda1232e93..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.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_setup_component from homeassistant.components import deconz -from tests.common import mock_coro +from tests.common import mock_coro, MockConfigEntry + CONFIG = { "config": { @@ -99,122 +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" - } +async def test_service_configure(hass): + """Test that service invokes pydeconz with the correct path and data.""" + 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 + + hass.data[deconz.DOMAIN].deconz_ids = { + 'light.test': '/light/1' } - 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) + data = {'on': True, 'attr1': 10, 'attr2': 20} + + # only field + 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() + + # only entity + 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() + + # entity + field + 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() + + # non-existing entity (or not from deCONZ) + 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() + + # field does not start with / + 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() - 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): +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 - async_dispatcher_send(hass, 'deconz_new_sensor', [remote]) - await hass.async_block_till_done() - assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0 + 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/device_tracker/test_asuswrt.py b/tests/components/device_tracker/test_asuswrt.py index 8c5af618288..09f14dc9700 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): @@ -205,379 +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])) - - @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) + 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 c2c39e17f64..93de359610f 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/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..b6c62a2411b --- /dev/null +++ b/tests/components/dialogflow/test_init.py @@ -0,0 +1,535 @@ +"""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" + + +@pytest.fixture +async def calls(hass, fixture): + """Return a list of Dialogflow calls triggered.""" + calls = [] + + @callback + def mock_service(call): + """Mock action call.""" + calls.append(call) + + 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": {}, + }) + 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, calls): + """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/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/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 48704ca4464..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() - self.assertTrue(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.""" - self.assertEqual(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() - self.assertNotEqual(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() - self.assertEqual(STATE_ON, self.get_entity().state) - self.assertEqual(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.""" - self.assertEqual(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) +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() - self.assertEqual(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.""" - self.assertEqual(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() - self.assertNotEqual(STATE_OFF, self.get_entity().state) - common.turn_off(self.hass) - self.hass.block_till_done() - self.assertEqual(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.""" - self.assertEqual(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() - self.assertEqual(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.""" - self.assertEqual(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')) +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.""" - self.assertFalse(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() - self.assertTrue(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')) +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.""" - self.assertFalse(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() - self.assertTrue(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_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..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') - self.assertEqual(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') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) - self.assertFalse(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertNotEqual(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') - self.assertEqual(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') - self.assertNotEqual(STATE_UNAVAILABLE, state.state) - self.assertFalse(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertNotEqual(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): 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() 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..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,18 +79,18 @@ 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 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") - self.assertIsNotNone(state) + state = hass.states.get("geo_location.title_1") + assert state is not None assert state.name == "Title 1" assert state.attributes == { ATTR_EXTERNAL_ID: "1234", ATTR_LATITUDE: -31.0, @@ -120,10 +106,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) + state = hass.states.get("geo_location.title_2") + assert state is not None assert state.name == "Title 2" assert state.attributes == { ATTR_EXTERNAL_ID: "2345", ATTR_LATITUDE: -31.1, @@ -131,10 +117,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) + state = hass.states.get("geo_location.title_3") + assert state is not None assert state.name == "Title 3" assert state.attributes == { ATTR_EXTERNAL_ID: "3456", ATTR_LATITUDE: -31.2, @@ -142,34 +128,34 @@ 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 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 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', diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 52dac7ddb61..a347b6c6fc0 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,8 @@ from homeassistant.components import ( scene, script, switch, + vacuum, + group, ) from homeassistant.components.google_assistant import trait, helpers, const from homeassistant.util import color @@ -105,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)) @@ -141,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)) @@ -178,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)) @@ -214,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)) @@ -250,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)) @@ -286,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)) @@ -357,6 +359,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) 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/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' 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 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/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 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/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..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', - } - }) - self.assertIsNone(self.hass.states.get('light.test')) - - 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') - 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')) - - 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')) - - 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') - 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)) - - 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')) - - 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) - - 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() - self.assertEqual(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']) - - 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']) - - 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() - self.assertEqual(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') - self.assertEqual((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')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(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')) - - 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) - - 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() - self.assertEqual(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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(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')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('white_value')) - self.assertFalse(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')) - - 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) - - 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() - self.assertEqual(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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertIsNone(state.attributes.get('rgb_color')) - - 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') - 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')) - - 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')) - - 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')) - - 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') - 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)) - - 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') - self.assertEqual(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') - self.assertEqual(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') - 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']) - - 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') - self.assertEqual(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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('color_temp')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('effect')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('hs_color')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('white_value')) - - 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')) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('xy_color')) - - 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')) - - 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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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.""" - self.assertTrue(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') - self.assertEqual(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) - - 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) - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - self.assertTrue(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') - self.assertEqual(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) - - 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) + state = hass.states.get('light.test') + 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 f388c638b0d..03a3927472a 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,576 +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', - } - }) - self.assertIsNone(self.hass.states.get('light.test')) + 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') - 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')) - - 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')) - - 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') - 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)) - - # 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') - 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')) - - # 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) - - 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() - self.assertEqual(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') - self.assertEqual((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') - self.assertEqual((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') - self.assertEqual((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') - self.assertEqual(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') - self.assertEqual('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') - self.assertEqual(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') - 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)) - - 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') - self.assertEqual(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') - self.assertEqual(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]) - # 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"]) - - 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']) - - # 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]) - # 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({ - 'r': 0, - 'g': 255, - 'b': 21, - }, message_json["color"]) - self.assertEqual("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']) - - 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]) - self.assertEqual("ON", message_json["state"]) - self.assertEqual({ - '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') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(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]) - # 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"]) - - 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]) - # 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"]) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertEqual(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]) - # 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"]) - - # 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]) - # 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"]) - - 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') - self.assertEqual(STATE_OFF, state.state) - self.assertIsNone(state.attributes.get('brightness')) - self.assertFalse(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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(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') - 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)) - - # 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') - 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')) - - # 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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual((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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(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') - self.assertEqual(STATE_ON, state.state) - self.assertEqual(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, { - 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') - self.assertEqual(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) - - 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) - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - self.assertTrue(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') - self.assertEqual(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) - - 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) + 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): diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py index 731e7cd4e45..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', - } - }) - self.assertIsNone(self.hass.states.get('light.test')) + 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') - 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)) + 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') - 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')) +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') - 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)) - # 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') - 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')) - - # 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) - - # 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() - self.assertEqual(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() - self.assertEqual(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')) - - # 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() - self.assertEqual(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') - self.assertEqual('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') - 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)) - - # 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') - self.assertEqual(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') - self.assertEqual(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') - 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']) - - 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') - self.assertEqual(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') - self.assertEqual(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') - 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)) - - # 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') - 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')) - - # 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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual((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') - self.assertEqual(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') - self.assertEqual('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, { +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') - self.assertEqual(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') - self.assertNotEqual(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) - - def test_custom_availability_payload(self): - """Test availability by custom payload with defined topic.""" - self.assertTrue(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') - self.assertEqual(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') - self.assertNotEqual(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') - self.assertEqual(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 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 ac0a83d4e2d..347005c75ac 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') - self.assertEqual(STATE_UNLOCKED, state.state) - self.assertFalse(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(STATE_UNLOCKED, state.state) - self.assertTrue(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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') - self.assertEqual(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, { - 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') - self.assertEqual(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') - self.assertNotEqual(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') - self.assertEqual(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, { - 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') - self.assertEqual(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) - - 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) + state = hass.states.get('lock.test') + assert state.state is STATE_UNAVAILABLE 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 c728d1c581c..e296d14c6f8 100644 --- a/tests/components/lovelace/test_init.py +++ b/tests/components/lovelace/test_init.py @@ -1,16 +1,12 @@ """Test the Lovelace initialization.""" -import os -import unittest from unittest.mock import patch -from tempfile import mkdtemp 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 @@ -58,6 +54,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 @@ -116,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) - self.assertEqual(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) - self.assertEqual(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): - 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): @@ -229,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): @@ -286,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): @@ -314,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, @@ -326,7 +293,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): @@ -335,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, @@ -355,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, @@ -367,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): @@ -376,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, @@ -402,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, @@ -424,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, @@ -439,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): @@ -448,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, @@ -474,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, @@ -493,3 +460,289 @@ 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 move_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/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 move_card to 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/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 move_card to view with position 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/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'] + + +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.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/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'] + + +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'] 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..6e84c68e980 --- /dev/null +++ b/tests/components/mailgun/test_init.py @@ -0,0 +1,231 @@ +"""Test the init file of Mailgun.""" +import hashlib +import hmac +from unittest.mock import Mock + +import pytest + +from homeassistant import data_entry_flow +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' + + +@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 + + 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.""" + events.append(event) + + hass.bus.async_listen(mailgun.MESSAGE_RECEIVED, handle_event) + + return events + + +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' 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..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 = { @@ -102,7 +103,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 +112,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 +126,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 +138,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 +152,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 +162,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 +206,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 +247,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 +259,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 +267,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 a18af7ba684..57397e21ba2 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): @@ -84,7 +83,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.""" @@ -94,8 +93,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.""" @@ -103,7 +102,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', @@ -129,7 +128,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': { @@ -159,7 +158,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', @@ -172,9 +171,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_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/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..ccfe4344373 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()) == 9 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_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/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..47874d1da42 100644 --- a/tests/components/sensor/test_jewish_calendar.py +++ b/tests/components/sensor/test_jewish_calendar.py @@ -1,29 +1,29 @@ """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 +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 -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 + set_default_time_zone(get_time_zone('UTC')) def test_jewish_calendar_min_config(self): """Test minimum jewish calendar configuration.""" @@ -60,111 +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) + 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="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() - self.assertEqual(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) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='date', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - self.assertEqual(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) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='holiday_name', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - self.assertEqual(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) - sensor = JewishCalSensor( - name='test', language='english', sensor_type='holiday_name', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) - 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") - - def test_jewish_calendar_sensor_holyness(self): - """Test Jewish calendar sensor holyness value.""" - test_time = dt(2018, 9, 10) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='holyness', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) - 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) - - def test_jewish_calendar_sensor_torah_reading(self): - """Test Jewish calendar sensor torah reading in hebrew.""" - test_time = dt(2018, 9, 8) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='weekly_portion', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="UTC", diaspora=False) - with patch('homeassistant.util.dt.now', return_value=test_time): - run_coroutine_threadsafe( - sensor.async_update(), self.hass.loop).result() - self.assertEqual(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) - 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) - 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)) - - def test_jewish_calendar_sensor_first_stars_jerusalem(self): - """Test Jewish calendar sensor first stars time in Jerusalem, IL.""" - test_time = dt(2018, 9, 8) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='first_stars', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="Asia/Jerusalem", diaspora=False) - 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)) - - def test_jewish_calendar_sensor_torah_reading_weekday(self): - """Test the sensor showing torah reading also on weekdays.""" - test_time = dt(2018, 10, 14) - sensor = JewishCalSensor( - name='test', language='hebrew', sensor_type='weekly_portion', - latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, - timezone="Asia/Jerusalem", diaspora=False) - 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 == result 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..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] - self.assertEqual(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 - )) - self.assertEqual(self.hum.name, '{0} {1}'.format( + temp._type + ) + 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() - self.assertEqual(self.temp.state, device[self.api.TEMP]) - self.hum.update() - self.assertEqual(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, '%') +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() - self.assertEqual(self.temp.state, 27.4) - self.hum.update() - self.assertEqual(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) - self.hum._api.status.return_value = {} - self.hum.update() - self.assertEqual(None, self.hum.state) +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/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..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) - self.assertTrue(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) - self.assertEqual(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) +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) 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 deleted file mode 100644 index 7b46ad99d41..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') - 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')) 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_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 diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index 34b37639cd1..84db2fd0df0 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 @@ -75,24 +76,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -110,30 +110,29 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -154,32 +153,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -201,32 +199,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -249,32 +246,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -295,32 +291,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -344,8 +339,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,24 +349,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -394,8 +388,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,24 +400,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -446,8 +439,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,24 +450,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -496,8 +488,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,24 +499,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -547,8 +538,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,24 +549,23 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -598,32 +588,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -648,32 +637,31 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -697,16 +685,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,26 +702,26 @@ 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.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) 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)) @@ -760,36 +747,35 @@ 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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -812,29 +798,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_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.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) def event_date(hass, event, now=None): - if event == 'sunrise': + if event == SUN_EVENT_SUNRISE: return sunrise_time return sunset_time @@ -859,4 +844,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_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]) 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..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,26 +104,39 @@ 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) 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 +145,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 +162,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 +210,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 +233,57 @@ 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() + 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.""" @@ -243,11 +300,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 +312,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_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'} 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 deleted file mode 100644 index 0acf0833543..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) - self.assertEqual(200, req.status_code) - self.assertEqual("", 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) - self.assertEqual(200, req.status_code) - self.assertEqual("", 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) - self.assertEqual(200, req.status_code) - text = req.json().get("speech") - self.assertEqual("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) - self.assertEqual(200, req.status_code) - text = req.json().get("speech") - self.assertEqual("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) - self.assertEqual(200, req.status_code) - text = req.json().get("speech") - - self.assertEqual("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) - text = req.json().get("speech") - self.assertEqual("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) - self.assertEqual(200, req.status_code) - self.assertEqual(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")) - - 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) - self.assertEqual(200, req.status_code) - text = req.json().get("speech") - self.assertEqual( - "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) - self.assertEqual(200, req.status_code) - text = req.json().get("speech") - self.assertEqual( - "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 2be58c7e9d4..d08fbb7451c 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.""" 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 139a97463ea..da06927db3a 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..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,27 +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="********") - 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 hass.data + assert isinstance(hass.data[melissa.DATA_MELISSA], type( + mocked_melissa.AsyncMelissa())) 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/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' 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() diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index ce4656032a6..6b2611b2509 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.config_flow.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.config_flow.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.config_flow.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.config_flow.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.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) + 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.config_flow.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.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) + 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] 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..c2634b2d621 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 @@ -588,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() @@ -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 @@ -652,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 @@ -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_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 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 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 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..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 @@ -83,18 +84,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, SUN_EVENT_SUNRISE) + assert next_setting == sun.get_astral_event_next( + self.hass, SUN_EVENT_SUNSET) def test_date_events(self): """Test retrieving next sun events.""" @@ -114,18 +115,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, SUN_EVENT_SUNRISE, utc_today) + assert sunset == sun.get_astral_event_date( + self.hass, SUN_EVENT_SUNSET, utc_today) def test_date_events_default_date(self): """Test retrieving next sun events.""" @@ -146,18 +147,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, SUN_EVENT_SUNRISE, utc_today) + assert sunset == sun.get_astral_event_date( + self.hass, SUN_EVENT_SUNSET, utc_today) def test_date_events_accepts_datetime(self): """Test retrieving next sun events.""" @@ -177,30 +178,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, SUN_EVENT_SUNRISE, utc_now) + assert sunset == sun.get_astral_event_date( + self.hass, SUN_EVENT_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.""" @@ -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 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..573a9f78b72 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,148 @@ 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 [] == template.extract_entities(None) - self.assertEqual( - MATCH_ALL, + assert [] == template.extract_entities("mdi:water") + + 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) - """)) +{{ 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'}) + + 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 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_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) 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' 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' 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