From 04f87eedf584dbb11f73f18771761b41c8e6395b Mon Sep 17 00:00:00 2001 From: On Freund Date: Thu, 1 Oct 2020 12:59:35 +0300 Subject: [PATCH] Add context to event trigger (#40932) --- .../homeassistant/triggers/event.py | 37 ++++++---- .../homeassistant/triggers/test_event.py | 73 ++++++++++++++++--- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 800d6bb5f77..4498702bac4 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -11,6 +11,7 @@ from homeassistant.helpers import config_validation as cv CONF_EVENT_TYPE = "event_type" CONF_EVENT_DATA = "event_data" +CONF_EVENT_CONTEXT = "context" _LOGGER = logging.getLogger(__name__) @@ -19,36 +20,42 @@ TRIGGER_SCHEMA = vol.Schema( vol.Required(CONF_PLATFORM): "event", vol.Required(CONF_EVENT_TYPE): cv.string, vol.Optional(CONF_EVENT_DATA): dict, + vol.Optional(CONF_EVENT_CONTEXT): dict, } ) +def _populate_schema(config, config_parameter): + if config_parameter not in config: + return None + + return vol.Schema( + {vol.Required(key): value for key, value in config[config_parameter].items()}, + extra=vol.ALLOW_EXTRA, + ) + + async def async_attach_trigger( hass, config, action, automation_info, *, platform_type="event" ): """Listen for events based on configuration.""" event_type = config.get(CONF_EVENT_TYPE) - event_data_schema = None - if config.get(CONF_EVENT_DATA): - event_data_schema = vol.Schema( - { - vol.Required(key): value - for key, value in config.get(CONF_EVENT_DATA).items() - }, - extra=vol.ALLOW_EXTRA, - ) + event_data_schema = _populate_schema(config, CONF_EVENT_DATA) + event_context_schema = _populate_schema(config, CONF_EVENT_CONTEXT) @callback def handle_event(event): """Listen for events and calls the action when data matches.""" - if event_data_schema: - # Check that the event data matches the configured + try: + # Check that the event data and context match the configured # schema if one was provided - try: + if event_data_schema: event_data_schema(event.data) - except vol.Invalid: - # If event data doesn't match requested schema, skip event - return + if event_context_schema: + event_context_schema(event.context.as_dict()) + except vol.Invalid: + # If event doesn't match, skip event + return hass.async_run_job( action, diff --git a/tests/components/homeassistant/triggers/test_event.py b/tests/components/homeassistant/triggers/test_event.py index 0e4c2674b1b..aa0acae254c 100644 --- a/tests/components/homeassistant/triggers/test_event.py +++ b/tests/components/homeassistant/triggers/test_event.py @@ -15,6 +15,12 @@ def calls(hass): return async_mock_service(hass, "test", "automation") +@pytest.fixture +def context_with_user(): + """Track calls to a mock service.""" + return Context(user_id="test_user_id") + + @pytest.fixture(autouse=True) def setup_comp(hass): """Initialize components.""" @@ -53,8 +59,8 @@ async def test_if_fires_on_event(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_event_extra_data(hass, calls): - """Test the firing of events still matches with event data.""" +async def test_if_fires_on_event_extra_data(hass, calls, context_with_user): + """Test the firing of events still matches with event data and context.""" assert await async_setup_component( hass, automation.DOMAIN, @@ -65,8 +71,9 @@ async def test_if_fires_on_event_extra_data(hass, calls): } }, ) - - hass.bus.async_fire("test_event", {"extra_key": "extra_data"}) + hass.bus.async_fire( + "test_event", {"extra_key": "extra_data"}, context=context_with_user + ) await hass.async_block_till_done() assert len(calls) == 1 @@ -82,8 +89,8 @@ async def test_if_fires_on_event_extra_data(hass, calls): assert len(calls) == 1 -async def test_if_fires_on_event_with_data(hass, calls): - """Test the firing of events with data.""" +async def test_if_fires_on_event_with_data_and_context(hass, calls, context_with_user): + """Test the firing of events with data and context.""" assert await async_setup_component( hass, automation.DOMAIN, @@ -96,6 +103,7 @@ async def test_if_fires_on_event_with_data(hass, calls): "some_attr": "some_value", "second_attr": "second_value", }, + "context": {"user_id": context_with_user.user_id}, }, "action": {"service": "test.automation"}, } @@ -105,17 +113,31 @@ async def test_if_fires_on_event_with_data(hass, calls): hass.bus.async_fire( "test_event", {"some_attr": "some_value", "another": "value", "second_attr": "second_value"}, + context=context_with_user, ) await hass.async_block_till_done() assert len(calls) == 1 - hass.bus.async_fire("test_event", {"some_attr": "some_value", "another": "value"}) + hass.bus.async_fire( + "test_event", + {"some_attr": "some_value", "another": "value"}, + context=context_with_user, + ) await hass.async_block_till_done() assert len(calls) == 1 # No new call + hass.bus.async_fire( + "test_event", + {"some_attr": "some_value", "another": "value", "second_attr": "second_value"}, + ) + await hass.async_block_till_done() + assert len(calls) == 1 -async def test_if_fires_on_event_with_empty_data_config(hass, calls): - """Test the firing of events with empty data config. + +async def test_if_fires_on_event_with_empty_data_and_context_config( + hass, calls, context_with_user +): + """Test the firing of events with empty data and context config. The frontend automation editor can produce configurations with an empty dict for event_data instead of no key. @@ -129,13 +151,18 @@ async def test_if_fires_on_event_with_empty_data_config(hass, calls): "platform": "event", "event_type": "test_event", "event_data": {}, + "context": {}, }, "action": {"service": "test.automation"}, } }, ) - hass.bus.async_fire("test_event", {"some_attr": "some_value", "another": "value"}) + hass.bus.async_fire( + "test_event", + {"some_attr": "some_value", "another": "value"}, + context=context_with_user, + ) await hass.async_block_till_done() assert len(calls) == 1 @@ -165,7 +192,7 @@ async def test_if_fires_on_event_with_nested_data(hass, calls): async def test_if_not_fires_if_event_data_not_matches(hass, calls): - """Test firing of event if no match.""" + """Test firing of event if no data match.""" assert await async_setup_component( hass, automation.DOMAIN, @@ -184,3 +211,27 @@ async def test_if_not_fires_if_event_data_not_matches(hass, calls): hass.bus.async_fire("test_event", {"some_attr": "some_other_value"}) await hass.async_block_till_done() assert len(calls) == 0 + + +async def test_if_not_fires_if_event_context_not_matches( + hass, calls, context_with_user +): + """Test firing of event if no context match.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "event", + "event_type": "test_event", + "context": {"user_id": "some_user"}, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.bus.async_fire("test_event", {}, context=context_with_user) + await hass.async_block_till_done() + assert len(calls) == 0