From 7127310f10db32876ecb8c4a31314b562087ee82 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 2 Feb 2020 07:13:07 -0800 Subject: [PATCH] Catch device not found in device automations (#31401) --- .../components/device_automation/__init__.py | 28 ++++++++++++++++++- .../device_automation/exceptions.py | 4 +++ .../components/device_automation/test_init.py | 14 ++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 56e087f0e5f..95b3fc9fdb3 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -1,5 +1,6 @@ """Helpers for device automations.""" import asyncio +from functools import wraps import logging from types import ModuleType from typing import Any, List, MutableMapping @@ -14,7 +15,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_registry import async_entries_for_device from homeassistant.loader import IntegrationNotFound, async_get_integration -from .exceptions import InvalidDeviceAutomationConfig +from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig # mypy: allow-untyped-calls, allow-untyped-defs @@ -117,6 +118,10 @@ async def _async_get_device_automations(hass, automation_type, device_id): domains = set() automations: List[MutableMapping[str, Any]] = [] device = device_registry.async_get(device_id) + + if device is None: + raise DeviceNotFound + for entry_id in device.config_entries: config_entry = hass.config_entries.async_get_entry(entry_id) domains.add(config_entry.domain) @@ -173,6 +178,21 @@ async def _async_get_device_automation_capabilities(hass, automation_type, autom return capabilities +def handle_device_errors(func): + """Handle device automation errors.""" + + @wraps(func) + async def with_error_handling(hass, connection, msg): + try: + await func(hass, connection, msg) + except DeviceNotFound: + connection.send_error( + msg["id"], websocket_api.const.ERR_NOT_FOUND, "Device not found" + ) + + return with_error_handling + + @websocket_api.websocket_command( { vol.Required("type"): "device_automation/action/list", @@ -180,6 +200,7 @@ async def _async_get_device_automation_capabilities(hass, automation_type, autom } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_list_actions(hass, connection, msg): """Handle request for device actions.""" device_id = msg["device_id"] @@ -194,6 +215,7 @@ async def websocket_device_automation_list_actions(hass, connection, msg): } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_list_conditions(hass, connection, msg): """Handle request for device conditions.""" device_id = msg["device_id"] @@ -208,6 +230,7 @@ async def websocket_device_automation_list_conditions(hass, connection, msg): } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_list_triggers(hass, connection, msg): """Handle request for device triggers.""" device_id = msg["device_id"] @@ -222,6 +245,7 @@ async def websocket_device_automation_list_triggers(hass, connection, msg): } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_get_action_capabilities(hass, connection, msg): """Handle request for device action capabilities.""" action = msg["action"] @@ -238,6 +262,7 @@ async def websocket_device_automation_get_action_capabilities(hass, connection, } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_get_condition_capabilities(hass, connection, msg): """Handle request for device condition capabilities.""" condition = msg["condition"] @@ -254,6 +279,7 @@ async def websocket_device_automation_get_condition_capabilities(hass, connectio } ) @websocket_api.async_response +@handle_device_errors async def websocket_device_automation_get_trigger_capabilities(hass, connection, msg): """Handle request for device trigger capabilities.""" trigger = msg["trigger"] diff --git a/homeassistant/components/device_automation/exceptions.py b/homeassistant/components/device_automation/exceptions.py index 2f7c0df0187..ad92696cb94 100644 --- a/homeassistant/components/device_automation/exceptions.py +++ b/homeassistant/components/device_automation/exceptions.py @@ -4,3 +4,7 @@ from homeassistant.exceptions import HomeAssistantError class InvalidDeviceAutomationConfig(HomeAssistantError): """When device automation config is invalid.""" + + +class DeviceNotFound(HomeAssistantError): + """When referenced device not found.""" diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 651d989d105..48426e2640e 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -761,3 +761,17 @@ async def test_automation_with_bad_trigger(hass, caplog): ) assert "required key not provided" in caplog.text + + +async def test_websocket_device_not_found(hass, hass_ws_client): + """Test caling command with unknown device.""" + await async_setup_component(hass, "device_automation", {}) + client = await hass_ws_client(hass) + await client.send_json( + {"id": 1, "type": "device_automation/action/list", "device_id": "non-existing"} + ) + msg = await client.receive_json() + + assert msg["id"] == 1 + assert not msg["success"] + assert msg["error"] == {"code": "not_found", "message": "Device not found"}