From 5f0cca9b264fcab86bfc929e824e84c8789d56ba Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 26 Aug 2022 02:56:26 +0200 Subject: [PATCH] Raise repairs issue if automation calls unknown service (#76949) --- .../components/automation/__init__.py | 19 +++++++++++++++++++ .../components/automation/manifest.json | 2 +- .../components/automation/strings.json | 13 +++++++++++++ .../automation/translations/en.json | 13 +++++++++++++ tests/components/automation/test_init.py | 13 ++++++++++++- tests/components/mobile_app/conftest.py | 1 + tests/components/mqtt/test_device_trigger.py | 1 + 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index e177b76faf3..b653d1649d6 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -9,6 +9,7 @@ import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant.components import blueprint +from homeassistant.components.repairs import IssueSeverity, async_create_issue from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, @@ -43,6 +44,7 @@ from homeassistant.exceptions import ( ConditionErrorContainer, ConditionErrorIndex, HomeAssistantError, + ServiceNotFound, TemplateError, ) from homeassistant.helpers import condition, extract_domain_configs @@ -525,6 +527,23 @@ class AutomationEntity(ToggleEntity, RestoreEntity): await self.action_script.async_run( variables, trigger_context, started_action ) + except ServiceNotFound as err: + async_create_issue( + self.hass, + DOMAIN, + f"{self.entity_id}_service_not_found_{err.domain}.{err.service}", + is_fixable=True, + is_persistent=True, + severity=IssueSeverity.ERROR, + translation_key="service_not_found", + translation_placeholders={ + "service": f"{err.domain}.{err.service}", + "entity_id": self.entity_id, + "name": self.name or self.entity_id, + "edit": f"/config/automation/edit/{self.unique_id}", + }, + ) + automation_trace.set_error(err) except (vol.Invalid, HomeAssistantError) as err: self._logger.error( "Error while executing automation %s: %s", diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index 9dd0130ee2f..6d1e4ee6027 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,7 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "dependencies": ["blueprint", "trace"], + "dependencies": ["blueprint", "repairs", "trace"], "after_dependencies": ["device_automation", "webhook"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/automation/strings.json b/homeassistant/components/automation/strings.json index adcc505b145..ea03868e639 100644 --- a/homeassistant/components/automation/strings.json +++ b/homeassistant/components/automation/strings.json @@ -5,5 +5,18 @@ "off": "[%key:common::state::off%]", "on": "[%key:common::state::on%]" } + }, + "issues": { + "service_not_found": { + "title": "{name} uses an unknown service", + "fix_flow": { + "step": { + "confirm": { + "title": "{name} uses an unknown service", + "description": "The automation \"{name}\" (`{entity_id}`) has an action that calls an unknown service: `{service}`.\n\nThis error prevents the automation from running correctly. Maybe this service is no longer available, or perhaps a typo caused it.\n\nTo fix this error, [edit the automation]({edit}) and remove the action that calls this service.\n\nClick on SUBMIT below to confirm you have fixed this automation." + } + } + } + } } } diff --git a/homeassistant/components/automation/translations/en.json b/homeassistant/components/automation/translations/en.json index e5dabcf3bce..e7f60221a01 100644 --- a/homeassistant/components/automation/translations/en.json +++ b/homeassistant/components/automation/translations/en.json @@ -1,4 +1,17 @@ { + "issues": { + "service_not_found": { + "fix_flow": { + "step": { + "confirm": { + "description": "The automation \"{name}\" (`{entity_id}`) has an action that calls an unknown service: `{service}`.\n\nThis error prevents the automation from running correctly. Maybe this service is no longer available, or perhaps a typo caused it.\n\nTo fix this error, [edit the automation]({edit}) and remove the action that calls this service.\n\nClick on SUBMIT below to confirm you have fixed this automation.", + "title": "{name} uses an unknown service" + } + } + }, + "title": "{name} uses an unknown service" + } + }, "state": { "_": { "off": "Off", diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index cef553653de..2c0e5bd5b6c 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,9 +1,11 @@ """The tests for the automation component.""" import asyncio +from collections.abc import Awaitable, Callable from datetime import timedelta import logging from unittest.mock import Mock, patch +from aiohttp import ClientWebSocketResponse import pytest import homeassistant.components.automation as automation @@ -53,6 +55,7 @@ from tests.common import ( mock_restore_cache, ) from tests.components.logbook.common import MockRow, mock_humanify +from tests.components.repairs import get_repairs @pytest.fixture @@ -983,7 +986,11 @@ async def test_automation_bad_trigger(hass, caplog): assert "Integration 'automation' does not provide trigger support." in caplog.text -async def test_automation_with_error_in_script(hass, caplog): +async def test_automation_with_error_in_script( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], +) -> None: """Test automation with an error in script.""" assert await async_setup_component( hass, @@ -1002,6 +1009,10 @@ async def test_automation_with_error_in_script(hass, caplog): assert "Service not found" in caplog.text assert "Traceback" not in caplog.text + issues = await get_repairs(hass, hass_ws_client) + assert len(issues) == 1 + assert issues[0]["issue_id"] == "automation.hello_service_not_found_test.automation" + async def test_automation_with_error_in_script_2(hass, caplog): """Test automation with an error in script.""" diff --git a/tests/components/mobile_app/conftest.py b/tests/components/mobile_app/conftest.py index 18f425d13c0..0455d4a8ea4 100644 --- a/tests/components/mobile_app/conftest.py +++ b/tests/components/mobile_app/conftest.py @@ -75,5 +75,6 @@ async def authed_api_client(hass, hass_client): @pytest.fixture(autouse=True) async def setup_ws(hass): """Configure the websocket_api component.""" + assert await async_setup_component(hass, "repairs", {}) assert await async_setup_component(hass, "websocket_api", {}) await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index b49276b8f85..8363ca34fd7 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -696,6 +696,7 @@ async def test_not_fires_on_mqtt_message_after_remove_from_registry( ): """Test triggers not firing after removal.""" assert await async_setup_component(hass, "config", {}) + assert await async_setup_component(hass, "repairs", {}) await hass.async_block_till_done() await mqtt_mock_entry_no_yaml_config()