diff --git a/.coveragerc b/.coveragerc index 820c7562276..d117e864e62 100644 --- a/.coveragerc +++ b/.coveragerc @@ -72,6 +72,7 @@ omit = homeassistant/components/camera/foscam.py homeassistant/components/camera/generic.py homeassistant/components/camera/mjpeg.py + homeassistant/components/camera/rpi_camera.py homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0cd55db150c..a7476162265 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -21,7 +21,7 @@ import homeassistant.util.package as pkg_util from homeassistant.const import ( CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED, - TEMP_CELCIUS, TEMP_FAHRENHEIT, __version__) + TEMP_CELCIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__) from homeassistant.helpers import ( event_decorators, service, config_per_platform, extract_domain_configs) from homeassistant.helpers.entity import Entity @@ -32,7 +32,6 @@ _CURRENT_SETUP = [] ATTR_COMPONENT = 'component' -PLATFORM_FORMAT = '{}.{}' ERROR_LOG_FILENAME = 'home-assistant.log' @@ -117,6 +116,13 @@ def _setup_component(hass, domain, config): domain, ex, p_config) return False + # Not all platform components follow same pattern for platforms + # Sof if p_name is None we are not going to validate platform + # (the automation component is one of them) + if p_name is None: + platforms.append(p_validated) + continue + platform = prepare_setup_platform(hass, config, domain, p_name) @@ -176,7 +182,7 @@ def prepare_setup_platform(hass, config, domain, platform_name): platform_path = PLATFORM_FORMAT.format(domain, platform_name) - platform = loader.get_component(platform_path) + platform = loader.get_platform(domain, platform_name) # Not found if platform is None: diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7a3f635bc49..ce5dde3609d 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -6,13 +6,15 @@ https://home-assistant.io/components/automation/ """ import logging +import voluptuous as vol + from homeassistant.bootstrap import prepare_setup_platform from homeassistant.const import CONF_PLATFORM from homeassistant.components import logbook from homeassistant.helpers import extract_domain_configs -from homeassistant.helpers.service import (call_from_config, - validate_service_call) - +from homeassistant.helpers.service import call_from_config +from homeassistant.loader import get_platform +import homeassistant.helpers.config_validation as cv DOMAIN = 'automation' @@ -31,17 +33,72 @@ CONDITION_TYPE_OR = 'or' DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND +METHOD_TRIGGER = 'trigger' +METHOD_IF_ACTION = 'if_action' + _LOGGER = logging.getLogger(__name__) +def _platform_validator(method, schema): + """Generate platform validator for different steps.""" + def validator(config): + """Validate it is a valid platform.""" + platform = get_platform(DOMAIN, config[CONF_PLATFORM]) + + if not hasattr(platform, method): + raise vol.Invalid('invalid method platform') + + if not hasattr(platform, schema): + return config + + print('validating config', method, config) + + return getattr(platform, schema)(config) + + return validator + +_TRIGGER_SCHEMA = vol.All( + cv.ensure_list, + [ + vol.All( + vol.Schema({ + vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN) + }, extra=vol.ALLOW_EXTRA), + _platform_validator(METHOD_TRIGGER, 'TRIGGER_SCHEMA') + ), + ] +) + +_CONDITION_SCHEMA = vol.Any( + CONDITION_USE_TRIGGER_VALUES, + vol.All( + cv.ensure_list, + [ + vol.All( + vol.Schema({ + vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN), + }, extra=vol.ALLOW_EXTRA), + _platform_validator(METHOD_IF_ACTION, 'IF_ACTION_SCHEMA'), + ) + ] + ) +) + +PLATFORM_SCHEMA = vol.Schema({ + CONF_ALIAS: cv.string, + vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, + vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE): + vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)), + CONF_CONDITION: _CONDITION_SCHEMA, + vol.Required(CONF_ACTION): cv.SERVICE_SCHEMA, +}) + + def setup(hass, config): """Setup the automation.""" for config_key in extract_domain_configs(config, DOMAIN): conf = config[config_key] - if not isinstance(conf, list): - conf = [conf] - for list_no, config_block in enumerate(conf): name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key, list_no)) @@ -54,10 +111,7 @@ def _setup_automation(hass, config_block, name, config): """Setup one instance of automation.""" action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) - if action is None: - return False - - if CONF_CONDITION in config_block or CONF_CONDITION_TYPE in config_block: + if CONF_CONDITION in config_block: action = _process_if(hass, config, config_block, action) if action is None: @@ -70,11 +124,6 @@ def _setup_automation(hass, config_block, name, config): def _get_action(hass, config, name): """Return an action based on a configuration.""" - validation_error = validate_service_call(config) - if validation_error: - _LOGGER.error(validation_error) - return None - def action(): """Action to be executed.""" _LOGGER.info('Executing %s', name) @@ -96,12 +145,9 @@ def _process_if(hass, config, p_config, action): if use_trigger: if_configs = p_config[CONF_TRIGGER] - if isinstance(if_configs, dict): - if_configs = [if_configs] - checks = [] for if_config in if_configs: - platform = _resolve_platform('if_action', hass, config, + platform = _resolve_platform(METHOD_IF_ACTION, hass, config, if_config.get(CONF_PLATFORM)) if platform is None: continue @@ -134,7 +180,7 @@ def _process_trigger(hass, config, trigger_configs, name, action): trigger_configs = [trigger_configs] for conf in trigger_configs: - platform = _resolve_platform('trigger', hass, config, + platform = _resolve_platform(METHOD_TRIGGER, hass, config, conf.get(CONF_PLATFORM)) if platform is None: continue diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index d87d7e3fff6..742e6195949 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -4,16 +4,17 @@ Offer state listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#state-trigger """ -import logging from datetime import timedelta -import homeassistant.util.dt as dt_util +import voluptuous as vol +import homeassistant.util.dt as dt_util from homeassistant.const import ( - EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) + EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, CONF_PLATFORM) from homeassistant.components.automation.time import ( CONF_HOURS, CONF_MINUTES, CONF_SECONDS) from homeassistant.helpers.event import track_state_change, track_point_in_time +import homeassistant.helpers.config_validation as cv CONF_ENTITY_ID = "entity_id" CONF_FROM = "from" @@ -21,6 +22,33 @@ CONF_TO = "to" CONF_STATE = "state" CONF_FOR = "for" +BASE_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'state', + vol.Required(CONF_ENTITY_ID): cv.entity_id, + # These are str on purpose. Want to catch YAML conversions + CONF_STATE: str, + CONF_FOR: vol.All(vol.Schema({ + CONF_HOURS: vol.Coerce(int), + CONF_MINUTES: vol.Coerce(int), + CONF_SECONDS: vol.Coerce(int), + }), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS)), +}) + +TRIGGER_SCHEMA = vol.Schema(vol.All( + BASE_SCHEMA.extend({ + # These are str on purpose. Want to catch YAML conversions + CONF_FROM: str, + CONF_TO: str, + }), + vol.Any(cv.key_dependency(CONF_FOR, CONF_TO), + cv.key_dependency(CONF_FOR, CONF_STATE)) +)) + +IF_ACTION_SCHEMA = vol.Schema(vol.All( + BASE_SCHEMA, + cv.key_dependency(CONF_FOR, CONF_STATE) +)) + def get_time_config(config): """Helper function to extract the time specified in the configuration.""" @@ -31,18 +59,6 @@ def get_time_config(config): minutes = config[CONF_FOR].get(CONF_MINUTES) seconds = config[CONF_FOR].get(CONF_SECONDS) - if hours is None and minutes is None and seconds is None: - logging.getLogger(__name__).error( - "Received invalid value for '%s': %s", - config[CONF_FOR], CONF_FOR) - return None - - if config.get(CONF_TO) is None and config.get(CONF_STATE) is None: - logging.getLogger(__name__).error( - "For: requires a to: value'%s': %s", - config[CONF_FOR], CONF_FOR) - return None - return timedelta(hours=(hours or 0.0), minutes=(minutes or 0.0), seconds=(seconds or 0.0)) @@ -51,24 +67,10 @@ def get_time_config(config): def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) - - if entity_id is None: - logging.getLogger(__name__).error( - "Missing trigger configuration key %s", CONF_ENTITY_ID) - return None - from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL time_delta = get_time_config(config) - if isinstance(from_state, bool) or isinstance(to_state, bool): - logging.getLogger(__name__).error( - 'Config error. Surround to/from values with quotes.') - return None - - if CONF_FOR in config and time_delta is None: - return None - def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" def state_for_listener(now): @@ -105,18 +107,7 @@ def if_action(hass, config): """Wrap action method with state based condition.""" entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) - - if entity_id is None or state is None: - logging.getLogger(__name__).error( - "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, - CONF_STATE) - return None - time_delta = get_time_config(config) - if CONF_FOR in config and time_delta is None: - return None - - state = str(state) def if_state(): """Test if condition.""" diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 63d7715ef7d..2a564a3b588 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -4,12 +4,16 @@ Offer sun based automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#sun-trigger """ -import logging from datetime import timedelta +import logging +import voluptuous as vol + +from homeassistant.const import CONF_PLATFORM import homeassistant.util.dt as dt_util from homeassistant.components import sun from homeassistant.helpers.event import track_sunrise, track_sunset +import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['sun'] @@ -26,22 +30,30 @@ EVENT_SUNRISE = 'sunrise' _LOGGER = logging.getLogger(__name__) +_SUN_EVENT = vol.All(vol.Lower, vol.Any(EVENT_SUNRISE, EVENT_SUNSET)) + +TRIGGER_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'sun', + vol.Required(CONF_EVENT): _SUN_EVENT, + vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_offset, +}) + +IF_ACTION_SCHEMA = vol.All( + vol.Schema({ + vol.Required(CONF_PLATFORM): 'sun', + CONF_BEFORE: _SUN_EVENT, + CONF_AFTER: _SUN_EVENT, + vol.Required(CONF_BEFORE_OFFSET, default=timedelta(0)): cv.time_offset, + vol.Required(CONF_AFTER_OFFSET, default=timedelta(0)): cv.time_offset, + }), + cv.has_at_least_one_key(CONF_BEFORE, CONF_AFTER), +) + + def trigger(hass, config, action): """Listen for events based on configuration.""" event = config.get(CONF_EVENT) - - if event is None: - _LOGGER.error("Missing configuration key %s", CONF_EVENT) - return False - - event = event.lower() - if event not in (EVENT_SUNRISE, EVENT_SUNSET): - _LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event) - return False - - offset = _parse_offset(config.get(CONF_OFFSET)) - if offset is False: - return False + offset = config.get(CONF_OFFSET) # Do something to call action if event == EVENT_SUNRISE: @@ -56,26 +68,8 @@ def if_action(hass, config): """Wrap action method with sun based condition.""" before = config.get(CONF_BEFORE) after = config.get(CONF_AFTER) - - # Make sure required configuration keys are present - if before is None and after is None: - logging.getLogger(__name__).error( - "Missing if-condition configuration key %s or %s", - CONF_BEFORE, CONF_AFTER) - return None - - # Make sure configuration keys have the right value - if before not in (None, EVENT_SUNRISE, EVENT_SUNSET) or \ - after not in (None, EVENT_SUNRISE, EVENT_SUNSET): - logging.getLogger(__name__).error( - "%s and %s can only be set to %s or %s", - CONF_BEFORE, CONF_AFTER, EVENT_SUNRISE, EVENT_SUNSET) - return None - - before_offset = _parse_offset(config.get(CONF_BEFORE_OFFSET)) - after_offset = _parse_offset(config.get(CONF_AFTER_OFFSET)) - if before_offset is False or after_offset is False: - return None + before_offset = config.get(CONF_BEFORE_OFFSET) + after_offset = config.get(CONF_AFTER_OFFSET) if before is None: def before_func(): @@ -120,27 +114,3 @@ def if_action(hass, config): return True return time_if - - -def _parse_offset(raw_offset): - """Parse the offset.""" - if raw_offset is None: - return timedelta(0) - - negative_offset = False - if raw_offset.startswith('-'): - negative_offset = True - raw_offset = raw_offset[1:] - - try: - (hour, minute, second) = [int(x) for x in raw_offset.split(':')] - except ValueError: - _LOGGER.error('Could not parse offset %s', raw_offset) - return False - - offset = timedelta(hours=hour, minutes=minute, seconds=second) - - if negative_offset: - offset *= -1 - - return offset diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 1d17246c012..02e8f30d209 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -6,21 +6,27 @@ at https://home-assistant.io/components/automation/#template-trigger """ import logging -from homeassistant.const import CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED +import voluptuous as vol + +from homeassistant.const import ( + CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED, CONF_PLATFORM) from homeassistant.exceptions import TemplateError from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv + _LOGGER = logging.getLogger(__name__) +TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'template', + vol.Required(CONF_VALUE_TEMPLATE): cv.template, +}) + def trigger(hass, config, action): """Listen for state changes based on configuration.""" value_template = config.get(CONF_VALUE_TEMPLATE) - if value_template is None: - _LOGGER.error("Missing configuration key %s", CONF_VALUE_TEMPLATE) - return False - # Local variable to keep track of if the action has already been triggered already_triggered = False @@ -44,10 +50,6 @@ def if_action(hass, config): """Wrap action method with state based condition.""" value_template = config.get(CONF_VALUE_TEMPLATE) - if value_template is None: - _LOGGER.error("Missing configuration key %s", CONF_VALUE_TEMPLATE) - return False - return lambda: _check_template(hass, value_template) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index a5f7259b112..66ea3c2d7c7 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -4,12 +4,13 @@ Offer zone automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#zone-trigger """ -import logging +import voluptuous as vol from homeassistant.components import zone from homeassistant.const import ( - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL) + ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL, CONF_PLATFORM) from homeassistant.helpers.event import track_state_change +import homeassistant.helpers.config_validation as cv CONF_ENTITY_ID = "entity_id" CONF_ZONE = "zone" @@ -18,19 +19,26 @@ EVENT_ENTER = "enter" EVENT_LEAVE = "leave" DEFAULT_EVENT = EVENT_ENTER +TRIGGER_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'zone', + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ZONE): cv.entity_id, + vol.Required(CONF_EVENT, default=DEFAULT_EVENT): + vol.Any(EVENT_ENTER, EVENT_LEAVE), +}) + +IF_ACTION_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'zone', + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_ZONE): cv.entity_id, +}) + def trigger(hass, config, action): """Listen for state changes based on configuration.""" entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) - - if entity_id is None or zone_entity_id is None: - logging.getLogger(__name__).error( - "Missing trigger configuration key %s or %s", CONF_ENTITY_ID, - CONF_ZONE) - return False - - event = config.get(CONF_EVENT, DEFAULT_EVENT) + event = config.get(CONF_EVENT) def zone_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" @@ -59,12 +67,6 @@ def if_action(hass, config): entity_id = config.get(CONF_ENTITY_ID) zone_entity_id = config.get(CONF_ZONE) - if entity_id is None or zone_entity_id is None: - logging.getLogger(__name__).error( - "Missing condition configuration key %s or %s", CONF_ENTITY_ID, - CONF_ZONE) - return False - def if_in_zone(): """Test if condition.""" return _in_zone(hass, zone_entity_id, hass.states.get(entity_id)) diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 28ebdbb5992..6a07665d372 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -77,9 +77,9 @@ _DELAY_SCHEMA = { 'minutes': vol.All(vol.Coerce(int), vol.Range(min=0)), 'hours': vol.All(vol.Coerce(int), vol.Range(min=0)), 'weeks': vol.All(vol.Coerce(int), vol.Range(min=0)), - }, cv.has_at_least_one_key([ + }, cv.has_at_least_one_key( 'days', 'seconds', 'microseconds', 'milliseconds', 'minutes', 'hours', - 'weeks'])) + 'weeks')) } _EVENT_SCHEMA = cv.EVENT_SCHEMA.extend({ @@ -97,7 +97,7 @@ _SCRIPT_ENTRY_SCHEMA = vol.Schema({ }) CONFIG_SCHEMA = vol.Schema({ - vol.Required(DOMAIN): cv.DictValidator(_SCRIPT_ENTRY_SCHEMA, cv.slug) + vol.Required(DOMAIN): {cv.slug: _SCRIPT_ENTRY_SCHEMA} }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/const.py b/homeassistant/const.py index a98e6c85a7e..17dcc9e8602 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -4,6 +4,8 @@ __version__ = "0.17.0.dev0" REQUIRED_PYTHON_VER = (3, 4) +PLATFORM_FORMAT = '{}.{}' + # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1e8e48c00a4..10c1f0210f6 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,7 +1,10 @@ """Helpers for config validation using voluptuous.""" +from datetime import timedelta + import jinja2 import voluptuous as vol +from homeassistant.loader import get_platform from homeassistant.const import ( CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import valid_entity_id @@ -11,7 +14,6 @@ from homeassistant.util import slugify # pylint: disable=invalid-name # Home Assistant types - byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)) latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90), @@ -32,6 +34,11 @@ def boolean(value): return bool(value) +def ensure_list(value): + """Wrap value in list if it is not one.""" + return value if isinstance(value, list) else [value] + + def entity_id(value): """Validate Entity ID.""" if valid_entity_id(value): @@ -61,6 +68,59 @@ def icon(value): raise vol.Invalid('Icons should start with prefix "mdi:"') +def time_offset(value): + """Validate and transform time offset.""" + if not isinstance(value, str): + raise vol.Invalid('offset should be a string') + + negative_offset = False + if value.startswith('-'): + negative_offset = True + value = value[1:] + elif value.startswith('+'): + value = value[1:] + + try: + parsed = [int(x) for x in value.split(':')] + except ValueError: + raise vol.Invalid( + 'offset {} should be format HH:MM or HH:MM:SS'.format(value)) + + if len(parsed) == 2: + hour, minute = parsed + second = 0 + elif len(parsed) == 3: + hour, minute, second = parsed + else: + raise vol.Invalid( + 'offset {} should be format HH:MM or HH:MM:SS'.format(value)) + + offset = timedelta(hours=hour, minutes=minute, seconds=second) + + if negative_offset: + offset *= -1 + + return offset + + +def match_all(value): + """Validator that matches all values.""" + return value + + +def platform_validator(domain): + """Validate if platform exists for given domain.""" + def validator(value): + """Test if platform exists.""" + if value is None: + raise vol.Invalid('platform cannot be None') + if get_platform(domain, str(value)): + return value + raise vol.Invalid( + 'platform {} does not exist for {}'.format(value, domain)) + return validator + + def service(value): """Validate service.""" # Services use same format as entities so we can use same helper. @@ -122,60 +182,24 @@ def time_zone(value): # Validator helpers -# pylint: disable=too-few-public-methods +def key_dependency(key, dependency): + """Validate that all dependencies exist for key.""" + def validator(value): + """Test dependencies.""" + if not isinstance(value, dict): + raise vol.Invalid('key dependencies require a dict') + print(key, value) + if key in value and dependency not in value: + raise vol.Invalid('dependency violation - key "{}" requires ' + 'key "{}" to exist'.format(key, dependency)) -class DictValidator(object): - """Validate keys and values in a dictionary.""" - - def __init__(self, value_validator=None, key_validator=None): - """Initialize the dict validator.""" - if value_validator is not None: - value_validator = vol.Schema(value_validator) - - self.value_validator = value_validator - - if key_validator is not None: - key_validator = vol.Schema(key_validator) - - self.key_validator = key_validator - - def __call__(self, obj): - """Validate the dict.""" - if not isinstance(obj, dict): - raise vol.Invalid('Expected dictionary.') - - errors = [] - - # So we keep it an OrderedDict if it is one - result = obj.__class__() - - for key, value in obj.items(): - if self.key_validator is not None: - try: - key = self.key_validator(key) - except vol.Invalid as ex: - errors.append('key {} is invalid ({})'.format(key, ex)) - - if self.value_validator is not None: - try: - value = self.value_validator(value) - except vol.Invalid as ex: - errors.append( - 'key {} contains invalid value ({})'.format(key, ex)) - - if not errors: - result[key] = value - - if errors: - raise vol.Invalid( - 'invalid dictionary: {}'.format(', '.join(errors))) - - return result + return value + return validator # Adapted from: # https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666 -def has_at_least_one_key(keys): +def has_at_least_one_key(*keys): """Validator that at least one key exists.""" def validate(obj): """Test keys exist in dict.""" @@ -206,5 +230,6 @@ SERVICE_SCHEMA = vol.All(vol.Schema({ vol.Exclusive('service', 'service name'): service, vol.Exclusive('service_template', 'service name'): string, vol.Exclusive('data', 'service data'): dict, - vol.Exclusive('data_template', 'service data'): DictValidator(template), -}), has_at_least_one_key(['service', 'service_template'])) + vol.Exclusive('data_template', 'service data'): {match_all: template}, + 'entity_id': entity_ids, +}), has_at_least_one_key('service', 'service_template')) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index cb530946530..edc54cce61b 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -16,6 +16,7 @@ import os import pkgutil import sys +from homeassistant.const import PLATFORM_FORMAT from homeassistant.util import OrderedSet PREPARED = False @@ -77,6 +78,11 @@ def set_component(comp_name, component): _COMPONENT_CACHE[comp_name] = component +def get_platform(domain, platform): + """Try to load specified platform.""" + return get_component(PLATFORM_FORMAT.format(domain, platform)) + + def get_component(comp_name): """Try to load specified component. diff --git a/requirements_all.txt b/requirements_all.txt index 0336b1aaeca..822306071b7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ pytz>=2016.3 pip>=7.0.0 vincenty==0.1.4 jinja2>=2.8 -voluptuous==0.8.10 +voluptuous==0.8.9 # homeassistant.components.isy994 PyISY==1.0.5 diff --git a/setup.py b/setup.py index a799720fea1..9569529fc84 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ REQUIRES = [ 'pip>=7.0.0', 'vincenty==0.1.4', 'jinja2>=2.8', - 'voluptuous==0.8.10', + 'voluptuous==0.8.9', ] setup( diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 7d8c6a64ab1..ef5d380075b 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,6 +1,7 @@ """The tests for the Event automation.""" import unittest +from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from tests.common import get_test_home_assistant @@ -12,6 +13,7 @@ class TestAutomationEvent(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.calls = [] def record_call(service): @@ -26,7 +28,7 @@ class TestAutomationEvent(unittest.TestCase): def test_if_fires_on_event(self): """Test the firing of events.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -36,7 +38,7 @@ class TestAutomationEvent(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() @@ -44,7 +46,7 @@ class TestAutomationEvent(unittest.TestCase): def test_if_fires_on_event_with_data(self): """Test the firing of events with data.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -55,7 +57,7 @@ class TestAutomationEvent(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.bus.fire('test_event', {'some_attr': 'some_value', 'another': 'value'}) @@ -64,7 +66,7 @@ class TestAutomationEvent(unittest.TestCase): def test_if_not_fires_if_event_data_not_matches(self): """Test firing of event if no match.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -75,7 +77,7 @@ class TestAutomationEvent(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index cb996d5a227..355865e9e9c 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,6 +1,7 @@ """The tests for the automation component.""" import unittest +from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from homeassistant.const import ATTR_ENTITY_ID @@ -13,6 +14,7 @@ class TestAutomation(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.calls = [] def record_call(service): @@ -26,7 +28,7 @@ class TestAutomation(unittest.TestCase): def test_service_data_not_a_dict(self): """Test service data not dict.""" - automation.setup(self.hass, { + assert not _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -39,13 +41,9 @@ class TestAutomation(unittest.TestCase): } }) - self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) - def test_service_specify_data(self): """Test service data.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -65,7 +63,7 @@ class TestAutomation(unittest.TestCase): def test_service_specify_entity_id(self): """Test service data.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -86,7 +84,7 @@ class TestAutomation(unittest.TestCase): def test_service_specify_entity_id_list(self): """Test service data.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -107,7 +105,7 @@ class TestAutomation(unittest.TestCase): def test_two_triggers(self): """Test triggers.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': [ { @@ -135,7 +133,7 @@ class TestAutomation(unittest.TestCase): def test_two_conditions_with_and(self): """Test two and conditions.""" entity_id = 'test.entity' - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': [ { @@ -147,7 +145,7 @@ class TestAutomation(unittest.TestCase): { 'platform': 'state', 'entity_id': entity_id, - 'state': 100 + 'state': '100' }, { 'platform': 'numeric_state', @@ -179,7 +177,7 @@ class TestAutomation(unittest.TestCase): def test_two_conditions_with_or(self): """Test two or conditions.""" entity_id = 'test.entity' - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': [ { @@ -192,7 +190,7 @@ class TestAutomation(unittest.TestCase): { 'platform': 'state', 'entity_id': entity_id, - 'state': 200 + 'state': '200' }, { 'platform': 'numeric_state', @@ -224,13 +222,13 @@ class TestAutomation(unittest.TestCase): def test_using_trigger_as_condition(self): """Test triggers as condition.""" entity_id = 'test.entity' - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': [ { 'platform': 'state', 'entity_id': entity_id, - 'state': 100 + 'state': '100' }, { 'platform': 'numeric_state', @@ -247,21 +245,21 @@ class TestAutomation(unittest.TestCase): self.hass.states.set(entity_id, 100) self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + self.assertEqual(2, len(self.calls)) self.hass.states.set(entity_id, 120) self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + self.assertEqual(2, len(self.calls)) self.hass.states.set(entity_id, 151) self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + self.assertEqual(2, len(self.calls)) def test_using_trigger_as_condition_with_invalid_condition(self): """Event is not a valid condition.""" entity_id = 'test.entity' self.hass.states.set(entity_id, 100) - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': [ { @@ -287,7 +285,7 @@ class TestAutomation(unittest.TestCase): def test_automation_list_setting(self): """Event is not a valid condition.""" - self.assertTrue(automation.setup(self.hass, { + self.assertTrue(_setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: [{ 'trigger': { 'platform': 'event', diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 482becab937..0fd2a9aef06 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -1,6 +1,7 @@ """The tests for the MQTT automation.""" import unittest +from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from tests.common import ( mock_mqtt_component, fire_mqtt_message, get_test_home_assistant) @@ -12,6 +13,7 @@ class TestAutomationMQTT(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') mock_mqtt_component(self.hass) self.calls = [] @@ -26,7 +28,7 @@ class TestAutomationMQTT(unittest.TestCase): def test_if_fires_on_topic_match(self): """Test if message is fired on topic match.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'mqtt', @@ -36,7 +38,7 @@ class TestAutomationMQTT(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_mqtt_message(self.hass, 'test-topic', '') self.hass.pool.block_till_done() @@ -44,7 +46,7 @@ class TestAutomationMQTT(unittest.TestCase): def test_if_fires_on_topic_and_payload_match(self): """Test if message is fired on topic and payload match.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'mqtt', @@ -55,7 +57,7 @@ class TestAutomationMQTT(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_mqtt_message(self.hass, 'test-topic', 'hello') self.hass.pool.block_till_done() @@ -63,7 +65,7 @@ class TestAutomationMQTT(unittest.TestCase): def test_if_not_fires_on_topic_but_no_payload_match(self): """Test if message is not fired on topic but no payload.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'mqtt', @@ -74,7 +76,7 @@ class TestAutomationMQTT(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_mqtt_message(self.hass, 'test-topic', 'no-hello') self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 111230f81ab..ee29c0fb56f 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,6 +1,7 @@ """The tests for numeric state automation.""" import unittest +from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from tests.common import get_test_home_assistant @@ -12,6 +13,7 @@ class TestAutomationNumericState(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.calls = [] def record_call(service): @@ -26,7 +28,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below(self): """"Test the firing with changed entity.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -37,7 +39,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 9) self.hass.pool.block_till_done() @@ -48,7 +50,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -59,7 +61,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 9) @@ -71,7 +73,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 9) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -82,7 +84,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 so this should not fire again self.hass.states.set('test.entity', 8) @@ -91,7 +93,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_above(self): """"Test the firing with changed entity.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -102,7 +104,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is above 10 self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() @@ -114,7 +116,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 9) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -125,7 +127,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is above 10 and 9 is below self.hass.states.set('test.entity', 11) @@ -138,7 +140,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -149,7 +151,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is above 10 so this should fire again self.hass.states.set('test.entity', 12) @@ -158,7 +160,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below_range(self): """"Test the firing with changed entity.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -170,7 +172,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 9) self.hass.pool.block_till_done() @@ -178,7 +180,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below_above_range(self): """"Test the firing with changed entity.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -190,7 +192,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 4 is below 5 self.hass.states.set('test.entity', 4) self.hass.pool.block_till_done() @@ -201,7 +203,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -213,7 +215,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 9) @@ -225,7 +227,7 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -237,7 +239,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 4 is below 5 so it should not fire self.hass.states.set('test.entity', 4) @@ -246,7 +248,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_if_entity_not_match(self): """"Test if not fired with non matching entity.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -256,7 +258,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 11) self.hass.pool.block_till_done() @@ -264,7 +266,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below_with_attribute(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -275,7 +277,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 9, {'test_attribute': 11}) self.hass.pool.block_till_done() @@ -283,7 +285,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_on_entity_change_not_below_with_attribute(self): """"Test attributes.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -294,7 +296,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is not below 10 self.hass.states.set('test.entity', 11, {'test_attribute': 9}) self.hass.pool.block_till_done() @@ -302,7 +304,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_attribute_change_with_attribute_below(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -314,7 +316,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 9}) self.hass.pool.block_till_done() @@ -322,7 +324,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_on_attribute_change_with_attribute_not_below(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -334,7 +336,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 11}) self.hass.pool.block_till_done() @@ -342,7 +344,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_on_entity_change_with_attribute_below(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -354,7 +356,7 @@ class TestAutomationNumericState(unittest.TestCase): '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.pool.block_till_done() @@ -362,7 +364,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_on_entity_change_with_not_attribute_below(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -374,7 +376,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is not below 10, entity state value should not be tested self.hass.states.set('test.entity', 'entity') self.hass.pool.block_till_done() @@ -382,7 +384,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(self): """"Test attributes change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -394,7 +396,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 9, 'not_test_attribute': 11}) @@ -403,7 +405,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_template_list(self): """"Test template list.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -416,7 +418,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 3 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': [11, 15, 3]}) @@ -425,7 +427,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_template_string(self): """"Test template string.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -438,7 +440,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 9 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': '0.9'}) @@ -447,7 +449,7 @@ class TestAutomationNumericState(unittest.TestCase): def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self): """"Test if not fired changed attributes.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', @@ -459,7 +461,7 @@ class TestAutomationNumericState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # 11 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 11, 'not_test_attribute': 9}) @@ -470,7 +472,7 @@ class TestAutomationNumericState(unittest.TestCase): """"Test if action.""" entity_id = 'domain.test_entity' test_state = 10 - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 3ccfcaeaeef..2f688249834 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -3,9 +3,9 @@ import unittest from datetime import timedelta from unittest.mock import patch +from homeassistant.bootstrap import _setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation -import homeassistant.components.automation.state as state from tests.common import fire_time_changed, get_test_home_assistant @@ -16,6 +16,7 @@ class TestAutomationState(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.hass.states.set('test.entity', 'hello') self.calls = [] @@ -30,7 +31,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change(self): """Test for firing on entity change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -40,7 +41,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -48,7 +49,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_from_filter(self): """Test for firing on entity change with filter.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -59,7 +60,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -67,7 +68,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_to_filter(self): """Test for firing on entity change with no filter.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -78,7 +79,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -86,7 +87,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_state_filter(self): """Test for firing on entity change with state filter.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -97,7 +98,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -105,7 +106,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_both_filters(self): """Test for firing if both filters are a non match.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -117,7 +118,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -125,7 +126,7 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_if_to_filter_not_match(self): """Test for not firing if to filter is not a match.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -137,7 +138,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'moon') self.hass.pool.block_till_done() @@ -147,7 +148,7 @@ class TestAutomationState(unittest.TestCase): """Test for not firing if from filter is not a match.""" self.hass.states.set('test.entity', 'bye') - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -159,7 +160,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -167,7 +168,7 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_if_entity_not_match(self): """Test for not firing if entity is not matching.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -177,7 +178,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -187,7 +188,7 @@ class TestAutomationState(unittest.TestCase): """Test for to action.""" entity_id = 'domain.test_entity' test_state = 'new_state' - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -218,48 +219,68 @@ class TestAutomationState(unittest.TestCase): def test_if_fails_setup_if_to_boolean_value(self): """Test for setup failure for boolean to.""" - self.assertFalse(state.trigger( - self.hass, { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': True, - }, lambda x: x)) + assert not _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.""" - self.assertFalse(state.trigger( - self.hass, { - 'platform': 'state', - 'entity_id': 'test.entity', - 'from': True, - }, lambda x: x)) + assert not _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.""" - self.assertFalse(state.trigger( - self.hass, { - 'platform': 'state', - 'entity_id': 'test.entity', - 'to': 'world', - 'for': { - 'invalid': 5 + assert not _setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world', + 'for': { + 'invalid': 5 + }, }, - }, lambda x: x)) + 'action': { + 'service': 'homeassistant.turn_on', + } + }}) def test_if_fails_setup_for_without_to(self): """Test for setup failures for missing to.""" - self.assertFalse(state.trigger( - self.hass, { - 'platform': 'state', - 'entity_id': 'test.entity', - 'for': { - 'seconds': 5 + assert not _setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'for': { + 'seconds': 5 + }, }, - }, lambda x: x)) + '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.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -273,7 +294,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -285,7 +306,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_for(self): """Test for firing on entity change with for.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'state', @@ -299,7 +320,7 @@ class TestAutomationState(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -314,7 +335,7 @@ class TestAutomationState(unittest.TestCase): with patch('homeassistant.core.dt_util.utcnow') as mock_utcnow: mock_utcnow.return_value = point1 self.hass.states.set('test.entity', 'on') - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -328,12 +349,9 @@ class TestAutomationState(unittest.TestCase): 'seconds': 5 }, }, - - 'action': { - 'service': 'test.automation' - } + 'action': {'service': 'test.automation'}, } - })) + }) # not enough time has passed self.hass.bus.fire('test_event') @@ -348,21 +366,32 @@ class TestAutomationState(unittest.TestCase): def test_if_fails_setup_for_without_time(self): """Test for setup failure if no time is provided.""" - self.assertIsNone(state.if_action( - self.hass, { - 'platform': 'state', - 'entity_id': 'test.entity', - 'state': 'on', - 'for': {}, - })) + assert not _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.""" - self.assertIsNone(state.if_action( - self.hass, { - 'platform': 'state', - 'state': 'on', - 'for': { - 'seconds': 5 + assert not _setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'trigger': {'event_type': 'bla'}, + 'condition': { + 'platform': 'state', + 'state': 'on', + 'for': { + 'seconds': 5 + }, }, - })) + 'action': {'service': 'test.automation'}, + }}) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 6b1212b70a6..58d28e5cbb8 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -3,6 +3,7 @@ from datetime import datetime import unittest from unittest.mock import patch +from homeassistant.bootstrap import _setup_component from homeassistant.components import sun import homeassistant.components.automation as automation import homeassistant.util.dt as dt_util @@ -16,6 +17,7 @@ class TestAutomationSun(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.hass.config.components.append('sun') self.calls = [] @@ -40,7 +42,7 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.components.automation.sun.dt_util.utcnow', return_value=now): - self.assertTrue(automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'sun', @@ -50,7 +52,7 @@ class TestAutomationSun(unittest.TestCase): 'service': 'test.automation', } } - })) + }) fire_time_changed(self.hass, trigger_time) self.hass.pool.block_till_done() @@ -67,7 +69,7 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.components.automation.sun.dt_util.utcnow', return_value=now): - self.assertTrue(automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'sun', @@ -77,7 +79,7 @@ class TestAutomationSun(unittest.TestCase): 'service': 'test.automation', } } - })) + }) fire_time_changed(self.hass, trigger_time) self.hass.pool.block_till_done() @@ -94,7 +96,7 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.components.automation.sun.dt_util.utcnow', return_value=now): - self.assertTrue(automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'sun', @@ -105,7 +107,7 @@ class TestAutomationSun(unittest.TestCase): 'service': 'test.automation', } } - })) + }) fire_time_changed(self.hass, trigger_time) self.hass.pool.block_till_done() @@ -122,7 +124,7 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.components.automation.sun.dt_util.utcnow', return_value=now): - self.assertTrue(automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'sun', @@ -133,7 +135,7 @@ class TestAutomationSun(unittest.TestCase): 'service': 'test.automation', } } - })) + }) fire_time_changed(self.hass, trigger_time) self.hass.pool.block_till_done() @@ -145,7 +147,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -181,7 +183,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -217,7 +219,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -254,7 +256,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -292,7 +294,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_SETTING: '15:00:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -338,7 +340,7 @@ class TestAutomationSun(unittest.TestCase): sun.STATE_ATTR_NEXT_SETTING: '17:30:00 16-09-2015', }) - automation.setup(self.hass, { + _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 315ff37f1ff..bb46c7a262a 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -1,6 +1,7 @@ """The tests fr the Template automation.""" import unittest +from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from tests.common import get_test_home_assistant @@ -12,6 +13,7 @@ class TestAutomationTemplate(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.hass.states.set('test.entity', 'hello') self.calls = [] @@ -27,7 +29,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_bool(self): """Test for firing on boolean change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -37,7 +39,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -45,7 +47,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_str(self): """Test for firing on change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -55,7 +57,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -63,7 +65,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_str_crazy(self): """Test for firing on change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -73,7 +75,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -81,7 +83,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_not_fires_on_change_bool(self): """Test for not firing on boolean change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -91,7 +93,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -99,7 +101,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_not_fires_on_change_str(self): """Test for not firing on string change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -109,7 +111,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -117,7 +119,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_not_fires_on_change_str_crazy(self): """Test for not firing on string change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -127,7 +129,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -135,7 +137,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_no_change(self): """Test for firing on no change.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -145,7 +147,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'hello') self.hass.pool.block_till_done() @@ -153,7 +155,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_two_change(self): """Test for firing on two changes.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -163,7 +165,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # Trigger once self.hass.states.set('test.entity', 'world') @@ -177,7 +179,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_with_template(self): """Test for firing on change with template.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -187,7 +189,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -195,7 +197,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_not_fires_on_change_with_template(self): """Test for not firing on change with template.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -205,7 +207,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -213,7 +215,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_with_template_advanced(self): """Test for firing on change with template advanced.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -227,7 +229,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -235,7 +237,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_no_change_with_template_advanced(self): """Test for firing on no change with template advanced.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -249,7 +251,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) # Different state self.hass.states.set('test.entity', 'worldz') @@ -263,7 +265,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_with_template_2(self): """Test for firing on change with template.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -274,7 +276,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() @@ -302,7 +304,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_action(self): """Test for firing if action.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -335,7 +337,7 @@ class TestAutomationTemplate(unittest.TestCase): def test_if_fires_on_change_with_bad_template(self): """Test for firing on change with bad template.""" - self.assertTrue(automation.setup(self.hass, { + assert not _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -345,15 +347,11 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) - - self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() - self.assertEqual(0, len(self.calls)) + }) def test_if_fires_on_change_with_bad_template_2(self): """Test for firing on change with bad template.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'template', @@ -363,7 +361,7 @@ class TestAutomationTemplate(unittest.TestCase): 'service': 'test.automation' } } - })) + }) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index ee5ed04340a..36f22a00148 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -3,6 +3,7 @@ from datetime import timedelta import unittest from unittest.mock import patch +from homeassistant.bootstrap import _setup_component import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation @@ -15,6 +16,7 @@ class TestAutomationTime(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') self.calls = [] def record_call(service): @@ -28,7 +30,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_when_hour_matches(self): """Test for firing if hour is matching.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -38,7 +40,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) @@ -47,7 +49,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_when_minute_matches(self): """Test for firing if minutes are matching.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -57,7 +59,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) @@ -66,7 +68,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_when_second_matches(self): """Test for firing if seconds are matching.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -76,7 +78,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) @@ -85,7 +87,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_when_all_matches(self): """Test for firing if everything matches.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -97,7 +99,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=1, minute=2, second=3)) @@ -107,7 +109,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_periodic_seconds(self): """Test for firing periodically every second.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -117,7 +119,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=0, second=2)) @@ -127,7 +129,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_periodic_minutes(self): """Test for firing periodically every minute.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -137,7 +139,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=2, second=0)) @@ -147,7 +149,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_periodic_hours(self): """Test for firing periodically every hour.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -157,7 +159,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=2, minute=0, second=0)) @@ -167,7 +169,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_fires_using_after(self): """Test for firing after.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -177,7 +179,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=5, minute=0, second=0)) @@ -187,7 +189,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_not_working_if_no_values_in_conf_provided(self): """Test for failure if no configuration.""" - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -196,7 +198,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=5, minute=0, second=0)) @@ -210,7 +212,7 @@ class TestAutomationTime(unittest.TestCase): This should break the before rule. """ - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'time', @@ -221,7 +223,7 @@ class TestAutomationTime(unittest.TestCase): 'service': 'test.automation' } } - })) + }) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=1, minute=0, second=5)) @@ -232,7 +234,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_action_before(self): """Test for if action before.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -267,7 +269,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_action_after(self): """Test for if action after.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -302,7 +304,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_action_one_weekday(self): """Test for if action with one weekday.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -338,7 +340,7 @@ class TestAutomationTime(unittest.TestCase): def test_if_action_list_weekday(self): """Test for action with a list of weekdays.""" - automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index b9ec1c9e727..87a22243760 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,6 +1,7 @@ """The tests for the location automation.""" import unittest +from homeassistant.bootstrap import _setup_component from homeassistant.components import automation, zone from tests.common import get_test_home_assistant @@ -12,6 +13,7 @@ class TestAutomationZone(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + self.hass.config.components.append('group') zone.setup(self.hass, { 'zone': { 'name': 'test', @@ -40,7 +42,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -52,7 +54,7 @@ class TestAutomationZone(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.states.set('test.entity', 'hello', { 'latitude': 32.880586, @@ -70,7 +72,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -82,7 +84,7 @@ class TestAutomationZone(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.states.set('test.entity', 'hello', { 'latitude': 32.881011, @@ -100,7 +102,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -112,7 +114,7 @@ class TestAutomationZone(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.states.set('test.entity', 'hello', { 'latitude': 32.881011, @@ -130,7 +132,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -142,7 +144,7 @@ class TestAutomationZone(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.states.set('test.entity', 'hello', { 'latitude': 32.880586, @@ -160,7 +162,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.pool.block_till_done() - self.assertTrue(automation.setup(self.hass, { + assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', @@ -175,7 +177,7 @@ class TestAutomationZone(unittest.TestCase): 'service': 'test.automation', } } - })) + }) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() diff --git a/tests/components/test_script.py b/tests/components/test_script.py index bbf61c014be..74b3ce28af6 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -31,7 +31,7 @@ class TestScript(unittest.TestCase): {'test': {}}, { 'test hello world': { - 'sequence': [] + 'sequence': [{'event': 'bla'}] } }, { diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index ab8b102a230..dc7074acffb 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,8 +1,12 @@ +from datetime import timedelta + import pytest import voluptuous as vol import homeassistant.helpers.config_validation as cv +from tests.common import get_test_home_assistant + def test_boolean(): """Test boolean validation.""" @@ -117,6 +121,19 @@ def test_event_schema(): cv.EVENT_SCHEMA(value) +def test_platform_validator(): + """Test platform validation.""" + # Prepares loading + get_test_home_assistant() + + schema = vol.Schema(cv.platform_validator('light')) + + with pytest.raises(vol.MultipleInvalid): + schema('platform_that_does_not_exist') + + schema('hue') + + def test_icon(): """Test icon validation.""" schema = vol.Schema(cv.icon) @@ -128,6 +145,25 @@ def test_icon(): schema('mdi:work') +def test_time_offset(): + """Test time_offset validation.""" + schema = vol.Schema(cv.time_offset) + + for value in ( + None, '', 1234, 'hello:world', '12:', '12:34:56:78' + ): + with pytest.raises(vol.MultipleInvalid): + schema(value) + + for value in ( + '8:20', '23:59', '-8:20', '-23:59:59', '-48:00' + ): + schema(value) + + assert timedelta(hours=23, minutes=59) == schema('23:59') + assert -1 * timedelta(hours=1, minutes=15) == schema('-1:15') + + def test_service(): """Test service validation.""" schema = vol.Schema(cv.service) @@ -165,6 +201,14 @@ def test_service_schema(): for value in ( {'service': 'homeassistant.turn_on'}, + { + 'service': 'homeassistant.turn_on', + 'entity_id': 'light.kitchen', + }, + { + 'service': 'homeassistant.turn_on', + 'entity_id': ['light.kitchen', 'light.ceiling'], + }, ): cv.SERVICE_SCHEMA(value) @@ -232,31 +276,26 @@ def test_time_zone(): schema('UTC') -def test_dict_validator(): - """Test DictValidator.""" - schema = vol.Schema(cv.DictValidator(cv.entity_ids, cv.slug)) +def test_key_dependency(): + """Test key_dependency validator.""" + schema = vol.Schema(cv.key_dependency('beer', 'soda')) for value in ( - None, - {'invalid slug': 'sensor.temp'}, - {'hello world': 'invalid_entity'} + {'beer': None} ): with pytest.raises(vol.MultipleInvalid): schema(value) for value in ( - {}, - {'hello_world': 'sensor.temp'}, + {'beer': None, 'soda': None}, + {'soda': None}, {} ): schema(value) - assert schema({'hello_world': 'sensor.temp'}) == \ - {'hello_world': ['sensor.temp']} - def test_has_at_least_one_key(): """Test has_at_least_one_key validator.""" - schema = vol.Schema(cv.has_at_least_one_key(['beer', 'soda'])) + schema = vol.Schema(cv.has_at_least_one_key('beer', 'soda')) for value in (None, [], {}, {'wine': None}): with pytest.raises(vol.MultipleInvalid):