def test_passing_variables(self): """Test different ways of passing in variables.""" calls = [] context = Context() @callback def record_call(service): """Add recorded event to set.""" calls.append(service) self.hass.services.register('test', 'script', record_call) assert setup_component( self.hass, 'script', { 'script': { 'test': { 'sequence': { 'service': 'test.script', 'data_template': { 'hello': '{{ greeting }}', }, }, }, }, }) turn_on(self.hass, ENTITY_ID, {'greeting': 'world'}, context=context) self.hass.block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data['hello'] == 'world' self.hass.services.call('script', 'test', { 'greeting': 'universe', }, context=context) self.hass.block_till_done() assert len(calls) == 2 assert calls[1].context is context assert calls[1].data['hello'] == 'universe'
async def test_wait_template(hass): """Test the wait template.""" event = "test_event" events = [] context = Context() wait_alias = "wait step" @callback def record_event(event): """Add recorded event to set.""" events.append(event) hass.bus.async_listen(event, record_event) hass.states.async_set("switch.test", "on") script_obj = script.Script( hass, cv.SCRIPT_SCHEMA( [ {"event": event}, { "wait_template": "{{states.switch.test.state == 'off'}}", "alias": wait_alias, }, {"event": event}, ] ), ) await script_obj.async_run(context=context) await hass.async_block_till_done() assert script_obj.is_running assert script_obj.can_cancel assert script_obj.last_action == wait_alias assert len(events) == 1 hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert not script_obj.is_running assert len(events) == 2 assert events[0].context is context assert events[1].context is context
async def async_trigger(self, run_variables, context=None, skip_condition=False): """Trigger automation. This method is a coroutine. """ if self._variables: try: variables = self._variables.async_render(self.hass, run_variables) except template.TemplateError as err: self._logger.error("Error rendering variables: %s", err) return else: variables = run_variables if ( not skip_condition and self._cond_func is not None and not self._cond_func(variables) ): return # Create a new context referring to the old context. parent_id = None if context is None else context.id trigger_context = Context(parent_id=parent_id) self.async_set_context(trigger_context) event_data = { ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id, } if "trigger" in variables and "description" in variables["trigger"]: event_data[ATTR_SOURCE] = variables["trigger"]["description"] @callback def started_action(): self.hass.bus.async_fire( EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context ) try: await self.action_script.async_run( variables, trigger_context, started_action ) except Exception: # pylint: disable=broad-except self._logger.exception("While executing automation %s", self.entity_id)
async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): """Setup cloud component and test services.""" cloud = hass.data[DOMAIN] assert hass.services.has_service(DOMAIN, "remote_connect") assert hass.services.has_service(DOMAIN, "remote_disconnect") with patch("hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro()) as mock_connect: await hass.services.async_call(DOMAIN, "remote_connect", blocking=True) assert mock_connect.called assert cloud.client.remote_autostart with patch("hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro()) as mock_disconnect: await hass.services.async_call(DOMAIN, "remote_disconnect", blocking=True) assert mock_disconnect.called assert not cloud.client.remote_autostart # Test admin access required non_admin_context = Context(user_id=hass_read_only_user.id) with patch("hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro()) as mock_connect, pytest.raises( Unauthorized): await hass.services.async_call(DOMAIN, "remote_connect", blocking=True, context=non_admin_context) assert mock_connect.called is False with patch("hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro()) as mock_disconnect, pytest.raises( Unauthorized): await hass.services.async_call(DOMAIN, "remote_disconnect", blocking=True, context=non_admin_context) assert mock_disconnect.called is False
async def test_trigger_entity(hass, start_ha): """Test trigger entity works.""" await hass.async_block_till_done() state = hass.states.get("binary_sensor.hello_name") assert state is not None assert state.state == STATE_UNKNOWN state = hass.states.get("binary_sensor.bare_minimum") assert state is not None assert state.state == STATE_UNKNOWN context = Context() hass.bus.async_fire("test_event", {"beer": 2}, context=context) await hass.async_block_till_done() state = hass.states.get("binary_sensor.hello_name") assert state.state == ON assert state.attributes.get("device_class") == "battery" assert state.attributes.get("icon") == "mdi:pirate" assert state.attributes.get("entity_picture") == "/local/dogs.png" assert state.attributes.get("plus_one") == 3 assert state.context is context ent_reg = entity_registry.async_get(hass) assert len(ent_reg.entities) == 2 assert (ent_reg.entities["binary_sensor.hello_name"].unique_id == "listening-test-event-hello_name-id") assert (ent_reg.entities["binary_sensor.via_list"].unique_id == "listening-test-event-via_list-id") state = hass.states.get("binary_sensor.via_list") assert state.state == ON assert state.attributes.get("device_class") == "battery" assert state.attributes.get("icon") == "mdi:pirate" assert state.attributes.get("entity_picture") == "/local/dogs.png" assert state.attributes.get("plus_one") == 3 assert state.attributes.get("another") == 1 assert state.context is context # Even if state itself didn't change, attributes might have changed hass.bus.async_fire("test_event", {"beer": 2, "uno_mas": "si"}) await hass.async_block_till_done() state = hass.states.get("binary_sensor.via_list") assert state.state == ON assert state.attributes.get("another") == "si"
async def test_if_fires_on_entity_change(hass, calls): """Test for firing on entity change.""" context = Context() hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() assert await async_setup_component( hass, automation.DOMAIN, { automation.DOMAIN: { "trigger": { "platform": "state", "entity_id": "test.entity" }, "action": { "service": "test.automation", "data_template": { "some": "{{ trigger.%s }}" % "}} - {{ trigger.".join(( "platform", "entity_id", "from_state.state", "to_state.state", "for", )) }, }, } }, ) await hass.async_block_till_done() hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() assert 1 == len(calls) assert calls[0].context.parent_id == context.id assert "state - test.entity - hello - world - None" == calls[0].data[ "some"] await common.async_turn_off(hass) await hass.async_block_till_done() hass.states.async_set("test.entity", "planet") await hass.async_block_till_done() assert 1 == len(calls)
async def test_counter_context(hass): """Test that counter context works.""" assert await async_setup_component(hass, 'counter', {'counter': { 'test': {} }}) state = hass.states.get('counter.test') assert state is not None await hass.services.async_call('counter', 'increment', { 'entity_id': state.entity_id, }, True, Context(user_id='abcd')) state2 = hass.states.get('counter.test') assert state2 is not None assert state.state != state2.state assert state2.context.user_id == 'abcd'
def async_call_service(self, domain, service, service_data, value=None): """Fire event and call service for changes from HomeKit.""" event_data = { ATTR_ENTITY_ID: self.entity_id, ATTR_DISPLAY_NAME: self.display_name, ATTR_SERVICE: service, ATTR_VALUE: value, } context = Context() self.hass.bus.async_fire(EVENT_HOMEKIT_CHANGED, event_data, context=context) self.hass.async_create_task( self.hass.services.async_call(domain, service, service_data, context=context))
def to_native(self): """Convert to an HA state object.""" context = Context(id=self.context_id, user_id=self.context_user_id) try: return State( self.entity_id, self.state, json.loads(self.attributes), _process_timestamp(self.last_changed), _process_timestamp(self.last_updated), context=context, # Temp, because database can still store invalid entity IDs # Remove with 1.0 or in 2020. temp_invalid_id_bypass=True) except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to state: %s", self) return None
def to_native(self, validate_entity_id=True): """Convert to an HA state object.""" try: return State( self.entity_id, self.state, json.loads(self.attributes), process_timestamp(self.last_changed), process_timestamp(self.last_updated), # Join the events table on event_id to get the context instead # as it will always be there for state_changed events context=Context(id=None), validate_entity_id=validate_entity_id, ) except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to state: %s", self) return None
def test_passing_variables(self): """Test different ways of passing in variables.""" calls = [] context = Context() @callback def record_call(service): """Add recorded event to set.""" calls.append(service) self.hass.services.register("test", "script", record_call) assert setup_component( self.hass, "script", { "script": { "test": { "sequence": { "service": "test.script", "data_template": {"hello": "{{ greeting }}"}, } } } }, ) turn_on(self.hass, ENTITY_ID, {"greeting": "world"}, context=context) self.hass.block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data["hello"] == "world" self.hass.services.call( "script", "test", {"greeting": "universe"}, context=context ) self.hass.block_till_done() assert len(calls) == 2 assert calls[1].context is context assert calls[1].data["hello"] == "universe"
async def test_reproduce_group(hass): """Test reproduce_state with group.""" context = Context() def clone_state(state, entity_id): """Return a cloned state with different entity_id.""" return State( entity_id, state.state, state.attributes, last_changed=state.last_changed, last_updated=state.last_updated, context=state.context, ) with patch( "homeassistant.components.group.reproduce_state.async_reproduce_state" ) as fun: fun.return_value = Future() fun.return_value.set_result(None) hass.states.async_set( "group.test", "off", {"entity_id": ["light.test1", "light.test2", "switch.test1"]}, ) hass.states.async_set("light.test1", "off") hass.states.async_set("light.test2", "off") hass.states.async_set("switch.test1", "off") state = State("group.test", "on") await async_reproduce_states(hass, [state], context=context) fun.assert_called_once_with( hass, [ clone_state(state, "light.test1"), clone_state(state, "light.test2"), clone_state(state, "switch.test1"), ], context=context, reproduce_options=None, )
async def test_unknown_zone(hass, calls, caplog): """Test for firing on zone enter.""" context = Context() hass.states.async_set( "test.entity", "hello", {"latitude": 32.881011, "longitude": -117.234758} ) await hass.async_block_till_done() assert await async_setup_component( hass, automation.DOMAIN, { automation.DOMAIN: { "alias": "My Automation", "trigger": { "platform": "zone", "entity_id": "test.entity", "zone": "zone.no_such_zone", "event": "enter", }, "action": { "service": "test.automation", }, } }, ) assert ( "Automation 'My Automation' is referencing non-existing zone 'zone.no_such_zone' in a zone trigger" not in caplog.text ) hass.states.async_set( "test.entity", "hello", {"latitude": 32.880586, "longitude": -117.237564}, context=context, ) await hass.async_block_till_done() assert ( "Automation 'My Automation' is referencing non-existing zone 'zone.no_such_zone' in a zone trigger" in caplog.text )
async def test_request_sync_service(aioclient_mock, hass): """Test that it posts to the request_sync url.""" aioclient_mock.post(ga.const.REQUEST_SYNC_BASE_URL, status=200) await async_setup_component( hass, "google_assistant", {"google_assistant": {"project_id": "test_project", "api_key": GA_API_KEY}}, ) assert aioclient_mock.call_count == 0 await hass.services.async_call( ga.const.DOMAIN, ga.const.SERVICE_REQUEST_SYNC, blocking=True, context=Context(user_id="123"), ) assert aioclient_mock.call_count == 1
async def test_calling_service_basic(hass): """Test the calling of a service.""" context = Context() calls = async_mock_service(hass, "test", "script") sequence = cv.SCRIPT_SCHEMA({ "service": "test.script", "data": { "hello": "world" } }) script_obj = script.Script(hass, sequence) await script_obj.async_run(context=context) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data.get("hello") == "world"
async def test_calling_service_template(hass): """Test the calling of a service.""" calls = [] context = Context() @callback def record_call(service): """Add recorded event to set.""" calls.append(service) hass.services.async_register("test", "script", record_call) hass.async_add_job( ft.partial( script.call_from_config, hass, { "service_template": """ {% if True %} test.script {% else %} test.not_script {% endif %}""", "data_template": { "hello": """ {% if is_world == 'yes' %} world {% else %} not world {% endif %} """ }, }, {"is_world": "yes"}, context=context, ) ) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data.get("hello") == "world"
async def test_input_button_context(hass, hass_admin_user): """Test that input_button context works.""" assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"update": {}}}) state = hass.states.get("input_button.update") assert state is not None await hass.services.async_call( DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: state.entity_id}, True, Context(user_id=hass_admin_user.id), ) state2 = hass.states.get("input_button.update") assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id
async def test_firing_event_template(hass): """Test the firing of events.""" event = "test_event" context = Context() calls = [] @callback def record_event(event): """Add recorded event to set.""" calls.append(event) hass.bus.async_listen(event, record_event) script_obj = script.Script( hass, cv.SCRIPT_SCHEMA({ "event": event, "event_data_template": { "dict": { 1: "{{ is_world }}", 2: "{{ is_world }}{{ is_world }}", 3: "{{ is_world }}{{ is_world }}{{ is_world }}", }, "list": ["{{ is_world }}", "{{ is_world }}{{ is_world }}"], }, }), ) await script_obj.async_run({"is_world": "yes"}, context=context) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data == { "dict": { 1: "yes", 2: "yesyes", 3: "yesyesyes" }, "list": ["yes", "yesyes"], } assert not script_obj.can_cancel
async def test_trigger_entity(hass, start_ha): """Test trigger entity works.""" state = hass.states.get("sensor.hello_name") assert state is not None assert state.state == STATE_UNKNOWN state = hass.states.get("sensor.bare_minimum") assert state is not None assert state.state == STATE_UNKNOWN context = Context() hass.bus.async_fire("test_event", {"beer": 2}, context=context) await hass.async_block_till_done() state = hass.states.get("sensor.hello_name") assert state.state == "2" assert state.attributes.get("device_class") == "battery" assert state.attributes.get("icon") == "mdi:pirate" assert state.attributes.get("entity_picture") == "/local/dogs.png" assert state.attributes.get("plus_one") == 3 assert state.attributes.get("unit_of_measurement") == "%" assert state.context is context ent_reg = entity_registry.async_get(hass) assert len(ent_reg.entities) == 2 assert ( ent_reg.entities["sensor.hello_name"].unique_id == "listening-test-event-hello_name-id" ) assert ( ent_reg.entities["sensor.via_list"].unique_id == "listening-test-event-via_list-id" ) state = hass.states.get("sensor.via_list") assert state.state == "3" assert state.attributes.get("device_class") == "battery" assert state.attributes.get("icon") == "mdi:pirate" assert state.attributes.get("entity_picture") == "/local/dogs.png" assert state.attributes.get("plus_one") == 3 assert state.attributes.get("unit_of_measurement") == "%" assert state.attributes.get("state_class") == "measurement" assert state.context is context
async def test_counter_context(hass, hass_admin_user): """Test that counter context works.""" assert await async_setup_component(hass, "counter", {"counter": {"test": {}}}) state = hass.states.get("counter.test") assert state is not None await hass.services.async_call( "counter", "increment", {"entity_id": state.entity_id}, True, Context(user_id=hass_admin_user.id), ) state2 = hass.states.get("counter.test") assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id
async def test_if_fires_on_change_with_for_advanced(hass, calls): """Test for firing on change with for advanced.""" context = Context() assert await async_setup_component( hass, automation.DOMAIN, { automation.DOMAIN: { "trigger": { "platform": "template", "value_template": '{{ is_state("test.entity", "world") }}', "for": { "seconds": 5 }, }, "action": { "service": "test.automation", "data_template": { "some": "{{ trigger.%s }}" % "}} - {{ trigger.".join(( "platform", "entity_id", "from_state.state", "to_state.state", "for", )) }, }, } }, ) await hass.async_block_till_done() hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].context.parent_id == context.id assert "template - test.entity - hello - world - 0:00:05" == calls[0].data[ "some"]
async def test_shared_context(hass): """Test that the shared context is passed down the chain.""" event = "test_event" context = Context() event_mock = Mock() run_mock = Mock() hass.bus.async_listen(event, event_mock) hass.bus.async_listen(EVENT_SCRIPT_STARTED, run_mock) assert await async_setup_component( hass, "script", {"script": { "test": { "sequence": [{ "event": event }] } }}) await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, context=context) await hass.async_block_till_done() assert event_mock.call_count == 1 assert run_mock.call_count == 1 args, kwargs = run_mock.call_args assert args[0].context == context # Ensure event data has all attributes set assert args[0].data.get(ATTR_NAME) == "test" assert args[0].data.get(ATTR_ENTITY_ID) == "script.test" # Ensure context carries through the event args, kwargs = event_mock.call_args assert args[0].context == context # Ensure the script state shares the same context state = hass.states.get("script.test") assert state is not None assert state.context == context
def test_delay(self): """Test the delay.""" event = 'test_event' events = [] context = Context() delay_alias = 'delay step' @callback def record_event(event): """Add recorded event to set.""" events.append(event) self.hass.bus.listen(event, record_event) script_obj = script.Script( self.hass, cv.SCRIPT_SCHEMA([{ 'event': event }, { 'delay': { 'seconds': 5 }, 'alias': delay_alias }, { 'event': event }])) script_obj.run(context=context) self.hass.block_till_done() assert script_obj.is_running assert script_obj.can_cancel assert script_obj.last_action == delay_alias assert len(events) == 1 future = dt_util.utcnow() + timedelta(seconds=5) fire_time_changed(self.hass, future) self.hass.block_till_done() assert not script_obj.is_running assert len(events) == 2 assert events[0].context is context assert events[1].context is context
def to_native(self, validate_entity_id=True): """Convert to a native HA Event.""" context = Context( id=self.context_id, user_id=self.context_user_id, parent_id=self.context_parent_id, ) try: return Event( self.event_type, json.loads(self.event_data), EventOrigin(self.origin), process_timestamp(self.time_fired), context=context, ) except ValueError: # When json.loads fails _LOGGER.exception("Error converting to event: %s", self) return None
async def test_template_with_trigger_templated_delay_on(hass): """Test binary sensor template with template delay on.""" config = { "template": { "trigger": { "platform": "event", "event_type": "test_event" }, "binary_sensor": { "name": "test", "state": "{{ trigger.event.data.beer == 2 }}", "device_class": "motion", "delay_on": '{{ ({ "seconds": 6 / 2 }) }}', "auto_off": '{{ ({ "seconds": 1 + 1 }) }}', }, } } await setup.async_setup_component(hass, "template", config) await hass.async_block_till_done() await hass.async_start() state = hass.states.get("binary_sensor.test") assert state.state == "off" context = Context() hass.bus.async_fire("test_event", {"beer": 2}, context=context) await hass.async_block_till_done() future = dt_util.utcnow() + timedelta(seconds=3) async_fire_time_changed(hass, future) await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state.state == "on" # Now wait for the auto-off future = dt_util.utcnow() + timedelta(seconds=2) async_fire_time_changed(hass, future) await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state.state == "off"
def test_firing_event_template(self): """Test the firing of events.""" event = 'test_event' context = Context() calls = [] @callback def record_event(event): """Add recorded event to set.""" calls.append(event) self.hass.bus.listen(event, record_event) script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA({ 'event': event, 'event_data_template': { 'dict': { 1: '{{ is_world }}', 2: '{{ is_world }}{{ is_world }}', 3: '{{ is_world }}{{ is_world }}{{ is_world }}', }, 'list': [ '{{ is_world }}', '{{ is_world }}{{ is_world }}' ] } })) script_obj.run({'is_world': 'yes'}, context=context) self.hass.block_till_done() assert len(calls) == 1 assert calls[0].context is context assert calls[0].data == { 'dict': { 1: 'yes', 2: 'yesyes', 3: 'yesyesyes', }, 'list': ['yes', 'yesyes'] } assert not script_obj.can_cancel
def test_wait_template(self): """Test the wait template.""" event = 'test_event' events = [] context = Context() wait_alias = 'wait step' @callback def record_event(event): """Add recorded event to set.""" events.append(event) self.hass.bus.listen(event, record_event) self.hass.states.set('switch.test', 'on') script_obj = script.Script( self.hass, cv.SCRIPT_SCHEMA([{ 'event': event }, { 'wait_template': "{{states.switch.test.state == 'off'}}", 'alias': wait_alias }, { 'event': event }])) script_obj.run(context=context) self.hass.block_till_done() assert script_obj.is_running assert script_obj.can_cancel assert script_obj.last_action == wait_alias assert len(events) == 1 self.hass.states.set('switch.test', 'off') self.hass.block_till_done() assert not script_obj.is_running assert len(events) == 2 assert events[0].context is context assert events[1].context is context
async def test_firing_event_basic(hass): """Test the firing of events.""" event = "test_event" context = Context() events = async_capture_events(hass, event) sequence = cv.SCRIPT_SCHEMA({ "event": event, "event_data": { "hello": "world" } }) script_obj = script.Script(hass, sequence) await script_obj.async_run(context=context) await hass.async_block_till_done() assert len(events) == 1 assert events[0].context is context assert events[0].data.get("hello") == "world"
async def test_state_with_context(hass): """Test that context is forwarded.""" hass.states.async_set( ENTITY_1, "something", {ATTR_SUPPORTED_FEATURES: MediaPlayerEntityFeature.TURN_ON}, ) calls = async_mock_service(hass, DOMAIN, SERVICE_TURN_ON) context = Context() await async_reproduce_states(hass, [State(ENTITY_1, "on")], context=context) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].data == {"entity_id": ENTITY_1} assert calls[0].context == context
def to_native(self, validate_entity_id: bool = True) -> Event | None: """Convert to a native HA Event.""" context = Context( id=self.context_id, user_id=self.context_user_id, parent_id=self.context_parent_id, ) try: return Event( self.event_type, json_loads(self.event_data) if self.event_data else {}, EventOrigin(self.origin) if self.origin else EVENT_ORIGIN_ORDER[self.origin_idx], process_timestamp(self.time_fired), context=context, ) except JSON_DECODE_EXCEPTIONS: # When json_loads fails _LOGGER.exception("Error converting to event: %s", self) return None