Improve validation of device action config (#27029)
This commit is contained in:
parent
2090d650c6
commit
5a1da72d5e
3 changed files with 64 additions and 11 deletions
|
@ -7,10 +7,10 @@ import voluptuous as vol
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
from homeassistant.config import async_log_exception, config_without_domain
|
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, script
|
||||||
from homeassistant.loader import IntegrationNotFound
|
from homeassistant.loader import IntegrationNotFound
|
||||||
|
|
||||||
from . import CONF_TRIGGER, DOMAIN, PLATFORM_SCHEMA
|
from . import CONF_ACTION, CONF_TRIGGER, DOMAIN, PLATFORM_SCHEMA
|
||||||
|
|
||||||
# mypy: allow-untyped-calls, allow-untyped-defs
|
# mypy: allow-untyped-calls, allow-untyped-defs
|
||||||
# mypy: no-check-untyped-defs, no-warn-return-any
|
# mypy: no-check-untyped-defs, no-warn-return-any
|
||||||
|
@ -32,6 +32,12 @@ async def async_validate_config_item(hass, config, full_config=None):
|
||||||
)
|
)
|
||||||
triggers.append(trigger)
|
triggers.append(trigger)
|
||||||
config[CONF_TRIGGER] = triggers
|
config[CONF_TRIGGER] = triggers
|
||||||
|
|
||||||
|
actions = []
|
||||||
|
for action in config[CONF_ACTION]:
|
||||||
|
action = await script.async_validate_action_config(hass, action)
|
||||||
|
actions.append(action)
|
||||||
|
config[CONF_ACTION] = actions
|
||||||
except (vol.Invalid, HomeAssistantError, IntegrationNotFound) as ex:
|
except (vol.Invalid, HomeAssistantError, IntegrationNotFound) as ex:
|
||||||
async_log_exception(ex, DOMAIN, full_config or config, hass)
|
async_log_exception(ex, DOMAIN, full_config or config, hass)
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -8,13 +8,9 @@ from typing import Optional, Sequence, Callable, Dict, List, Set, Tuple, Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.components.device_automation as device_automation
|
||||||
from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
|
from homeassistant.core import HomeAssistant, Context, callback, CALLBACK_TYPE
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_CONDITION, CONF_DEVICE_ID, CONF_TIMEOUT
|
||||||
CONF_CONDITION,
|
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_DOMAIN,
|
|
||||||
CONF_TIMEOUT,
|
|
||||||
)
|
|
||||||
from homeassistant import exceptions
|
from homeassistant import exceptions
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
service,
|
service,
|
||||||
|
@ -27,7 +23,6 @@ from homeassistant.helpers.event import (
|
||||||
async_track_template,
|
async_track_template,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import async_get_integration
|
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
from homeassistant.util.async_ import run_callback_threadsafe
|
from homeassistant.util.async_ import run_callback_threadsafe
|
||||||
|
|
||||||
|
@ -86,6 +81,21 @@ def call_from_config(
|
||||||
Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables, context)
|
Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables, context)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_action_config(
|
||||||
|
hass: HomeAssistant, config: ConfigType
|
||||||
|
) -> ConfigType:
|
||||||
|
"""Validate config."""
|
||||||
|
action_type = _determine_action(config)
|
||||||
|
|
||||||
|
if action_type == ACTION_DEVICE_AUTOMATION:
|
||||||
|
platform = await device_automation.async_get_device_automation_platform(
|
||||||
|
hass, config, "action"
|
||||||
|
)
|
||||||
|
config = platform.ACTION_SCHEMA(config)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
class _StopScript(Exception):
|
class _StopScript(Exception):
|
||||||
"""Throw if script needs to stop."""
|
"""Throw if script needs to stop."""
|
||||||
|
|
||||||
|
@ -335,8 +345,9 @@ class Script:
|
||||||
"""
|
"""
|
||||||
self.last_action = action.get(CONF_ALIAS, "device automation")
|
self.last_action = action.get(CONF_ALIAS, "device automation")
|
||||||
self._log("Executing step %s" % self.last_action)
|
self._log("Executing step %s" % self.last_action)
|
||||||
integration = await async_get_integration(self.hass, action[CONF_DOMAIN])
|
platform = await device_automation.async_get_device_automation_platform(
|
||||||
platform = integration.get_platform("device_action")
|
self.hass, action, "action"
|
||||||
|
)
|
||||||
await platform.async_call_action_from_config(
|
await platform.async_call_action_from_config(
|
||||||
self.hass, action, variables, context
|
self.hass, action, variables, context
|
||||||
)
|
)
|
||||||
|
|
|
@ -185,6 +185,25 @@ async def test_automation_with_non_existing_integration(hass, caplog):
|
||||||
assert "Integration 'beer' not found" in caplog.text
|
assert "Integration 'beer' not found" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_automation_with_integration_without_device_action(hass, caplog):
|
||||||
|
"""Test automation with integration without device action support."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"alias": "hello",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event1"},
|
||||||
|
"action": {"device_id": "", "domain": "test"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Integration 'test' does not support device automation actions" in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_automation_with_integration_without_device_trigger(hass, caplog):
|
async def test_automation_with_integration_without_device_trigger(hass, caplog):
|
||||||
"""Test automation with integration without device trigger support."""
|
"""Test automation with integration without device trigger support."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -208,6 +227,23 @@ async def test_automation_with_integration_without_device_trigger(hass, caplog):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_automation_with_bad_action(hass, caplog):
|
||||||
|
"""Test automation with bad device action."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"alias": "hello",
|
||||||
|
"trigger": {"platform": "event", "event_type": "test_event1"},
|
||||||
|
"action": {"device_id": "", "domain": "light"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "required key not provided" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_automation_with_bad_trigger(hass, caplog):
|
async def test_automation_with_bad_trigger(hass, caplog):
|
||||||
"""Test automation with bad device trigger."""
|
"""Test automation with bad device trigger."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue