mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Set automations which fail validation unavailable (#94856)
This commit is contained in:
parent
5c4d010b90
commit
17ac1a6d32
@ -1,6 +1,7 @@
|
|||||||
"""Allow to set up simple automation rules via the config file."""
|
"""Allow to set up simple automation rules via the config file."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@ -153,7 +154,7 @@ def _automations_with_x(
|
|||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
automation_entity.entity_id
|
automation_entity.entity_id
|
||||||
@ -169,7 +170,7 @@ def _x_in_automation(
|
|||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||||
|
|
||||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||||
return []
|
return []
|
||||||
@ -219,7 +220,7 @@ def automations_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list
|
|||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
automation_entity.entity_id
|
automation_entity.entity_id
|
||||||
@ -234,7 +235,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
|||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||||
|
|
||||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||||
return None
|
return None
|
||||||
@ -244,7 +245,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
|||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up all automations."""
|
"""Set up all automations."""
|
||||||
hass.data[DOMAIN] = component = EntityComponent[AutomationEntity](
|
hass.data[DOMAIN] = component = EntityComponent[BaseAutomationEntity](
|
||||||
LOGGER, DOMAIN, hass
|
LOGGER, DOMAIN, hass
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -262,7 +263,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
await async_get_blueprints(hass).async_populate()
|
await async_get_blueprints(hass).async_populate()
|
||||||
|
|
||||||
async def trigger_service_handler(
|
async def trigger_service_handler(
|
||||||
entity: AutomationEntity, service_call: ServiceCall
|
entity: BaseAutomationEntity, service_call: ServiceCall
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle forced automation trigger, e.g. from frontend."""
|
"""Handle forced automation trigger, e.g. from frontend."""
|
||||||
await entity.async_trigger(
|
await entity.async_trigger(
|
||||||
@ -310,7 +311,103 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class AutomationEntity(ToggleEntity, RestoreEntity):
|
class BaseAutomationEntity(ToggleEntity, ABC):
|
||||||
|
"""Base class for automation entities."""
|
||||||
|
|
||||||
|
raw_config: ConfigType | None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capability_attributes(self) -> dict[str, Any] | None:
|
||||||
|
"""Return capability attributes."""
|
||||||
|
if self.unique_id is not None:
|
||||||
|
return {CONF_ID: self.unique_id}
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def referenced_areas(self) -> set[str]:
|
||||||
|
"""Return a set of referenced areas."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def referenced_blueprint(self) -> str | None:
|
||||||
|
"""Return referenced blueprint or None."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def referenced_devices(self) -> set[str]:
|
||||||
|
"""Return a set of referenced devices."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def referenced_entities(self) -> set[str]:
|
||||||
|
"""Return a set of referenced entities."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def async_trigger(
|
||||||
|
self,
|
||||||
|
run_variables: dict[str, Any],
|
||||||
|
context: Context | None = None,
|
||||||
|
skip_condition: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Trigger automation."""
|
||||||
|
|
||||||
|
|
||||||
|
class UnavailableAutomationEntity(BaseAutomationEntity):
|
||||||
|
"""A non-functional automation entity with its state set to unavailable.
|
||||||
|
|
||||||
|
This class is instatiated when an automation fails to validate.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
_attr_available = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
automation_id: str | None,
|
||||||
|
name: str,
|
||||||
|
raw_config: ConfigType | None,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize an automation entity."""
|
||||||
|
self._name = name
|
||||||
|
self._attr_unique_id = automation_id
|
||||||
|
self.raw_config = raw_config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def referenced_areas(self) -> set[str]:
|
||||||
|
"""Return a set of referenced areas."""
|
||||||
|
return set()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def referenced_blueprint(self) -> str | None:
|
||||||
|
"""Return referenced blueprint or None."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def referenced_devices(self) -> set[str]:
|
||||||
|
"""Return a set of referenced devices."""
|
||||||
|
return set()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def referenced_entities(self) -> set[str]:
|
||||||
|
"""Return a set of referenced entities."""
|
||||||
|
return set()
|
||||||
|
|
||||||
|
async def async_trigger(
|
||||||
|
self,
|
||||||
|
run_variables: dict[str, Any],
|
||||||
|
context: Context | None = None,
|
||||||
|
skip_condition: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Trigger automation."""
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||||
"""Entity to show status of entity."""
|
"""Entity to show status of entity."""
|
||||||
|
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
@ -363,8 +460,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
|||||||
}
|
}
|
||||||
if self.action_script.supports_max:
|
if self.action_script.supports_max:
|
||||||
attrs[ATTR_MAX] = self.action_script.max_runs
|
attrs[ATTR_MAX] = self.action_script.max_runs
|
||||||
if self.unique_id is not None:
|
|
||||||
attrs[CONF_ID] = self.unique_id
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -686,6 +781,7 @@ class AutomationEntityConfig:
|
|||||||
list_no: int
|
list_no: int
|
||||||
raw_blueprint_inputs: ConfigType | None
|
raw_blueprint_inputs: ConfigType | None
|
||||||
raw_config: ConfigType | None
|
raw_config: ConfigType | None
|
||||||
|
validation_failed: bool
|
||||||
|
|
||||||
|
|
||||||
async def _prepare_automation_config(
|
async def _prepare_automation_config(
|
||||||
@ -700,9 +796,14 @@ async def _prepare_automation_config(
|
|||||||
for list_no, config_block in enumerate(conf):
|
for list_no, config_block in enumerate(conf):
|
||||||
raw_config = cast(AutomationConfig, config_block).raw_config
|
raw_config = cast(AutomationConfig, config_block).raw_config
|
||||||
raw_blueprint_inputs = cast(AutomationConfig, config_block).raw_blueprint_inputs
|
raw_blueprint_inputs = cast(AutomationConfig, config_block).raw_blueprint_inputs
|
||||||
|
validation_failed = cast(AutomationConfig, config_block).validation_failed
|
||||||
automation_configs.append(
|
automation_configs.append(
|
||||||
AutomationEntityConfig(
|
AutomationEntityConfig(
|
||||||
config_block, list_no, raw_blueprint_inputs, raw_config
|
config_block,
|
||||||
|
list_no,
|
||||||
|
raw_blueprint_inputs,
|
||||||
|
raw_config,
|
||||||
|
validation_failed,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -718,9 +819,9 @@ def _automation_name(automation_config: AutomationEntityConfig) -> str:
|
|||||||
|
|
||||||
async def _create_automation_entities(
|
async def _create_automation_entities(
|
||||||
hass: HomeAssistant, automation_configs: list[AutomationEntityConfig]
|
hass: HomeAssistant, automation_configs: list[AutomationEntityConfig]
|
||||||
) -> list[AutomationEntity]:
|
) -> list[BaseAutomationEntity]:
|
||||||
"""Create automation entities from prepared configuration."""
|
"""Create automation entities from prepared configuration."""
|
||||||
entities: list[AutomationEntity] = []
|
entities: list[BaseAutomationEntity] = []
|
||||||
|
|
||||||
for automation_config in automation_configs:
|
for automation_config in automation_configs:
|
||||||
config_block = automation_config.config_block
|
config_block = automation_config.config_block
|
||||||
@ -728,6 +829,16 @@ async def _create_automation_entities(
|
|||||||
automation_id: str | None = config_block.get(CONF_ID)
|
automation_id: str | None = config_block.get(CONF_ID)
|
||||||
name = _automation_name(automation_config)
|
name = _automation_name(automation_config)
|
||||||
|
|
||||||
|
if automation_config.validation_failed:
|
||||||
|
entities.append(
|
||||||
|
UnavailableAutomationEntity(
|
||||||
|
automation_id,
|
||||||
|
name,
|
||||||
|
automation_config.raw_config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
initial_state: bool | None = config_block.get(CONF_INITIAL_STATE)
|
initial_state: bool | None = config_block.get(CONF_INITIAL_STATE)
|
||||||
|
|
||||||
action_script = Script(
|
action_script = Script(
|
||||||
@ -786,18 +897,18 @@ async def _create_automation_entities(
|
|||||||
async def _async_process_config(
|
async def _async_process_config(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: dict[str, Any],
|
config: dict[str, Any],
|
||||||
component: EntityComponent[AutomationEntity],
|
component: EntityComponent[BaseAutomationEntity],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Process config and add automations."""
|
"""Process config and add automations."""
|
||||||
|
|
||||||
def automation_matches_config(
|
def automation_matches_config(
|
||||||
automation: AutomationEntity, config: AutomationEntityConfig
|
automation: BaseAutomationEntity, config: AutomationEntityConfig
|
||||||
) -> bool:
|
) -> bool:
|
||||||
name = _automation_name(config)
|
name = _automation_name(config)
|
||||||
return automation.name == name and automation.raw_config == config.raw_config
|
return automation.name == name and automation.raw_config == config.raw_config
|
||||||
|
|
||||||
def find_matches(
|
def find_matches(
|
||||||
automations: list[AutomationEntity],
|
automations: list[BaseAutomationEntity],
|
||||||
automation_configs: list[AutomationEntityConfig],
|
automation_configs: list[AutomationEntityConfig],
|
||||||
) -> tuple[set[int], set[int]]:
|
) -> tuple[set[int], set[int]]:
|
||||||
"""Find matches between a list of automation entities and a list of configurations.
|
"""Find matches between a list of automation entities and a list of configurations.
|
||||||
@ -843,7 +954,7 @@ async def _async_process_config(
|
|||||||
return automation_matches, config_matches
|
return automation_matches, config_matches
|
||||||
|
|
||||||
automation_configs = await _prepare_automation_config(hass, config)
|
automation_configs = await _prepare_automation_config(hass, config)
|
||||||
automations: list[AutomationEntity] = list(component.entities)
|
automations: list[BaseAutomationEntity] = list(component.entities)
|
||||||
|
|
||||||
# Find automations and configurations which have matches
|
# Find automations and configurations which have matches
|
||||||
automation_matches, config_matches = find_matches(automations, automation_configs)
|
automation_matches, config_matches = find_matches(automations, automation_configs)
|
||||||
@ -968,7 +1079,7 @@ def websocket_config(
|
|||||||
msg: dict[str, Any],
|
msg: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get automation config."""
|
"""Get automation config."""
|
||||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||||
|
|
||||||
automation = component.get_entity(msg["entity_id"])
|
automation = component.get_entity(msg["entity_id"])
|
||||||
|
|
||||||
|
@ -43,6 +43,16 @@ PACKAGE_MERGE_HINT = "list"
|
|||||||
|
|
||||||
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
||||||
|
|
||||||
|
_MINIMAL_PLATFORM_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
CONF_ID: str,
|
||||||
|
CONF_ALIAS: cv.string,
|
||||||
|
vol.Optional(CONF_DESCRIPTION): cv.string,
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = vol.All(
|
PLATFORM_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_HIDE_ENTITY),
|
cv.deprecated(CONF_HIDE_ENTITY),
|
||||||
script.make_script_schema(
|
script.make_script_schema(
|
||||||
@ -68,6 +78,7 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
async def _async_validate_config_item(
|
async def _async_validate_config_item(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
|
raise_on_errors: bool,
|
||||||
warn_on_errors: bool,
|
warn_on_errors: bool,
|
||||||
) -> AutomationConfig:
|
) -> AutomationConfig:
|
||||||
"""Validate config item."""
|
"""Validate config item."""
|
||||||
@ -104,6 +115,15 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _minimal_config() -> AutomationConfig:
|
||||||
|
"""Try validating id, alias and description."""
|
||||||
|
minimal_config = _MINIMAL_PLATFORM_SCHEMA(config)
|
||||||
|
automation_config = AutomationConfig(minimal_config)
|
||||||
|
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
|
automation_config.raw_config = raw_config
|
||||||
|
automation_config.validation_failed = True
|
||||||
|
return automation_config
|
||||||
|
|
||||||
if blueprint.is_blueprint_instance_config(config):
|
if blueprint.is_blueprint_instance_config(config):
|
||||||
uses_blueprint = True
|
uses_blueprint = True
|
||||||
blueprints = async_get_blueprints(hass)
|
blueprints = async_get_blueprints(hass)
|
||||||
@ -115,7 +135,9 @@ async def _async_validate_config_item(
|
|||||||
"Failed to generate automation from blueprint: %s",
|
"Failed to generate automation from blueprint: %s",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
raise
|
if raise_on_errors:
|
||||||
|
raise
|
||||||
|
return _minimal_config()
|
||||||
|
|
||||||
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
||||||
|
|
||||||
@ -130,7 +152,9 @@ async def _async_validate_config_item(
|
|||||||
blueprint_inputs.inputs,
|
blueprint_inputs.inputs,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
raise HomeAssistantError from err
|
if raise_on_errors:
|
||||||
|
raise HomeAssistantError(err) from err
|
||||||
|
return _minimal_config()
|
||||||
|
|
||||||
automation_name = "Unnamed automation"
|
automation_name = "Unnamed automation"
|
||||||
if isinstance(config, Mapping):
|
if isinstance(config, Mapping):
|
||||||
@ -143,10 +167,16 @@ async def _async_validate_config_item(
|
|||||||
validated_config = PLATFORM_SCHEMA(config)
|
validated_config = PLATFORM_SCHEMA(config)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
||||||
raise
|
if raise_on_errors:
|
||||||
|
raise
|
||||||
|
return _minimal_config()
|
||||||
|
|
||||||
|
automation_config = AutomationConfig(validated_config)
|
||||||
|
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
|
automation_config.raw_config = raw_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validated_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
automation_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||||
hass, validated_config[CONF_TRIGGER]
|
hass, validated_config[CONF_TRIGGER]
|
||||||
)
|
)
|
||||||
except (
|
except (
|
||||||
@ -156,11 +186,14 @@ async def _async_validate_config_item(
|
|||||||
_log_invalid_automation(
|
_log_invalid_automation(
|
||||||
err, automation_name, "failed to setup triggers", validated_config
|
err, automation_name, "failed to setup triggers", validated_config
|
||||||
)
|
)
|
||||||
raise
|
if raise_on_errors:
|
||||||
|
raise
|
||||||
|
automation_config.validation_failed = True
|
||||||
|
return automation_config
|
||||||
|
|
||||||
if CONF_CONDITION in validated_config:
|
if CONF_CONDITION in validated_config:
|
||||||
try:
|
try:
|
||||||
validated_config[CONF_CONDITION] = await async_validate_conditions_config(
|
automation_config[CONF_CONDITION] = await async_validate_conditions_config(
|
||||||
hass, validated_config[CONF_CONDITION]
|
hass, validated_config[CONF_CONDITION]
|
||||||
)
|
)
|
||||||
except (
|
except (
|
||||||
@ -170,10 +203,13 @@ async def _async_validate_config_item(
|
|||||||
_log_invalid_automation(
|
_log_invalid_automation(
|
||||||
err, automation_name, "failed to setup conditions", validated_config
|
err, automation_name, "failed to setup conditions", validated_config
|
||||||
)
|
)
|
||||||
raise
|
if raise_on_errors:
|
||||||
|
raise
|
||||||
|
automation_config.validation_failed = True
|
||||||
|
return automation_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validated_config[CONF_ACTION] = await script.async_validate_actions_config(
|
automation_config[CONF_ACTION] = await script.async_validate_actions_config(
|
||||||
hass, validated_config[CONF_ACTION]
|
hass, validated_config[CONF_ACTION]
|
||||||
)
|
)
|
||||||
except (
|
except (
|
||||||
@ -183,11 +219,11 @@ async def _async_validate_config_item(
|
|||||||
_log_invalid_automation(
|
_log_invalid_automation(
|
||||||
err, automation_name, "failed to setup actions", validated_config
|
err, automation_name, "failed to setup actions", validated_config
|
||||||
)
|
)
|
||||||
raise
|
if raise_on_errors:
|
||||||
|
raise
|
||||||
|
automation_config.validation_failed = True
|
||||||
|
return automation_config
|
||||||
|
|
||||||
automation_config = AutomationConfig(validated_config)
|
|
||||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
|
||||||
automation_config.raw_config = raw_config
|
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
|
|
||||||
@ -196,6 +232,7 @@ class AutomationConfig(dict):
|
|||||||
|
|
||||||
raw_config: dict[str, Any] | None = None
|
raw_config: dict[str, Any] | None = None
|
||||||
raw_blueprint_inputs: dict[str, Any] | None = None
|
raw_blueprint_inputs: dict[str, Any] | None = None
|
||||||
|
validation_failed: bool = False
|
||||||
|
|
||||||
|
|
||||||
async def _try_async_validate_config_item(
|
async def _try_async_validate_config_item(
|
||||||
@ -204,7 +241,7 @@ async def _try_async_validate_config_item(
|
|||||||
) -> AutomationConfig | None:
|
) -> AutomationConfig | None:
|
||||||
"""Validate config item."""
|
"""Validate config item."""
|
||||||
try:
|
try:
|
||||||
return await _async_validate_config_item(hass, config, True)
|
return await _async_validate_config_item(hass, config, False, True)
|
||||||
except (vol.Invalid, HomeAssistantError):
|
except (vol.Invalid, HomeAssistantError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -215,7 +252,7 @@ async def async_validate_config_item(
|
|||||||
config: dict[str, Any],
|
config: dict[str, Any],
|
||||||
) -> AutomationConfig | None:
|
) -> AutomationConfig | None:
|
||||||
"""Validate config item, called by EditAutomationConfigView."""
|
"""Validate config item, called by EditAutomationConfigView."""
|
||||||
return await _async_validate_config_item(hass, config, False)
|
return await _async_validate_config_item(hass, config, True, False)
|
||||||
|
|
||||||
|
|
||||||
async def async_validate_config(hass: HomeAssistant, config: ConfigType) -> ConfigType:
|
async def async_validate_config(hass: HomeAssistant, config: ConfigType) -> ConfigType:
|
||||||
|
@ -25,6 +25,7 @@ from homeassistant.const import (
|
|||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
Context,
|
Context,
|
||||||
@ -1428,8 +1429,13 @@ async def test_automation_bad_config_validation(
|
|||||||
f" {details}"
|
f" {details}"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
# Make sure one bad automation does not prevent other automations from setting up
|
# Make sure both automations are setup
|
||||||
assert hass.states.async_entity_ids("automation") == ["automation.good_automation"]
|
assert set(hass.states.async_entity_ids("automation")) == {
|
||||||
|
"automation.bad_automation",
|
||||||
|
"automation.good_automation",
|
||||||
|
}
|
||||||
|
# The automation failing validation should be unavailable
|
||||||
|
assert hass.states.get("automation.bad_automation").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_automation_with_error_in_script(
|
async def test_automation_with_error_in_script(
|
||||||
@ -1558,6 +1564,31 @@ async def test_extraction_functions_unknown_automation(hass: HomeAssistant) -> N
|
|||||||
assert automation.entities_in_automation(hass, "automation.unknown") == []
|
assert automation.entities_in_automation(hass, "automation.unknown") == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_extraction_functions_unavailable_automation(hass: HomeAssistant) -> None:
|
||||||
|
"""Test extraction functions for an unknown automation."""
|
||||||
|
entity_id = "automation.test1"
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: [
|
||||||
|
{
|
||||||
|
"alias": "test1",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||||
|
assert automation.automations_with_area(hass, "area-in-both") == []
|
||||||
|
assert automation.areas_in_automation(hass, entity_id) == []
|
||||||
|
assert automation.automations_with_blueprint(hass, "blabla.yaml") == []
|
||||||
|
assert automation.blueprint_in_automation(hass, entity_id) is None
|
||||||
|
assert automation.automations_with_device(hass, "device-in-both") == []
|
||||||
|
assert automation.devices_in_automation(hass, entity_id) == []
|
||||||
|
assert automation.automations_with_entity(hass, "light.in_both") == []
|
||||||
|
assert automation.entities_in_automation(hass, entity_id) == []
|
||||||
|
|
||||||
|
|
||||||
async def test_extraction_functions(hass: HomeAssistant) -> None:
|
async def test_extraction_functions(hass: HomeAssistant) -> None:
|
||||||
"""Test extraction functions."""
|
"""Test extraction functions."""
|
||||||
await async_setup_component(hass, "homeassistant", {})
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
"""Test Automation config panel."""
|
"""Test Automation config panel."""
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import json
|
import json
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.bootstrap import async_setup_component
|
from homeassistant.bootstrap import async_setup_component
|
||||||
from homeassistant.components import config
|
from homeassistant.components import config
|
||||||
|
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.util import yaml
|
||||||
|
|
||||||
from tests.typing import ClientSessionGenerator
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
@ -75,8 +78,11 @@ async def test_update_automation_config(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert sorted(hass.states.async_entity_ids("automation")) == [
|
assert sorted(hass.states.async_entity_ids("automation")) == [
|
||||||
"automation.automation_0"
|
"automation.automation_0",
|
||||||
|
"automation.automation_1",
|
||||||
]
|
]
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
assert hass.states.get("automation.automation_1").state == STATE_ON
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
@ -88,12 +94,61 @@ async def test_update_automation_config(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("automation_config", ({},))
|
@pytest.mark.parametrize("automation_config", ({},))
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("updated_config", "validation_error"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{"action": []},
|
||||||
|
"required key not provided @ data['trigger']",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"trigger": {"platform": "automation"},
|
||||||
|
"action": [],
|
||||||
|
},
|
||||||
|
"Integration 'automation' does not provide trigger support",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"condition": {
|
||||||
|
"condition": "state",
|
||||||
|
# The UUID will fail being resolved to en entity_id
|
||||||
|
"entity_id": "abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
"state": "blah",
|
||||||
|
},
|
||||||
|
"action": [],
|
||||||
|
},
|
||||||
|
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"action": {
|
||||||
|
"condition": "state",
|
||||||
|
# The UUID will fail being resolved to en entity_id
|
||||||
|
"entity_id": "abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
"state": "blah",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"use_blueprint": {"path": "test_event_service.yaml", "input": {}},
|
||||||
|
},
|
||||||
|
"Missing input a_number, service_to_call, trigger_event",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_update_automation_config_with_error(
|
async def test_update_automation_config_with_error(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
hass_config_store,
|
hass_config_store,
|
||||||
setup_automation,
|
setup_automation,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
updated_config: Any,
|
||||||
|
validation_error: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test updating automation config with errors."""
|
"""Test updating automation config with errors."""
|
||||||
with patch.object(config, "SECTIONS", ["automation"]):
|
with patch.object(config, "SECTIONS", ["automation"]):
|
||||||
@ -108,14 +163,70 @@ async def test_update_automation_config_with_error(
|
|||||||
|
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
"/api/config/automation/config/moon",
|
"/api/config/automation/config/moon",
|
||||||
data=json.dumps({"action": []}),
|
data=json.dumps(updated_config),
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert sorted(hass.states.async_entity_ids("automation")) == []
|
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||||
|
|
||||||
assert resp.status != HTTPStatus.OK
|
assert resp.status != HTTPStatus.OK
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
validation_error = "required key not provided @ data['trigger']"
|
assert result == {"message": f"Message malformed: {validation_error}"}
|
||||||
|
# Assert the validation error is not logged
|
||||||
|
assert validation_error not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("automation_config", ({},))
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("updated_config", "validation_error"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"use_blueprint": {
|
||||||
|
"path": "test_event_service.yaml",
|
||||||
|
"input": {
|
||||||
|
"trigger_event": "test_event",
|
||||||
|
"service_to_call": "test.automation",
|
||||||
|
"a_number": 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"No substitution found for input blah",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_update_automation_config_with_blueprint_substitution_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
hass_config_store,
|
||||||
|
setup_automation,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
updated_config: Any,
|
||||||
|
validation_error: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test updating automation config with errors."""
|
||||||
|
with patch.object(config, "SECTIONS", ["automation"]):
|
||||||
|
await async_setup_component(hass, "config", {})
|
||||||
|
|
||||||
|
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
orig_data = [{"id": "sun"}, {"id": "moon"}]
|
||||||
|
hass_config_store["automations.yaml"] = orig_data
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.blueprint.models.BlueprintInputs.async_substitute",
|
||||||
|
side_effect=yaml.UndefinedSubstitution("blah"),
|
||||||
|
):
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/config/automation/config/moon",
|
||||||
|
data=json.dumps(updated_config),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert sorted(hass.states.async_entity_ids("automation")) == []
|
||||||
|
|
||||||
|
assert resp.status != HTTPStatus.OK
|
||||||
|
result = await resp.json()
|
||||||
assert result == {"message": f"Message malformed: {validation_error}"}
|
assert result == {"message": f"Message malformed: {validation_error}"}
|
||||||
# Assert the validation error is not logged
|
# Assert the validation error is not logged
|
||||||
assert validation_error not in caplog.text
|
assert validation_error not in caplog.text
|
||||||
@ -145,8 +256,11 @@ async def test_update_remove_key_automation_config(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert sorted(hass.states.async_entity_ids("automation")) == [
|
assert sorted(hass.states.async_entity_ids("automation")) == [
|
||||||
"automation.automation_0"
|
"automation.automation_0",
|
||||||
|
"automation.automation_1",
|
||||||
]
|
]
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
assert hass.states.get("automation.automation_1").state == STATE_ON
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
@ -187,8 +301,11 @@ async def test_bad_formatted_automations(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert sorted(hass.states.async_entity_ids("automation")) == [
|
assert sorted(hass.states.async_entity_ids("automation")) == [
|
||||||
"automation.automation_0"
|
"automation.automation_0",
|
||||||
|
"automation.automation_1",
|
||||||
]
|
]
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
assert hass.states.get("automation.automation_1").state == STATE_ON
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
result = await resp.json()
|
result = await resp.json()
|
||||||
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
|||||||
CONF_DOMAIN,
|
CONF_DOMAIN,
|
||||||
CONF_PLATFORM,
|
CONF_PLATFORM,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
@ -440,7 +441,9 @@ async def test_validate_trigger_unsupported_device(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0
|
automations = hass.states.async_entity_ids(AUTOMATION_DOMAIN)
|
||||||
|
assert len(automations) == 1
|
||||||
|
assert hass.states.get(automations[0]).state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_validate_trigger_unsupported_trigger(
|
async def test_validate_trigger_unsupported_trigger(
|
||||||
@ -481,7 +484,9 @@ async def test_validate_trigger_unsupported_trigger(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(hass.states.async_entity_ids(AUTOMATION_DOMAIN)) == 0
|
automations = hass.states.async_entity_ids(AUTOMATION_DOMAIN)
|
||||||
|
assert len(automations) == 1
|
||||||
|
assert hass.states.get(automations[0]).state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_attach_trigger_no_matching_event(
|
async def test_attach_trigger_no_matching_event(
|
||||||
|
@ -10,7 +10,12 @@ import homeassistant.components.automation as automation
|
|||||||
from homeassistant.components.homeassistant.triggers import (
|
from homeassistant.components.homeassistant.triggers import (
|
||||||
numeric_state as numeric_state_trigger,
|
numeric_state as numeric_state_trigger,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ENTITY_MATCH_ALL,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
from homeassistant.core import Context, HomeAssistant
|
from homeassistant.core import Context, HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -1090,7 +1095,7 @@ async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls, above, below)
|
|||||||
hass.states.async_set("test.entity", 5)
|
hass.states.async_set("test.entity", 5)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -1107,13 +1112,14 @@ async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls, above, below)
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fails_setup_for_without_above_below(
|
async def test_if_fails_setup_for_without_above_below(
|
||||||
hass: HomeAssistant, calls
|
hass: HomeAssistant, calls
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test for setup failures for missing above or below."""
|
"""Test for setup failures for missing above or below."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -1128,6 +1134,7 @@ async def test_if_fails_setup_for_without_above_below(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -6,7 +6,12 @@ import pytest
|
|||||||
|
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.homeassistant.triggers import state as state_trigger
|
from homeassistant.components.homeassistant.triggers import state as state_trigger
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ENTITY_MATCH_ALL,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
from homeassistant.core import Context, HomeAssistant, ServiceCall
|
from homeassistant.core import Context, HomeAssistant, ServiceCall
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -554,7 +559,7 @@ async def test_if_action(hass: HomeAssistant, calls) -> None:
|
|||||||
|
|
||||||
async def test_if_fails_setup_if_to_boolean_value(hass: HomeAssistant, calls) -> None:
|
async def test_if_fails_setup_if_to_boolean_value(hass: HomeAssistant, calls) -> None:
|
||||||
"""Test for setup failure for boolean to."""
|
"""Test for setup failure for boolean to."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -569,11 +574,12 @@ async def test_if_fails_setup_if_to_boolean_value(hass: HomeAssistant, calls) ->
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fails_setup_if_from_boolean_value(hass: HomeAssistant, calls) -> None:
|
async def test_if_fails_setup_if_from_boolean_value(hass: HomeAssistant, calls) -> None:
|
||||||
"""Test for setup failure for boolean from."""
|
"""Test for setup failure for boolean from."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -588,11 +594,12 @@ async def test_if_fails_setup_if_from_boolean_value(hass: HomeAssistant, calls)
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls) -> None:
|
async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls) -> None:
|
||||||
"""Test for setup failure for bad for."""
|
"""Test for setup failure for bad for."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -608,6 +615,7 @@ async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls) -> None:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_if_not_fires_on_entity_change_with_for(
|
async def test_if_not_fires_on_entity_change_with_for(
|
||||||
@ -1018,7 +1026,7 @@ async def test_if_fires_on_for_condition_attribute_change(
|
|||||||
|
|
||||||
async def test_if_fails_setup_for_without_time(hass: HomeAssistant, calls) -> None:
|
async def test_if_fails_setup_for_without_time(hass: HomeAssistant, calls) -> None:
|
||||||
"""Test for setup failure if no time is provided."""
|
"""Test for setup failure if no time is provided."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -1035,11 +1043,12 @@ async def test_if_fails_setup_for_without_time(hass: HomeAssistant, calls) -> No
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fails_setup_for_without_entity(hass: HomeAssistant, calls) -> None:
|
async def test_if_fails_setup_for_without_entity(hass: HomeAssistant, calls) -> None:
|
||||||
"""Test for setup failure if no entity is provided."""
|
"""Test for setup failure if no entity is provided."""
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -1055,6 +1064,7 @@ async def test_if_fails_setup_for_without_entity(hass: HomeAssistant, calls) ->
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_wait_template_with_trigger(hass: HomeAssistant, calls) -> None:
|
async def test_wait_template_with_trigger(hass: HomeAssistant, calls) -> None:
|
||||||
|
@ -8,7 +8,12 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import automation
|
from homeassistant.components import automation
|
||||||
from homeassistant.components.homeassistant.triggers import time
|
from homeassistant.components.homeassistant.triggers import time
|
||||||
from homeassistant.components.sensor import SensorDeviceClass
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, SERVICE_TURN_OFF
|
from homeassistant.const import (
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -210,7 +215,7 @@ async def test_if_not_fires_using_wrong_at(hass: HomeAssistant, calls) -> None:
|
|||||||
with patch(
|
with patch(
|
||||||
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
):
|
):
|
||||||
with assert_setup_component(0, automation.DOMAIN):
|
with assert_setup_component(1, automation.DOMAIN):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
automation.DOMAIN,
|
automation.DOMAIN,
|
||||||
@ -226,6 +231,7 @@ async def test_if_not_fires_using_wrong_at(hass: HomeAssistant, calls) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
async_fire_time_changed(
|
async_fire_time_changed(
|
||||||
hass, now.replace(year=now.year + 1, hour=1, minute=0, second=5)
|
hass, now.replace(year=now.year + 1, hour=1, minute=0, second=5)
|
||||||
|
@ -7,7 +7,12 @@ import pytest
|
|||||||
|
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
from homeassistant.components.template import trigger as template_trigger
|
from homeassistant.components.template import trigger as template_trigger
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_OFF
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ENTITY_MATCH_ALL,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
from homeassistant.core import Context, HomeAssistant, callback
|
from homeassistant.core import Context, HomeAssistant, callback
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -389,7 +394,7 @@ async def test_if_action(hass: HomeAssistant, start_ha, calls) -> None:
|
|||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("count", "domain"), [(0, automation.DOMAIN)])
|
@pytest.mark.parametrize(("count", "domain"), [(1, automation.DOMAIN)])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"config",
|
"config",
|
||||||
[
|
[
|
||||||
@ -405,6 +410,7 @@ async def test_if_fires_on_change_with_bad_template(
|
|||||||
hass: HomeAssistant, start_ha, calls
|
hass: HomeAssistant, start_ha, calls
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test for firing on change with bad template."""
|
"""Test for firing on change with bad template."""
|
||||||
|
assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(("count", "domain"), [(1, automation.DOMAIN)])
|
@pytest.mark.parametrize(("count", "domain"), [(1, automation.DOMAIN)])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user