From bbb82ded689c30ad5948ca4e65e93b244e9b0507 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 20 Nov 2020 15:43:28 +0100 Subject: [PATCH] Fix time trigger based on entities ignoring entities if initially in the past (#43431) --- .../components/homeassistant/triggers/time.py | 7 +- .../homeassistant/triggers/test_time.py | 92 +++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index b636a7a3590..b0fced4d55d 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -143,9 +143,12 @@ async def async_attach_trigger(hass, config, action, automation_info): if remove: entities[entity_id] = remove + to_track = [] + for at_time in config[CONF_AT]: if isinstance(at_time, str): # entity + to_track.append(at_time) update_entity_trigger(at_time, new_state=hass.states.get(at_time)) else: # datetime.time @@ -161,9 +164,7 @@ async def async_attach_trigger(hass, config, action, automation_info): # Track state changes of any entities. removes.append( - async_track_state_change_event( - hass, list(entities), update_entity_trigger_event - ) + async_track_state_change_event(hass, to_track, update_entity_trigger_event) ) @callback diff --git a/tests/components/homeassistant/triggers/test_time.py b/tests/components/homeassistant/triggers/test_time.py index 673d0231912..a37be71102d 100644 --- a/tests/components/homeassistant/triggers/test_time.py +++ b/tests/components/homeassistant/triggers/test_time.py @@ -2,8 +2,10 @@ from datetime import timedelta import pytest +import voluptuous as vol from homeassistant.components import automation, sensor +from homeassistant.components.homeassistant.triggers import time from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, SERVICE_TURN_OFF from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -492,3 +494,93 @@ async def test_if_fires_using_at_sensor(hass, calls): # We should not have listened to anything assert len(calls) == 2 + + +@pytest.mark.parametrize( + "conf", + [ + {"platform": "time", "at": "input_datetime.bla"}, + {"platform": "time", "at": "sensor.bla"}, + {"platform": "time", "at": "12:34"}, + ], +) +def test_schema_valid(conf): + """Make sure we don't accept number for 'at' value.""" + time.TRIGGER_SCHEMA(conf) + + +@pytest.mark.parametrize( + "conf", + [ + {"platform": "time", "at": "binary_sensor.bla"}, + {"platform": "time", "at": 745}, + {"platform": "time", "at": "25:00"}, + ], +) +def test_schema_invalid(conf): + """Make sure we don't accept number for 'at' value.""" + with pytest.raises(vol.Invalid): + time.TRIGGER_SCHEMA(conf) + + +async def test_datetime_in_past_on_load(hass, calls): + """Test time trigger works if input_datetime is in past.""" + await async_setup_component( + hass, + "input_datetime", + {"input_datetime": {"my_trigger": {"has_date": True, "has_time": True}}}, + ) + + now = dt_util.now() + past = now - timedelta(days=2) + future = now + timedelta(days=1) + + await hass.services.async_call( + "input_datetime", + "set_datetime", + { + ATTR_ENTITY_ID: "input_datetime.my_trigger", + "datetime": str(past.replace(tzinfo=None)), + }, + blocking=True, + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": {"platform": "time", "at": "input_datetime.my_trigger"}, + "action": { + "service": "test.automation", + "data_template": { + "some": "{{ trigger.platform }}-{{ trigger.now.day }}-{{ trigger.now.hour }}-{{trigger.entity_id}}" + }, + }, + } + }, + ) + + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + assert len(calls) == 0 + + await hass.services.async_call( + "input_datetime", + "set_datetime", + { + ATTR_ENTITY_ID: "input_datetime.my_trigger", + "datetime": str(future.replace(tzinfo=None)), + }, + blocking=True, + ) + + async_fire_time_changed(hass, future + timedelta(seconds=1)) + await hass.async_block_till_done() + + assert len(calls) == 1 + assert ( + calls[0].data["some"] + == f"time-{future.day}-{future.hour}-input_datetime.my_trigger" + )