mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Add extended validation for script repeat/choose (#41265)
This commit is contained in:
parent
0a3db42d61
commit
8ae3f575dd
@ -10,7 +10,7 @@ from homeassistant.config import async_log_exception, config_without_domain
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_per_platform
|
from homeassistant.helpers import config_per_platform
|
||||||
from homeassistant.helpers.condition import async_validate_condition_config
|
from homeassistant.helpers.condition import async_validate_condition_config
|
||||||
from homeassistant.helpers.script import async_validate_action_config
|
from homeassistant.helpers.script import async_validate_actions_config
|
||||||
from homeassistant.helpers.trigger import async_validate_trigger_config
|
from homeassistant.helpers.trigger import async_validate_trigger_config
|
||||||
from homeassistant.loader import IntegrationNotFound
|
from homeassistant.loader import IntegrationNotFound
|
||||||
|
|
||||||
@ -36,9 +36,7 @@ async def async_validate_config_item(hass, config, full_config=None):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
config[CONF_ACTION] = await asyncio.gather(
|
config[CONF_ACTION] = await async_validate_actions_config(hass, config[CONF_ACTION])
|
||||||
*[async_validate_action_config(hass, action) for action in config[CONF_ACTION]]
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -123,30 +123,71 @@ def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
STATIC_VALIDATION_ACTION_TYPES = (
|
||||||
|
cv.SCRIPT_ACTION_CALL_SERVICE,
|
||||||
|
cv.SCRIPT_ACTION_DELAY,
|
||||||
|
cv.SCRIPT_ACTION_WAIT_TEMPLATE,
|
||||||
|
cv.SCRIPT_ACTION_FIRE_EVENT,
|
||||||
|
cv.SCRIPT_ACTION_ACTIVATE_SCENE,
|
||||||
|
cv.SCRIPT_ACTION_VARIABLES,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_actions_config(
|
||||||
|
hass: HomeAssistant, actions: List[ConfigType]
|
||||||
|
) -> List[ConfigType]:
|
||||||
|
"""Validate a list of actions."""
|
||||||
|
return await asyncio.gather(
|
||||||
|
*[async_validate_action_config(hass, action) for action in actions]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_validate_action_config(
|
async def async_validate_action_config(
|
||||||
hass: HomeAssistant, config: ConfigType
|
hass: HomeAssistant, config: ConfigType
|
||||||
) -> ConfigType:
|
) -> ConfigType:
|
||||||
"""Validate config."""
|
"""Validate config."""
|
||||||
action_type = cv.determine_script_action(config)
|
action_type = cv.determine_script_action(config)
|
||||||
|
|
||||||
if action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION:
|
if action_type in STATIC_VALIDATION_ACTION_TYPES:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION:
|
||||||
platform = await device_automation.async_get_device_automation_platform(
|
platform = await device_automation.async_get_device_automation_platform(
|
||||||
hass, config[CONF_DOMAIN], "action"
|
hass, config[CONF_DOMAIN], "action"
|
||||||
)
|
)
|
||||||
config = platform.ACTION_SCHEMA(config) # type: ignore
|
config = platform.ACTION_SCHEMA(config) # type: ignore
|
||||||
elif (
|
|
||||||
action_type == cv.SCRIPT_ACTION_CHECK_CONDITION
|
elif action_type == cv.SCRIPT_ACTION_CHECK_CONDITION:
|
||||||
and config[CONF_CONDITION] == "device"
|
if config[CONF_CONDITION] == "device":
|
||||||
):
|
|
||||||
platform = await device_automation.async_get_device_automation_platform(
|
platform = await device_automation.async_get_device_automation_platform(
|
||||||
hass, config[CONF_DOMAIN], "condition"
|
hass, config[CONF_DOMAIN], "condition"
|
||||||
)
|
)
|
||||||
config = platform.CONDITION_SCHEMA(config) # type: ignore
|
config = platform.CONDITION_SCHEMA(config) # type: ignore
|
||||||
|
|
||||||
elif action_type == cv.SCRIPT_ACTION_WAIT_FOR_TRIGGER:
|
elif action_type == cv.SCRIPT_ACTION_WAIT_FOR_TRIGGER:
|
||||||
config[CONF_WAIT_FOR_TRIGGER] = await async_validate_trigger_config(
|
config[CONF_WAIT_FOR_TRIGGER] = await async_validate_trigger_config(
|
||||||
hass, config[CONF_WAIT_FOR_TRIGGER]
|
hass, config[CONF_WAIT_FOR_TRIGGER]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif action_type == cv.SCRIPT_ACTION_REPEAT:
|
||||||
|
config[CONF_SEQUENCE] = await async_validate_actions_config(
|
||||||
|
hass, config[CONF_REPEAT][CONF_SEQUENCE]
|
||||||
|
)
|
||||||
|
|
||||||
|
elif action_type == cv.SCRIPT_ACTION_CHOOSE:
|
||||||
|
if CONF_DEFAULT in config:
|
||||||
|
config[CONF_DEFAULT] = await async_validate_actions_config(
|
||||||
|
hass, config[CONF_DEFAULT]
|
||||||
|
)
|
||||||
|
|
||||||
|
for choose_conf in config[CONF_CHOOSE]:
|
||||||
|
choose_conf[CONF_SEQUENCE] = await async_validate_actions_config(
|
||||||
|
hass, choose_conf[CONF_SEQUENCE]
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"No validation for {action_type}")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import homeassistant.components.scene as scene
|
|||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
|
||||||
from homeassistant.core import Context, CoreState, callback
|
from homeassistant.core import Context, CoreState, callback
|
||||||
from homeassistant.helpers import config_validation as cv, script
|
from homeassistant.helpers import config_validation as cv, script
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
@ -1828,3 +1829,114 @@ async def test_set_redefines_variable(hass, caplog):
|
|||||||
|
|
||||||
assert mock_calls[0].data["value"] == "1"
|
assert mock_calls[0].data["value"] == "1"
|
||||||
assert mock_calls[1].data["value"] == "2"
|
assert mock_calls[1].data["value"] == "2"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_validate_action_config(hass):
|
||||||
|
"""Validate action config."""
|
||||||
|
configs = {
|
||||||
|
cv.SCRIPT_ACTION_CALL_SERVICE: {"service": "light.turn_on"},
|
||||||
|
cv.SCRIPT_ACTION_DELAY: {"delay": 5},
|
||||||
|
cv.SCRIPT_ACTION_WAIT_TEMPLATE: {
|
||||||
|
"wait_template": "{{ states.light.kitchen.state == 'on' }}"
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_FIRE_EVENT: {"event": "my_event"},
|
||||||
|
cv.SCRIPT_ACTION_CHECK_CONDITION: {
|
||||||
|
"condition": "{{ states.light.kitchen.state == 'on' }}"
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_DEVICE_AUTOMATION: {
|
||||||
|
"domain": "light",
|
||||||
|
"entity_id": "light.kitchen",
|
||||||
|
"device_id": "abcd",
|
||||||
|
"type": "turn_on",
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_ACTIVATE_SCENE: {"scene": "scene.relax"},
|
||||||
|
cv.SCRIPT_ACTION_REPEAT: {
|
||||||
|
"repeat": {"count": 3, "sequence": [{"event": "repeat_event"}]}
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_CHOOSE: {
|
||||||
|
"choose": [
|
||||||
|
{
|
||||||
|
"condition": "{{ states.light.kitchen.state == 'on' }}",
|
||||||
|
"sequence": [{"event": "choose_event"}],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": [{"event": "choose_default_event"}],
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_WAIT_FOR_TRIGGER: {
|
||||||
|
"wait_for_trigger": [
|
||||||
|
{"platform": "event", "event_type": "wait_for_trigger_event"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
cv.SCRIPT_ACTION_VARIABLES: {"variables": {"hello": "world"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in cv.ACTION_TYPE_SCHEMAS:
|
||||||
|
assert key in configs, f"No validate config test found for {key}"
|
||||||
|
|
||||||
|
# Verify we raise if we don't know the action type
|
||||||
|
with patch(
|
||||||
|
"homeassistant.helpers.config_validation.determine_script_action",
|
||||||
|
return_value="non-existing",
|
||||||
|
), pytest.raises(ValueError):
|
||||||
|
await script.async_validate_action_config(hass, {})
|
||||||
|
|
||||||
|
for action_type, config in configs.items():
|
||||||
|
assert cv.determine_script_action(config) == action_type
|
||||||
|
try:
|
||||||
|
await script.async_validate_action_config(hass, config)
|
||||||
|
except vol.Invalid as err:
|
||||||
|
assert False, f"{action_type} config invalid: {err}"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_embedded_wait_for_trigger_in_automation(hass):
|
||||||
|
"""Test an embedded wait for trigger."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"automation",
|
||||||
|
{
|
||||||
|
"automation": {
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||||
|
"action": {
|
||||||
|
"repeat": {
|
||||||
|
"while": [
|
||||||
|
{
|
||||||
|
"condition": "template",
|
||||||
|
"value_template": '{{ is_state("test.value1", "trigger-while") }}',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sequence": [
|
||||||
|
{"event": "trigger_wait_event"},
|
||||||
|
{
|
||||||
|
"wait_for_trigger": [
|
||||||
|
{
|
||||||
|
"platform": "template",
|
||||||
|
"value_template": '{{ is_state("test.value2", "trigger-wait") }}',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"service": "test.script"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.states.async_set("test.value1", "trigger-while")
|
||||||
|
hass.states.async_set("test.value2", "not-trigger-wait")
|
||||||
|
mock_calls = async_mock_service(hass, "test", "script")
|
||||||
|
|
||||||
|
async def trigger_wait_event(_):
|
||||||
|
# give script the time to attach the trigger.
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
hass.states.async_set("test.value1", "not-trigger-while")
|
||||||
|
hass.states.async_set("test.value2", "trigger-wait")
|
||||||
|
|
||||||
|
hass.bus.async_listen("trigger_wait_event", trigger_wait_event)
|
||||||
|
|
||||||
|
# Start automation
|
||||||
|
hass.bus.async_fire("test_event")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_calls) == 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user