mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Raise repair issues when scripts can't be set up (#122087)
This commit is contained in:
parent
e2276458ed
commit
0927dd9090
@ -43,6 +43,11 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
from homeassistant.helpers.config_validation import make_entity_service_schema
|
from homeassistant.helpers.config_validation import make_entity_service_schema
|
||||||
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,
|
||||||
|
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,
|
||||||
@ -60,7 +65,7 @@ from homeassistant.loader import bind_hass
|
|||||||
from homeassistant.util.async_ import create_eager_task
|
from homeassistant.util.async_ import create_eager_task
|
||||||
from homeassistant.util.dt import parse_datetime
|
from homeassistant.util.dt import parse_datetime
|
||||||
|
|
||||||
from .config import ScriptConfig
|
from .config import ScriptConfig, ValidationStatus
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_LAST_ACTION,
|
ATTR_LAST_ACTION,
|
||||||
ATTR_LAST_TRIGGERED,
|
ATTR_LAST_TRIGGERED,
|
||||||
@ -288,7 +293,8 @@ class ScriptEntityConfig:
|
|||||||
key: str
|
key: str
|
||||||
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_script_config(
|
async def _prepare_script_config(
|
||||||
@ -303,11 +309,17 @@ async def _prepare_script_config(
|
|||||||
for key, config_block in conf.items():
|
for key, config_block in conf.items():
|
||||||
raw_config = cast(ScriptConfig, config_block).raw_config
|
raw_config = cast(ScriptConfig, config_block).raw_config
|
||||||
raw_blueprint_inputs = cast(ScriptConfig, config_block).raw_blueprint_inputs
|
raw_blueprint_inputs = cast(ScriptConfig, config_block).raw_blueprint_inputs
|
||||||
validation_failed = cast(ScriptConfig, config_block).validation_failed
|
validation_error = cast(ScriptConfig, config_block).validation_error
|
||||||
|
validation_status = cast(ScriptConfig, config_block).validation_status
|
||||||
|
|
||||||
script_configs.append(
|
script_configs.append(
|
||||||
ScriptEntityConfig(
|
ScriptEntityConfig(
|
||||||
config_block, key, raw_blueprint_inputs, raw_config, validation_failed
|
config_block,
|
||||||
|
key,
|
||||||
|
raw_blueprint_inputs,
|
||||||
|
raw_config,
|
||||||
|
validation_error,
|
||||||
|
validation_status,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -321,11 +333,13 @@ async def _create_script_entities(
|
|||||||
entities: list[BaseScriptEntity] = []
|
entities: list[BaseScriptEntity] = []
|
||||||
|
|
||||||
for script_config in script_configs:
|
for script_config in script_configs:
|
||||||
if script_config.validation_failed:
|
if script_config.validation_status != ValidationStatus.OK:
|
||||||
entities.append(
|
entities.append(
|
||||||
UnavailableScriptEntity(
|
UnavailableScriptEntity(
|
||||||
script_config.key,
|
script_config.key,
|
||||||
script_config.raw_config,
|
script_config.raw_config,
|
||||||
|
cast(str, script_config.validation_error),
|
||||||
|
script_config.validation_status,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -457,11 +471,15 @@ class UnavailableScriptEntity(BaseScriptEntity):
|
|||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
raw_config: ConfigType | None,
|
raw_config: ConfigType | None,
|
||||||
|
validation_error: str,
|
||||||
|
validation_status: ValidationStatus,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a script entity."""
|
"""Initialize a script entity."""
|
||||||
self._attr_name = raw_config.get(CONF_ALIAS, key) if raw_config else key
|
self._attr_name = raw_config.get(CONF_ALIAS, key) if raw_config else key
|
||||||
self._attr_unique_id = key
|
self._attr_unique_id = key
|
||||||
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]:
|
||||||
@ -493,6 +511,31 @@ class UnavailableScriptEntity(BaseScriptEntity):
|
|||||||
"""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/script/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:
|
||||||
|
"""Run when entity will be removed from hass."""
|
||||||
|
await super().async_will_remove_from_hass()
|
||||||
|
async_delete_issue(
|
||||||
|
self.hass, DOMAIN, f"{self.entity_id}_validation_{self._validation_status}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ScriptEntity(BaseScriptEntity, RestoreEntity):
|
class ScriptEntity(BaseScriptEntity, RestoreEntity):
|
||||||
"""Representation of a script entity."""
|
"""Representation of a script entity."""
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from enum import StrEnum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -118,6 +119,12 @@ async def _async_validate_config_item(
|
|||||||
with suppress(ValueError): # Invalid config
|
with suppress(ValueError): # Invalid config
|
||||||
raw_config = dict(config)
|
raw_config = dict(config)
|
||||||
|
|
||||||
|
def _humanize(err: Exception, data: Any) -> str:
|
||||||
|
"""Humanize vol.Invalid, stringify other exceptions."""
|
||||||
|
if isinstance(err, vol.Invalid):
|
||||||
|
return humanize_error(data, err)
|
||||||
|
return str(err)
|
||||||
|
|
||||||
def _log_invalid_script(
|
def _log_invalid_script(
|
||||||
err: Exception,
|
err: Exception,
|
||||||
script_name: str,
|
script_name: str,
|
||||||
@ -133,7 +140,7 @@ async def _async_validate_config_item(
|
|||||||
"Blueprint '%s' generated invalid script with inputs %s: %s",
|
"Blueprint '%s' generated invalid script with inputs %s: %s",
|
||||||
blueprint_inputs.blueprint.name,
|
blueprint_inputs.blueprint.name,
|
||||||
blueprint_inputs.inputs,
|
blueprint_inputs.inputs,
|
||||||
humanize_error(data, err) if isinstance(err, vol.Invalid) else err,
|
_humanize(err, data),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -141,17 +148,35 @@ async def _async_validate_config_item(
|
|||||||
"%s %s and has been disabled: %s",
|
"%s %s and has been disabled: %s",
|
||||||
script_name,
|
script_name,
|
||||||
problem,
|
problem,
|
||||||
humanize_error(data, err) if isinstance(err, vol.Invalid) else err,
|
_humanize(err, data),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _minimal_config() -> ScriptConfig:
|
def _set_validation_status(
|
||||||
|
script_config: ScriptConfig,
|
||||||
|
validation_status: ValidationStatus,
|
||||||
|
validation_error: Exception,
|
||||||
|
config: ConfigType,
|
||||||
|
) -> None:
|
||||||
|
"""Set validation status."""
|
||||||
|
if uses_blueprint:
|
||||||
|
validation_status = ValidationStatus.FAILED_BLUEPRINT
|
||||||
|
script_config.validation_status = validation_status
|
||||||
|
script_config.validation_error = _humanize(validation_error, config)
|
||||||
|
|
||||||
|
def _minimal_config(
|
||||||
|
validation_status: ValidationStatus,
|
||||||
|
validation_error: Exception,
|
||||||
|
config: ConfigType,
|
||||||
|
) -> ScriptConfig:
|
||||||
"""Try validating id, alias and description."""
|
"""Try validating id, alias and description."""
|
||||||
minimal_config = _MINIMAL_SCRIPT_ENTITY_SCHEMA(config)
|
minimal_config = _MINIMAL_SCRIPT_ENTITY_SCHEMA(config)
|
||||||
script_config = ScriptConfig(minimal_config)
|
script_config = ScriptConfig(minimal_config)
|
||||||
script_config.raw_blueprint_inputs = raw_blueprint_inputs
|
script_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
script_config.raw_config = raw_config
|
script_config.raw_config = raw_config
|
||||||
script_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
script_config, validation_status, validation_error, config
|
||||||
|
)
|
||||||
return script_config
|
return script_config
|
||||||
|
|
||||||
if is_blueprint_instance_config(config):
|
if is_blueprint_instance_config(config):
|
||||||
@ -167,7 +192,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
|
||||||
|
|
||||||
@ -184,7 +209,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)
|
||||||
|
|
||||||
script_name = f"Script with object id '{object_id}'"
|
script_name = f"Script with object id '{object_id}'"
|
||||||
if isinstance(config, Mapping):
|
if isinstance(config, Mapping):
|
||||||
@ -202,7 +227,7 @@ async def _async_validate_config_item(
|
|||||||
_log_invalid_script(err, script_name, "could not be validated", config)
|
_log_invalid_script(err, script_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)
|
||||||
|
|
||||||
script_config = ScriptConfig(validated_config)
|
script_config = ScriptConfig(validated_config)
|
||||||
script_config.raw_blueprint_inputs = raw_blueprint_inputs
|
script_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||||
@ -217,22 +242,34 @@ async def _async_validate_config_item(
|
|||||||
HomeAssistantError,
|
HomeAssistantError,
|
||||||
) as err:
|
) as err:
|
||||||
_log_invalid_script(
|
_log_invalid_script(
|
||||||
err, script_name, "failed to setup actions", validated_config
|
err, script_name, "failed to setup sequence", validated_config
|
||||||
)
|
)
|
||||||
if raise_on_errors:
|
if raise_on_errors:
|
||||||
raise
|
raise
|
||||||
script_config.validation_failed = True
|
_set_validation_status(
|
||||||
|
script_config, ValidationStatus.FAILED_SEQUENCE, err, validated_config
|
||||||
|
)
|
||||||
return script_config
|
return script_config
|
||||||
|
|
||||||
return script_config
|
return script_config
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationStatus(StrEnum):
|
||||||
|
"""What was changed in a config entry."""
|
||||||
|
|
||||||
|
FAILED_BLUEPRINT = "failed_blueprint"
|
||||||
|
FAILED_SCHEMA = "failed_schema"
|
||||||
|
FAILED_SEQUENCE = "failed_sequence"
|
||||||
|
OK = "ok"
|
||||||
|
|
||||||
|
|
||||||
class ScriptConfig(dict):
|
class ScriptConfig(dict):
|
||||||
"""Dummy class to allow adding attributes."""
|
"""Dummy class to allow adding attributes."""
|
||||||
|
|
||||||
raw_config: ConfigType | None = None
|
raw_config: ConfigType | None = None
|
||||||
raw_blueprint_inputs: ConfigType | None = None
|
raw_blueprint_inputs: ConfigType | 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": "Script {name} failed to set up"
|
||||||
|
},
|
||||||
"title": "Script",
|
"title": "Script",
|
||||||
"entity_component": {
|
"entity_component": {
|
||||||
"_": {
|
"_": {
|
||||||
@ -32,6 +35,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"issues": {
|
||||||
|
"validation_failed_blueprint": {
|
||||||
|
"title": "[%key:component::script::common::validation_failed_title%]",
|
||||||
|
"description": "The blueprinted script \"{name}\" (`{entity_id}`) failed to set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the script]({edit}) to correct it, then save and reload the script configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_schema": {
|
||||||
|
"title": "[%key:component::script::common::validation_failed_title%]",
|
||||||
|
"description": "The script \"{name}\" (`{entity_id}`) is not active because the configuration has errors.\n\nError:`{error}`.\n\nTo fix this error, [edit the script]({edit}) to correct it, then save and reload the script configuration."
|
||||||
|
},
|
||||||
|
"validation_failed_sequence": {
|
||||||
|
"title": "[%key:component::script::common::validation_failed_title%]",
|
||||||
|
"description": "The script \"{name}\" (`{entity_id}`) is not active because its sequence could not be set up.\n\nError:`{error}`.\n\nTo fix this error, [edit the script]({edit}) to correct it, then save and reload the script configuration."
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"reload": {
|
"reload": {
|
||||||
"name": "[%key:common::action::reload%]",
|
"name": "[%key:common::action::reload%]",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
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
|
||||||
|
|
||||||
@ -47,11 +47,13 @@ import homeassistant.util.dt as dt_util
|
|||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
MockUser,
|
||||||
async_fire_time_changed,
|
async_fire_time_changed,
|
||||||
async_mock_service,
|
async_mock_service,
|
||||||
mock_restore_cache,
|
mock_restore_cache,
|
||||||
)
|
)
|
||||||
from tests.components.logbook.common import MockRow, mock_humanify
|
from tests.components.logbook.common import MockRow, mock_humanify
|
||||||
|
from tests.components.repairs import get_repairs
|
||||||
from tests.typing import WebSocketGenerator
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
ENTITY_ID = "script.test"
|
ENTITY_ID = "script.test"
|
||||||
@ -252,13 +254,14 @@ async def test_bad_config_validation_critical(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("object_id", "broken_config", "problem", "details"),
|
("object_id", "broken_config", "problem", "details", "issue"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"bad_script",
|
"bad_script",
|
||||||
{},
|
{},
|
||||||
"could not be validated",
|
"could not be validated",
|
||||||
"required key not provided @ data['sequence']",
|
"required key not provided @ data['sequence']",
|
||||||
|
"validation_failed_schema",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"bad_script",
|
"bad_script",
|
||||||
@ -270,18 +273,22 @@ async def test_bad_config_validation_critical(
|
|||||||
"state": "blah",
|
"state": "blah",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"failed to setup actions",
|
"failed to setup sequence",
|
||||||
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
"Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.",
|
||||||
|
"validation_failed_sequence",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_bad_config_validation(
|
async def test_bad_config_validation(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
hass_admin_user: MockUser,
|
||||||
object_id,
|
object_id,
|
||||||
broken_config,
|
broken_config,
|
||||||
problem,
|
problem,
|
||||||
details,
|
details,
|
||||||
|
issue,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test bad script configuration which can be detected during validation."""
|
"""Test bad script configuration which can be detected during validation."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
@ -301,11 +308,22 @@ async def test_bad_config_validation(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check we get the expected error message
|
# Check we get the expected error message and issue
|
||||||
assert (
|
assert (
|
||||||
f"Script with alias 'bad_script' {problem} and has been disabled: {details}"
|
f"Script with alias 'bad_script' {problem} and has been disabled: {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"script.bad_script_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/script/edit/bad_script",
|
||||||
|
"entity_id": "script.bad_script",
|
||||||
|
"error": ANY,
|
||||||
|
"name": "bad_script",
|
||||||
|
}
|
||||||
|
assert issues[0]["translation_placeholders"]["error"].startswith(details)
|
||||||
|
|
||||||
# Make sure both scripts are setup
|
# Make sure both scripts are setup
|
||||||
assert set(hass.states.async_entity_ids("script")) == {
|
assert set(hass.states.async_entity_ids("script")) == {
|
||||||
@ -315,6 +333,31 @@ async def test_bad_config_validation(
|
|||||||
# The script failing validation should be unavailable
|
# The script failing validation should be unavailable
|
||||||
assert hass.states.get("script.bad_script").state == STATE_UNAVAILABLE
|
assert hass.states.get("script.bad_script").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={
|
||||||
|
script.DOMAIN: {
|
||||||
|
object_id: {
|
||||||
|
"alias": "bad_script",
|
||||||
|
"sequence": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"entity_id": "hello.world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
script.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
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("running", ["no", "same", "different"])
|
@pytest.mark.parametrize("running", ["no", "same", "different"])
|
||||||
async def test_reload_service(hass: HomeAssistant, running) -> None:
|
async def test_reload_service(hass: HomeAssistant, running) -> None:
|
||||||
@ -1563,9 +1606,7 @@ async def test_script_service_changed_entity_id(
|
|||||||
assert calls[1].data["entity_id"] == "script.custom_entity_id_2"
|
assert calls[1].data["entity_id"] == "script.custom_entity_id_2"
|
||||||
|
|
||||||
|
|
||||||
async def test_blueprint_automation(
|
async def test_blueprint_script(hass: HomeAssistant, calls: list[ServiceCall]) -> None:
|
||||||
hass: HomeAssistant, calls: list[ServiceCall]
|
|
||||||
) -> None:
|
|
||||||
"""Test blueprint script."""
|
"""Test blueprint script."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
@ -1623,6 +1664,7 @@ async def test_blueprint_automation(
|
|||||||
)
|
)
|
||||||
async def test_blueprint_script_bad_config(
|
async def test_blueprint_script_bad_config(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
blueprint_inputs,
|
blueprint_inputs,
|
||||||
problem,
|
problem,
|
||||||
@ -1646,9 +1688,24 @@ async def test_blueprint_script_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"script.test_script_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/script/edit/test_script",
|
||||||
|
"entity_id": "script.test_script",
|
||||||
|
"error": ANY,
|
||||||
|
"name": "test_script",
|
||||||
|
}
|
||||||
|
assert issues[0]["translation_placeholders"]["error"].startswith(details)
|
||||||
|
|
||||||
|
|
||||||
async def test_blueprint_script_fails_substitution(
|
async def test_blueprint_script_fails_substitution(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test blueprint script with bad inputs."""
|
"""Test blueprint script with bad inputs."""
|
||||||
with patch(
|
with patch(
|
||||||
@ -1677,6 +1734,18 @@ async def test_blueprint_script_fails_substitution(
|
|||||||
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"script.test_script_{issue}"
|
||||||
|
assert issues[0]["translation_key"] == issue
|
||||||
|
assert issues[0]["translation_placeholders"] == {
|
||||||
|
"edit": "/config/script/edit/test_script",
|
||||||
|
"entity_id": "script.test_script",
|
||||||
|
"error": "No substitution found for input blah",
|
||||||
|
"name": "test_script",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("response", [{"value": 5}, '{"value": 5}'])
|
@pytest.mark.parametrize("response", [{"value": 5}, '{"value": 5}'])
|
||||||
async def test_responses(hass: HomeAssistant, response: Any) -> None:
|
async def test_responses(hass: HomeAssistant, response: Any) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user