core/tests/components/automation/test_init.py

798 lines
26 KiB
Python

"""The tests for the automation component."""
import asyncio
from datetime import timedelta
import unittest
from unittest.mock import patch
from homeassistant.core import State, CoreState
from homeassistant.setup import setup_component, async_setup_component
import homeassistant.components.automation as automation
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import HomeAssistantError
import homeassistant.util.dt as dt_util
from tests.common import (
assert_setup_component, get_test_home_assistant, fire_time_changed,
mock_service, async_mock_service, mock_restore_cache)
from tests.components.automation import common
# pylint: disable=invalid-name
class TestAutomation(unittest.TestCase):
"""Test the event automation."""
def setUp(self):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test', 'automation')
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
def test_service_data_not_a_dict(self):
"""Test service data not dict."""
with assert_setup_component(0, automation.DOMAIN):
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'data': 100,
}
}
})
def test_service_specify_data(self):
"""Test service data."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'data_template': {
'some': '{{ trigger.platform }} - '
'{{ trigger.event.event_type }}'
},
}
}
})
time = dt_util.utcnow()
with patch('homeassistant.components.automation.utcnow',
return_value=time):
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data['some'] == 'event - test_event'
state = self.hass.states.get('automation.hello')
assert state is not None
assert state.attributes.get('last_triggered') == time
state = self.hass.states.get('group.all_automations')
assert state is not None
assert state.attributes.get('entity_id') == ('automation.hello',)
def test_action_delay(self):
"""Test action delay."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': [
{
'service': 'test.automation',
'data_template': {
'some': '{{ trigger.platform }} - '
'{{ trigger.event.event_type }}'
}
},
{'delay': {'minutes': '10'}},
{
'service': 'test.automation',
'data_template': {
'some': '{{ trigger.platform }} - '
'{{ trigger.event.event_type }}'
}
},
]
}
})
time = dt_util.utcnow()
with patch('homeassistant.components.automation.utcnow',
return_value=time):
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data['some'] == 'event - test_event'
future = dt_util.utcnow() + timedelta(minutes=10)
fire_time_changed(self.hass, future)
self.hass.block_till_done()
assert len(self.calls) == 2
assert self.calls[1].data['some'] == 'event - test_event'
state = self.hass.states.get('automation.hello')
assert state is not None
assert state.attributes.get('last_triggered') == time
state = self.hass.states.get('group.all_automations')
assert state is not None
assert state.attributes.get('entity_id') == ('automation.hello',)
def test_service_specify_entity_id(self):
"""Test service data."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_service_specify_entity_id_list(self):
"""Test service data."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': ['hello.world', 'hello.world2']
}
}
})
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.assertEqual(['hello.world', 'hello.world2'],
self.calls[0].data.get(ATTR_ENTITY_ID))
def test_two_triggers(self):
"""Test triggers."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
{
'platform': 'state',
'entity_id': 'test.entity',
}
],
'action': {
'service': 'test.automation',
}
}
})
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set('test.entity', 'hello')
self.hass.block_till_done()
self.assertEqual(2, len(self.calls))
def test_trigger_service_ignoring_condition(self):
"""Test triggers."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'test',
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
],
'condition': {
'condition': 'state',
'entity_id': 'non.existing',
'state': 'beer',
},
'action': {
'service': 'test.automation',
}
}
})
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 0
self.hass.services.call('automation', 'trigger',
{'entity_id': 'automation.test'},
blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 1
def test_two_conditions_with_and(self):
"""Test two and conditions."""
entity_id = 'test.entity'
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': [
{
'platform': 'event',
'event_type': 'test_event',
},
],
'condition': [
{
'condition': 'state',
'entity_id': entity_id,
'state': '100'
},
{
'condition': 'numeric_state',
'entity_id': entity_id,
'below': 150
}
],
'action': {
'service': 'test.automation',
}
}
})
self.hass.states.set(entity_id, 100)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 101)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.states.set(entity_id, 151)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
def test_automation_list_setting(self):
"""Event is not a valid condition."""
self.assertTrue(setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: [{
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
}
}, {
'trigger': {
'platform': 'event',
'event_type': 'test_event_2',
},
'action': {
'service': 'test.automation',
}
}]
}))
self.hass.bus.fire('test_event')
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
self.hass.bus.fire('test_event_2')
self.hass.block_till_done()
self.assertEqual(2, len(self.calls))
def test_automation_calling_two_actions(self):
"""Test if we can call two actions from automation definition."""
self.assertTrue(setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': [{
'service': 'test.automation',
'data': {'position': 0},
}, {
'service': 'test.automation',
'data': {'position': 1},
}],
}
}))
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 2
assert self.calls[0].data['position'] == 0
assert self.calls[1].data['position'] == 1
def test_services(self):
"""Test the automation services for turning entities on/off."""
entity_id = 'automation.hello'
assert self.hass.states.get(entity_id) is None
assert not automation.is_on(self.hass, entity_id)
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
}
}
})
assert self.hass.states.get(entity_id) is not None
assert automation.is_on(self.hass, entity_id)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
common.turn_off(self.hass, entity_id)
self.hass.block_till_done()
assert not automation.is_on(self.hass, entity_id)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
common.toggle(self.hass, entity_id)
self.hass.block_till_done()
assert automation.is_on(self.hass, entity_id)
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 2
common.trigger(self.hass, entity_id)
self.hass.block_till_done()
assert len(self.calls) == 3
common.turn_off(self.hass, entity_id)
self.hass.block_till_done()
common.trigger(self.hass, entity_id)
self.hass.block_till_done()
assert len(self.calls) == 4
common.turn_on(self.hass, entity_id)
self.hass.block_till_done()
assert automation.is_on(self.hass, entity_id)
def test_reload_config_service(self):
"""Test the reload config service."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'data_template': {
'event': '{{ trigger.event.event_type }}'
}
}
}
})
assert self.hass.states.get('automation.hello') is not None
assert self.hass.states.get('automation.bye') is None
listeners = self.hass.bus.listeners
assert listeners.get('test_event') == 1
assert listeners.get('test_event2') is None
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data.get('event') == 'test_event'
with patch('homeassistant.config.load_yaml_config_file', autospec=True,
return_value={
automation.DOMAIN: {
'alias': 'bye',
'trigger': {
'platform': 'event',
'event_type': 'test_event2',
},
'action': {
'service': 'test.automation',
'data_template': {
'event': '{{ trigger.event.event_type }}'
}
}
}}):
with patch('homeassistant.config.find_config_file',
return_value=''):
common.reload(self.hass)
self.hass.block_till_done()
# De-flake ?!
self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is None
assert self.hass.states.get('automation.bye') is not None
listeners = self.hass.bus.listeners
assert listeners.get('test_event') is None
assert listeners.get('test_event2') == 1
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
self.hass.bus.fire('test_event2')
self.hass.block_till_done()
assert len(self.calls) == 2
assert self.calls[1].data.get('event') == 'test_event2'
def test_reload_config_when_invalid_config(self):
"""Test the reload config service handling invalid config."""
with assert_setup_component(1, automation.DOMAIN):
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'data_template': {
'event': '{{ trigger.event.event_type }}'
}
}
}
})
assert self.hass.states.get('automation.hello') is not None
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data.get('event') == 'test_event'
with patch('homeassistant.config.load_yaml_config_file', autospec=True,
return_value={automation.DOMAIN: 'not valid'}):
with patch('homeassistant.config.find_config_file',
return_value=''):
common.reload(self.hass)
self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is None
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
def test_reload_config_handles_load_fails(self):
"""Test the reload config service."""
assert setup_component(self.hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'data_template': {
'event': '{{ trigger.event.event_type }}'
}
}
}
})
assert self.hass.states.get('automation.hello') is not None
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data.get('event') == 'test_event'
with patch('homeassistant.config.load_yaml_config_file',
side_effect=HomeAssistantError('bla')):
with patch('homeassistant.config.find_config_file',
return_value=''):
common.reload(self.hass)
self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is not None
self.hass.bus.fire('test_event')
self.hass.block_till_done()
assert len(self.calls) == 2
@asyncio.coroutine
def test_automation_restore_state(hass):
"""Ensure states are restored on startup."""
time = dt_util.utcnow()
mock_restore_cache(hass, (
State('automation.hello', STATE_ON),
State('automation.bye', STATE_OFF, {'last_triggered': time}),
))
config = {automation.DOMAIN: [{
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event_hello',
},
'action': {'service': 'test.automation'}
}, {
'alias': 'bye',
'trigger': {
'platform': 'event',
'event_type': 'test_event_bye',
},
'action': {'service': 'test.automation'}
}]}
assert (yield from async_setup_component(hass, automation.DOMAIN, config))
state = hass.states.get('automation.hello')
assert state
assert state.state == STATE_ON
state = hass.states.get('automation.bye')
assert state
assert state.state == STATE_OFF
assert state.attributes.get('last_triggered') == time
calls = async_mock_service(hass, 'test', 'automation')
assert automation.is_on(hass, 'automation.bye') is False
hass.bus.async_fire('test_event_bye')
yield from hass.async_block_till_done()
assert len(calls) == 0
assert automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event_hello')
yield from hass.async_block_till_done()
assert len(calls) == 1
@asyncio.coroutine
def test_initial_value_off(hass):
"""Test initial value off."""
calls = async_mock_service(hass, 'test', 'automation')
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'initial_state': 'off',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert not automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 0
@asyncio.coroutine
def test_initial_value_on(hass):
"""Test initial value on."""
calls = async_mock_service(hass, 'test', 'automation')
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'initial_state': 'on',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': ['hello.world', 'hello.world2']
}
}
})
assert res
assert automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 1
@asyncio.coroutine
def test_initial_value_off_but_restore_on(hass):
"""Test initial value off and restored state is turned on."""
calls = async_mock_service(hass, 'test', 'automation')
mock_restore_cache(hass, (
State('automation.hello', STATE_ON),
))
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'initial_state': 'off',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert not automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 0
@asyncio.coroutine
def test_initial_value_on_but_restore_off(hass):
"""Test initial value on and restored state is turned off."""
calls = async_mock_service(hass, 'test', 'automation')
mock_restore_cache(hass, (
State('automation.hello', STATE_OFF),
))
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'initial_state': 'on',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 1
@asyncio.coroutine
def test_no_initial_value_and_restore_off(hass):
"""Test initial value off and restored state is turned on."""
calls = async_mock_service(hass, 'test', 'automation')
mock_restore_cache(hass, (
State('automation.hello', STATE_OFF),
))
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert not automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 0
@asyncio.coroutine
def test_automation_is_on_if_no_initial_state_or_restore(hass):
"""Test initial value is on when no initial state or restored state."""
calls = async_mock_service(hass, 'test', 'automation')
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 1
@asyncio.coroutine
def test_automation_not_trigger_on_bootstrap(hass):
"""Test if automation is not trigger on bootstrap."""
hass.state = CoreState.not_running
calls = async_mock_service(hass, 'test', 'automation')
res = yield from async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'alias': 'hello',
'trigger': {
'platform': 'event',
'event_type': 'test_event',
},
'action': {
'service': 'test.automation',
'entity_id': 'hello.world'
}
}
})
assert res
assert not automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 0
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from hass.async_block_till_done()
assert automation.is_on(hass, 'automation.hello')
hass.bus.async_fire('test_event')
yield from hass.async_block_till_done()
assert len(calls) == 1
assert ['hello.world'] == calls[0].data.get(ATTR_ENTITY_ID)