diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index c733a96ca9d..ea00de33390 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -704,10 +704,12 @@ async def handle_execute_script( """Handle execute script command.""" # Circular dep # pylint: disable-next=import-outside-toplevel - from homeassistant.helpers.script import Script + from homeassistant.helpers.script import Script, async_validate_actions_config + + script_config = await async_validate_actions_config(hass, msg["sequence"]) context = connection.context(msg) - script_obj = Script(hass, msg["sequence"], f"{const.DOMAIN} script", const.DOMAIN) + script_obj = Script(hass, script_config, f"{const.DOMAIN} script", const.DOMAIN) response = await script_obj.async_run(msg.get("variables"), context=context) connection.send_result( msg["id"], diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 00d27035464..232362ce96f 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1,12 +1,14 @@ """Tests for WebSocket API commands.""" from copy import deepcopy import datetime -from unittest.mock import ANY, patch +from unittest.mock import ANY, AsyncMock, Mock, patch from async_timeout import timeout import pytest import voluptuous as vol +from homeassistant import config_entries, loader +from homeassistant.components.device_automation import toggle_entity from homeassistant.components.websocket_api import const from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, @@ -17,13 +19,20 @@ from homeassistant.components.websocket_api.const import FEATURE_COALESCE_MESSAG from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS from homeassistant.core import Context, HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import entity +from homeassistant.helpers import device_registry as dr, entity from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.loader import async_get_integration from homeassistant.setup import DATA_SETUP_TIME, async_setup_component from homeassistant.util.json import json_loads -from tests.common import MockEntity, MockEntityPlatform, MockUser, async_mock_service +from tests.common import ( + MockConfigEntry, + MockEntity, + MockEntityPlatform, + MockUser, + async_mock_service, + mock_platform, +) from tests.typing import ( ClientSessionGenerator, WebSocketGenerator, @@ -40,6 +49,25 @@ STATE_KEY_SHORT_NAMES = { STATE_KEY_LONG_NAMES = {v: k for k, v in STATE_KEY_SHORT_NAMES.items()} +@pytest.fixture +def fake_integration(hass: HomeAssistant): + """Set up a mock integration with device automation support.""" + DOMAIN = "fake_integration" + + hass.config.components.add(DOMAIN) + + mock_platform( + hass, + f"{DOMAIN}.device_action", + Mock( + ACTION_SCHEMA=toggle_entity.ACTION_SCHEMA.extend( + {vol.Required("domain"): DOMAIN} + ), + spec=["ACTION_SCHEMA"], + ), + ) + + def _apply_entities_changes(state_dict: dict, change_dict: dict) -> None: """Apply a diff set to a dict. @@ -1775,6 +1803,52 @@ async def test_execute_script_complex_response( } +async def test_execute_script_with_dynamically_validated_action( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + device_registry: dr.DeviceRegistry, + fake_integration, +) -> None: + """Test executing a script with an action which is dynamically validated.""" + + ws_client = await hass_ws_client(hass) + + module_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {}) + module = module_cache["fake_integration.device_action"] + module.async_call_action_from_config = AsyncMock() + module.async_validate_action_config = AsyncMock( + side_effect=lambda hass, config: config + ) + + config_entry = MockConfigEntry(domain="fake_integration", data={}) + config_entry.state = config_entries.ConfigEntryState.LOADED + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + + await ws_client.send_json_auto_id( + { + "type": "execute_script", + "sequence": [ + { + "device_id": device_entry.id, + "domain": "fake_integration", + }, + ], + } + ) + + msg_no_var = await ws_client.receive_json() + assert msg_no_var["type"] == const.TYPE_RESULT + assert msg_no_var["success"] + assert msg_no_var["result"]["response"] is None + + module.async_validate_action_config.assert_awaited_once() + module.async_call_action_from_config.assert_awaited_once() + + async def test_subscribe_unsubscribe_bootstrap_integrations( hass: HomeAssistant, websocket_client, hass_admin_user: MockUser ) -> None: