mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 08:47:10 +00:00
Raise repair issues when automations can't be set up (#120010)
This commit is contained in:
parent
5138c3de0a
commit
d2a5683fa0
@ -65,7 +65,11 @@ from homeassistant.helpers.deprecation import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import (
|
||||||
|
IssueSeverity,
|
||||||
|
async_create_issue,
|
||||||
|
async_delete_issue,
|
||||||
|
)
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.helpers.script import (
|
from homeassistant.helpers.script import (
|
||||||
ATTR_CUR,
|
ATTR_CUR,
|
||||||
@ -98,7 +102,7 @@ from homeassistant.helpers.typing import ConfigType
|
|||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util.dt import parse_datetime
|
from homeassistant.util.dt import parse_datetime
|
||||||
|
|
||||||
from .config import AutomationConfig
|
from .config import AutomationConfig, ValidationStatus
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_ACTION,
|
CONF_ACTION,
|
||||||
CONF_INITIAL_STATE,
|
CONF_INITIAL_STATE,
|
||||||
@ -426,11 +430,15 @@ class UnavailableAutomationEntity(BaseAutomationEntity):
|
|||||||
automation_id: str | None,
|
automation_id: str | None,
|
||||||
name: str,
|
name: str,
|
||||||
raw_config: ConfigType | None,
|
raw_config: ConfigType | None,
|
||||||
|
validation_error: str,
|
||||||
|
validation_status: ValidationStatus,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize an automation entity."""
|
"""Initialize an automation entity."""
|
||||||
self._attr_name = name
|
self._attr_name = name
|
||||||
self._attr_unique_id = automation_id
|
self._attr_unique_id = automation_id
|
||||||
self.raw_config = raw_config
|
self.raw_config = raw_config
|
||||||
|
self._validation_error = validation_error
|
||||||
|
self._validation_status = validation_status
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def referenced_labels(self) -> set[str]:
|
def referenced_labels(self) -> set[str]:
|
||||||
@ -462,6 +470,30 @@ class UnavailableAutomationEntity(BaseAutomationEntity):
|
|||||||
"""Return a set of referenced entities."""
|
"""Return a set of referenced entities."""
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Create a repair issue to notify the user the automation has errors."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
async_create_issue(
|
||||||
|
self.hass,
|
||||||
|
DOMAIN,
|
||||||
|
f"{self.entity_id}_validation_{self._validation_status}",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=IssueSeverity.ERROR,
|
||||||
|
translation_key=f"validation_{self._validation_status}",
|
||||||
|
translation_placeholders={
|
||||||
|
"edit": f"/config/automation/edit/{self.unique_id}",
|
||||||
|
"entity_id": self.entity_id,
|
||||||
|
"error": self._validation_error,
|
||||||
|
"name": self._attr_name or self.entity_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
await super().async_will_remove_from_hass()
|
||||||
|
async_delete_issue(
|
||||||
|
self.hass, DOMAIN, f"{self.entity_id}_validation_{self._validation_status}"
|
||||||
|
)
|
||||||
|
|
||||||
async def async_trigger(
|
async def async_trigger(
|
||||||
self,
|
self,
|
||||||
run_variables: dict[str, Any],
|
run_variables: dict[str, Any],
|
||||||
@ -864,7 +896,8 @@ 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
|
validation_error: str | None
|
||||||
|
validation_status: ValidationStatus
|
||||||
|
|
||||||
|
|
||||||
async def _prepare_automation_config(
|
async def _prepare_automation_config(
|
||||||
@ -884,14 +917,16 @@ async def _prepare_automation_config(
|
|||||||
|
|
||||||
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
|
validation_error = cast(AutomationConfig, config_block).validation_error
|
||||||
|
validation_status = cast(AutomationConfig, config_block).validation_status
|
||||||
automation_configs.append(
|
automation_configs.append(
|
||||||
AutomationEntityConfig(
|
AutomationEntityConfig(
|
||||||
config_block,
|
config_block,
|
||||||
list_no,
|
list_no,
|
||||||
raw_blueprint_inputs,
|
raw_blueprint_inputs,
|
||||||
raw_config,
|
raw_config,
|
||||||
validation_failed,
|
validation_error,
|
||||||
|
validation_status,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -917,12 +952,14 @@ 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:
|
if automation_config.validation_status != ValidationStatus.OK:
|
||||||
entities.append(
|
entities.append(
|
||||||
UnavailableAutomationEntity(
|
UnavailableAutomationEntity(
|
||||||
automation_id,
|
automation_id,
|
||||||
name,
|
name,
|
||||||
automation_config.raw_config,
|
automation_config.raw_config,
|
||||||
|
cast(str, automation_config.validation_error),
|
||||||
|
automation_config.validation_status,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
@ -4,7 +4,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any
|
from enum import StrEnum
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
@ -73,7 +74,7 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _async_validate_config_item(
|
async def _async_validate_config_item( # noqa: C901
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
raise_on_errors: bool,
|
raise_on_errors: bool,
|
||||||
@ -86,6 +87,12 @@ async def _async_validate_config_item(
|
|||||||
with suppress(ValueError):
|
with suppress(ValueError):
|
||||||
raw_config = dict(config)
|
raw_config = dict(config)
|
||||||
|
|
||||||
|
def _humanize(err: Exception, config: ConfigType) -> str:
|
||||||
|
"""Humanize vol.Invalid, stringify other exceptions."""
|
||||||
|
if isinstance(err, vol.Invalid):
|
||||||
|
return cast(str, humanize_error(config, err))
|
||||||
|
return str(err)
|
||||||
|
|
||||||
def _log_invalid_automation(
|
def _log_invalid_automation(
|
||||||
err: Exception,
|
err: Exception,
|
||||||
automation_name: str,
|
automation_name: str,
|
||||||
@ -101,7 +108,7 @@ async def _async_validate_config_item(
|
|||||||
"Blueprint '%s' generated invalid automation with inputs %s: %s",
|
"Blueprint '%s' generated invalid automation with inputs %s: %s",
|
||||||
blueprint_inputs.blueprint.name,
|
blueprint_inputs.blueprint.name,
|
||||||
blueprint_inputs.inputs,
|
blueprint_inputs.inputs,
|
||||||
humanize_error(config, err) if isinstance(err, vol.Invalid) else err,
|
_humanize(err, config),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -109,17 +116,35 @@ async def _async_validate_config_item(
|
|||||||
"%s %s and has been disabled: %s",
|
"%s %s and has been disabled: %s",
|
||||||
automation_name,
|
automation_name,
|
||||||
problem,
|
problem,
|
||||||
humanize_error(config, err) if isinstance(err, vol.Invalid) else err,
|
_humanize(err, config),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _minimal_config() -> AutomationConfig:
|
def _set_validation_status(
|
||||||
|
automation_config: AutomationConfig,
|
||||||
|
validation_status: ValidationStatus,
|
||||||
|
validation_error: Exception,
|
||||||
|
config: ConfigType,
|
||||||
|
) -> None:
|
||||||
|
"""Set validation status."""
|
||||||
|
if uses_blueprint:
|
||||||
|
validation_status = ValidationStatus.FAILED_BLUEPRINT
|
||||||
|
automation_config.validation_status = validation_status
|
||||||
|
automation_config.validation_error = _humanize(validation_error, config)
|
||||||
|
|
||||||
|
def _minimal_config(
|
||||||
|
validation_status: ValidationStatus,
|
||||||
|
validation_error: Exception,
|
||||||
|
config: ConfigType,
|
||||||
|
) -> AutomationConfig:
|
||||||
"""Try validating id, alias and description."""
|
"""Try validating id, alias and description."""
|
||||||
minimal_config = _MINIMAL_PLATFORM_SCHEMA(config)
|
minimal_config = _MINIMAL_PLATFORM_SCHEMA(config)
|
||||||
automation_config = AutomationConfig(minimal_config)
|
automation_config = AutomationConfig(minimal_config)
|
||||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
automation_config.raw_config = raw_config
|
automation_config.raw_config = raw_config
|
||||||
automation_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
automation_config, validation_status, validation_error, config
|
||||||
|
)
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
if blueprint.is_blueprint_instance_config(config):
|
if blueprint.is_blueprint_instance_config(config):
|
||||||
@ -135,7 +160,7 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
return _minimal_config()
|
return _minimal_config(ValidationStatus.FAILED_BLUEPRINT, err, config)
|
||||||
|
|
||||||
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
||||||
|
|
||||||
@ -152,7 +177,7 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise HomeAssistantError(err) from err
|
raise HomeAssistantError(err) from err
|
||||||
return _minimal_config()
|
return _minimal_config(ValidationStatus.FAILED_BLUEPRINT, err, config)
|
||||||
|
|
||||||
automation_name = "Unnamed automation"
|
automation_name = "Unnamed automation"
|
||||||
if isinstance(config, Mapping):
|
if isinstance(config, Mapping):
|
||||||
@ -167,7 +192,7 @@ async def _async_validate_config_item(
|
|||||||
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
return _minimal_config()
|
return _minimal_config(ValidationStatus.FAILED_SCHEMA, err, config)
|
||||||
|
|
||||||
automation_config = AutomationConfig(validated_config)
|
automation_config = AutomationConfig(validated_config)
|
||||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
@ -186,7 +211,9 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
automation_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
automation_config, ValidationStatus.FAILED_TRIGGERS, err, validated_config
|
||||||
|
)
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
if CONF_CONDITION in validated_config:
|
if CONF_CONDITION in validated_config:
|
||||||
@ -203,7 +230,12 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
automation_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
automation_config,
|
||||||
|
ValidationStatus.FAILED_CONDITIONS,
|
||||||
|
err,
|
||||||
|
validated_config,
|
||||||
|
)
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -219,18 +251,32 @@ async def _async_validate_config_item(
|
|||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
automation_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
automation_config, ValidationStatus.FAILED_ACTIONS, err, validated_config
|
||||||
|
)
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
return automation_config
|
return automation_config
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationStatus(StrEnum):
|
||||||
|
"""What was changed in a config entry."""
|
||||||
|
|
||||||
|
FAILED_ACTIONS = "failed_actions"
|
||||||
|
FAILED_BLUEPRINT = "failed_blueprint"
|
||||||
|
FAILED_CONDITIONS = "failed_conditions"
|
||||||
|
FAILED_SCHEMA = "failed_schema"
|
||||||
|
FAILED_TRIGGERS = "failed_triggers"
|
||||||
|
OK = "ok"
|
||||||
|
|
||||||
|
|
||||||
class AutomationConfig(dict):
|
class AutomationConfig(dict):
|
||||||
"""Dummy class to allow adding attributes."""
|
"""Dummy class to allow adding attributes."""
|
||||||
|
|
||||||
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
|
validation_status: ValidationStatus = ValidationStatus.OK
|
||||||
|
validation_error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
async def _try_async_validate_config_item(
|
async def _try_async_validate_config_item(
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"validation_failed_title": "Automation {name} failed to set up"
|
||||||
|
},
|
||||||
"title": "Automation",
|
"title": "Automation",
|
||||||
"entity_component": {
|
"entity_component": {
|
||||||
"_": {
|
"_": {
|
||||||
@ -43,6 +46,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"validation_failed_actions": {
|
||||||
|
"title": "[%key:component::automation::common::validation_failed_title%]",
|
||||||
|
"description": "The automation \"{name}\" (`{entity_id}`) is not active because its actions could not be set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the automation]({edit}) to correct it, then save and reload the automation configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_blueprint": {
|
||||||
|
"title": "[%key:component::automation::common::validation_failed_title%]",
|
||||||
|
"description": "The blueprinted automation \"{name}\" (`{entity_id}`) failed to set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the automation]({edit}) to correct it, then save and reload the automation configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_conditions": {
|
||||||
|
"title": "[%key:component::automation::common::validation_failed_title%]",
|
||||||
|
"description": "The automation \"{name}\" (`{entity_id}`) is not active because its conditions could not be set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the automation]({edit}) to correct it, then save and reload the automation configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_schema": {
|
||||||
|
"title": "[%key:component::automation::common::validation_failed_title%]",
|
||||||
|
"description": "The automation \"{name}\" (`{entity_id}`) is not active because the configuration has errors.\n\nError:`{error}`.\n\nTo fix this error, [edit the automation]({edit}) to correct it, then save and reload the automation configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_triggers": {
|
||||||
|
"title": "[%key:component::automation::common::validation_failed_title%]",
|
||||||
|
"description": "The automation \"{name}\" (`{entity_id}`) is not active because its triggers could not be set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the automation]({edit}) to correct it, then save and reload the automation configuration."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
@ -4,7 +4,7 @@ import asyncio
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import ANY, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -1645,12 +1645,13 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("broken_config", "problem", "details"),
|
("broken_config", "problem", "details", "issue"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
{},
|
{},
|
||||||
"could not be validated",
|
"could not be validated",
|
||||||
"required key not provided @ data['action']",
|
"required key not provided @ data['action']",
|
||||||
|
"validation_failed_schema",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -1659,6 +1660,7 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
"failed to setup triggers",
|
"failed to setup triggers",
|
||||||
"Integration 'automation' does not provide trigger support.",
|
"Integration 'automation' does not provide trigger support.",
|
||||||
|
"validation_failed_triggers",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -1673,6 +1675,7 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
"failed to setup conditions",
|
"failed to setup conditions",
|
||||||
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
||||||
|
"validation_failed_conditions",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -1686,15 +1689,19 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
"failed to setup actions",
|
"failed to setup actions",
|
||||||
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
||||||
|
"validation_failed_actions",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_automation_bad_config_validation(
|
async def test_automation_bad_config_validation(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
hass_admin_user,
|
||||||
broken_config,
|
broken_config,
|
||||||
problem,
|
problem,
|
||||||
details,
|
details,
|
||||||
|
issue,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test bad automation configuration which can be detected during validation."""
|
"""Test bad automation configuration which can be detected during validation."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -1715,11 +1722,22 @@ async def test_automation_bad_config_validation(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check we get the expected error message
|
# Check we get the expected error message and issue
|
||||||
assert (
|
assert (
|
||||||
f"Automation with alias 'bad_automation' {problem} and has been disabled:"
|
f"Automation with alias 'bad_automation' {problem} and has been disabled:"
|
||||||
f" {details}"
|
f" {details}"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
issues = await get_repairs(hass, hass_ws_client)
|
||||||
|
assert len(issues) == 1
|
||||||
|
assert issues[0]["issue_id"] == f"automation.bad_automation_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/automation/edit/None",
|
||||||
|
"entity_id": "automation.bad_automation",
|
||||||
|
"error": ANY,
|
||||||
|
"name": "bad_automation",
|
||||||
|
}
|
||||||
|
assert issues[0]["translation_placeholders"]["error"].startswith(details)
|
||||||
|
|
||||||
# Make sure both automations are setup
|
# Make sure both automations are setup
|
||||||
assert set(hass.states.async_entity_ids("automation")) == {
|
assert set(hass.states.async_entity_ids("automation")) == {
|
||||||
@ -1729,6 +1747,30 @@ async def test_automation_bad_config_validation(
|
|||||||
# The automation failing validation should be unavailable
|
# The automation failing validation should be unavailable
|
||||||
assert hass.states.get("automation.bad_automation").state == STATE_UNAVAILABLE
|
assert hass.states.get("automation.bad_automation").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Reloading the automation with fixed config should clear the issue
|
||||||
|
with patch(
|
||||||
|
"homeassistant.config.load_yaml_config_file",
|
||||||
|
autospec=True,
|
||||||
|
return_value={
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"alias": "bad_automation",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event2"},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"event": "{{ trigger.event.event_type }}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
automation.DOMAIN,
|
||||||
|
SERVICE_RELOAD,
|
||||||
|
context=Context(user_id=hass_admin_user.id),
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
issues = await get_repairs(hass, hass_ws_client)
|
||||||
|
assert len(issues) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_automation_with_error_in_script(
|
async def test_automation_with_error_in_script(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -2507,6 +2549,7 @@ async def test_blueprint_automation(
|
|||||||
)
|
)
|
||||||
async def test_blueprint_automation_bad_config(
|
async def test_blueprint_automation_bad_config(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
blueprint_inputs,
|
blueprint_inputs,
|
||||||
problem,
|
problem,
|
||||||
@ -2528,9 +2571,24 @@ async def test_blueprint_automation_bad_config(
|
|||||||
assert problem in caplog.text
|
assert problem in caplog.text
|
||||||
assert details in caplog.text
|
assert details in caplog.text
|
||||||
|
|
||||||
|
issues = await get_repairs(hass, hass_ws_client)
|
||||||
|
assert len(issues) == 1
|
||||||
|
issue = "validation_failed_blueprint"
|
||||||
|
assert issues[0]["issue_id"] == f"automation.automation_0_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/automation/edit/None",
|
||||||
|
"entity_id": "automation.automation_0",
|
||||||
|
"error": ANY,
|
||||||
|
"name": "automation 0",
|
||||||
|
}
|
||||||
|
assert issues[0]["translation_placeholders"]["error"].startswith(details)
|
||||||
|
|
||||||
|
|
||||||
async def test_blueprint_automation_fails_substitution(
|
async def test_blueprint_automation_fails_substitution(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test blueprint automation with bad inputs."""
|
"""Test blueprint automation with bad inputs."""
|
||||||
with patch(
|
with patch(
|
||||||
@ -2559,6 +2617,18 @@ async def test_blueprint_automation_fails_substitution(
|
|||||||
" 'a_number': 5}: No substitution found for input blah"
|
" 'a_number': 5}: No substitution found for input blah"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
issues = await get_repairs(hass, hass_ws_client)
|
||||||
|
assert len(issues) == 1
|
||||||
|
issue = "validation_failed_blueprint"
|
||||||
|
assert issues[0]["issue_id"] == f"automation.automation_0_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/automation/edit/None",
|
||||||
|
"entity_id": "automation.automation_0",
|
||||||
|
"error": "No substitution found for input blah",
|
||||||
|
"name": "automation 0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_trigger_service(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
async def test_trigger_service(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
||||||
"""Test the automation trigger service."""
|
"""Test the automation trigger service."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user