From 63545ceaa46140e487a6c1b4da4f3d1c740e3857 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 10 Apr 2024 02:42:18 -1000 Subject: [PATCH] Ensure automations do not execute from a trigger if they are disabled (#115305) * Ensure automations are stopped as soon as the stop future is set * revert script changes and move them to #115325 --- .../components/automation/__init__.py | 18 ++++- tests/components/automation/test_init.py | 80 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 785d5849d74..299be2c82f9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -795,6 +795,22 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): """Log helper callback.""" self._logger.log(level, "%s %s", msg, self.name, **kwargs) + async def _async_trigger_if_enabled( + self, + run_variables: dict[str, Any], + context: Context | None = None, + skip_condition: bool = False, + ) -> ScriptRunResult | None: + """Trigger automation if enabled. + + If the trigger starts but has a delay, the automation will be triggered + when the delay has passed so we need to make sure its still enabled before + executing the action. + """ + if not self._is_enabled: + return None + return await self.async_trigger(run_variables, context, skip_condition) + async def _async_attach_triggers( self, home_assistant_start: bool ) -> Callable[[], None] | None: @@ -818,7 +834,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): return await async_initialize_triggers( self.hass, self._trigger_config, - self.async_trigger, + self._async_trigger_if_enabled, DOMAIN, str(self.name), self._log_callback, diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 7805f3ea151..5b3fc2a723e 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -2651,3 +2651,83 @@ def test_deprecated_constants( import_and_test_deprecated_constant( caplog, automation, constant_name, replacement.__name__, replacement, "2025.1" ) + + +async def test_automation_turns_off_other_automation( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test an automation that turns off another automation.""" + hass.set_state(CoreState.not_running) + calls = async_mock_service(hass, "persistent_notification", "create") + hass.states.async_set("binary_sensor.presence", "on") + await hass.async_block_till_done() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "state", + "entity_id": "binary_sensor.presence", + "from": "on", + }, + "action": { + "service": "automation.turn_off", + "target": { + "entity_id": "automation.automation_1", + }, + "data": { + "stop_actions": True, + }, + }, + "id": "automation_0", + "mode": "single", + }, + { + "trigger": { + "platform": "state", + "entity_id": "binary_sensor.presence", + "from": "on", + "for": { + "hours": 0, + "minutes": 0, + "seconds": 5, + }, + }, + "action": { + "service": "persistent_notification.create", + "metadata": {}, + "data": { + "message": "Test race", + }, + }, + "id": "automation_1", + "mode": "single", + }, + ] + }, + ) + await hass.async_start() + await hass.async_block_till_done() + + hass.states.async_set("binary_sensor.presence", "off") + await hass.async_block_till_done() + assert len(calls) == 0 + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) + await hass.async_block_till_done() + assert len(calls) == 0 + + await hass.services.async_call( + "automation", + "turn_on", + {"entity_id": "automation.automation_1"}, + blocking=True, + ) + hass.states.async_set("binary_sensor.presence", "off") + await hass.async_block_till_done() + assert len(calls) == 0 + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) + await hass.async_block_till_done() + assert len(calls) == 0