336 lines
12 KiB
Python
336 lines
12 KiB
Python
"""The tests for the Entity component helper."""
|
|
# pylint: disable=protected-access
|
|
import asyncio
|
|
from collections import OrderedDict
|
|
import logging
|
|
import unittest
|
|
from unittest.mock import patch, Mock
|
|
from datetime import timedelta
|
|
|
|
import homeassistant.core as ha
|
|
import homeassistant.loader as loader
|
|
from homeassistant.exceptions import PlatformNotReady
|
|
from homeassistant.components import group
|
|
from homeassistant.helpers.entity_component import EntityComponent
|
|
from homeassistant.setup import setup_component, async_setup_component
|
|
|
|
from homeassistant.helpers import discovery
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
from tests.common import (
|
|
get_test_home_assistant, MockPlatform, MockModule, mock_coro,
|
|
async_fire_time_changed, MockEntity)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
DOMAIN = "test_domain"
|
|
|
|
|
|
class TestHelpersEntityComponent(unittest.TestCase):
|
|
"""Test homeassistant.helpers.entity_component module."""
|
|
|
|
def setUp(self): # pylint: disable=invalid-name
|
|
"""Initialize a test Home Assistant instance."""
|
|
self.hass = get_test_home_assistant()
|
|
|
|
def tearDown(self): # pylint: disable=invalid-name
|
|
"""Clean up the test Home Assistant instance."""
|
|
self.hass.stop()
|
|
|
|
def test_setting_up_group(self):
|
|
"""Setup the setting of a group."""
|
|
setup_component(self.hass, 'group', {'group': {}})
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass,
|
|
group_name='everyone')
|
|
|
|
# No group after setup
|
|
assert len(self.hass.states.entity_ids()) == 0
|
|
|
|
component.add_entities([MockEntity()])
|
|
self.hass.block_till_done()
|
|
|
|
# group exists
|
|
assert len(self.hass.states.entity_ids()) == 2
|
|
assert self.hass.states.entity_ids('group') == ['group.everyone']
|
|
|
|
group = self.hass.states.get('group.everyone')
|
|
|
|
assert group.attributes.get('entity_id') == \
|
|
('test_domain.unnamed_device',)
|
|
|
|
# group extended
|
|
component.add_entities([MockEntity(name='goodbye')])
|
|
self.hass.block_till_done()
|
|
|
|
assert len(self.hass.states.entity_ids()) == 3
|
|
group = self.hass.states.get('group.everyone')
|
|
|
|
# Ordered in order of added to the group
|
|
assert group.attributes.get('entity_id') == \
|
|
('test_domain.goodbye', 'test_domain.unnamed_device')
|
|
|
|
def test_setup_loads_platforms(self):
|
|
"""Test the loading of the platforms."""
|
|
component_setup = Mock(return_value=True)
|
|
platform_setup = Mock(return_value=None)
|
|
loader.set_component(
|
|
'test_component',
|
|
MockModule('test_component', setup=component_setup))
|
|
loader.set_component('test_domain.mod2',
|
|
MockPlatform(platform_setup, ['test_component']))
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
|
|
|
assert not component_setup.called
|
|
assert not platform_setup.called
|
|
|
|
component.setup({
|
|
DOMAIN: {
|
|
'platform': 'mod2',
|
|
}
|
|
})
|
|
|
|
self.hass.block_till_done()
|
|
assert component_setup.called
|
|
assert platform_setup.called
|
|
|
|
def test_setup_recovers_when_setup_raises(self):
|
|
"""Test the setup if exceptions are happening."""
|
|
platform1_setup = Mock(side_effect=Exception('Broken'))
|
|
platform2_setup = Mock(return_value=None)
|
|
|
|
loader.set_component('test_domain.mod1', MockPlatform(platform1_setup))
|
|
loader.set_component('test_domain.mod2', MockPlatform(platform2_setup))
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
|
|
|
assert not platform1_setup.called
|
|
assert not platform2_setup.called
|
|
|
|
component.setup(OrderedDict([
|
|
(DOMAIN, {'platform': 'mod1'}),
|
|
("{} 2".format(DOMAIN), {'platform': 'non_exist'}),
|
|
("{} 3".format(DOMAIN), {'platform': 'mod2'}),
|
|
]))
|
|
|
|
self.hass.block_till_done()
|
|
assert platform1_setup.called
|
|
assert platform2_setup.called
|
|
|
|
@patch('homeassistant.helpers.entity_component.EntityComponent'
|
|
'._async_setup_platform', return_value=mock_coro())
|
|
@patch('homeassistant.setup.async_setup_component',
|
|
return_value=mock_coro(True))
|
|
def test_setup_does_discovery(self, mock_setup_component, mock_setup):
|
|
"""Test setup for discovery."""
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
|
|
|
component.setup({})
|
|
|
|
discovery.load_platform(self.hass, DOMAIN, 'platform_test',
|
|
{'msg': 'discovery_info'})
|
|
|
|
self.hass.block_till_done()
|
|
|
|
assert mock_setup.called
|
|
assert ('platform_test', {}, {'msg': 'discovery_info'}) == \
|
|
mock_setup.call_args[0]
|
|
|
|
@patch('homeassistant.helpers.entity_platform.'
|
|
'async_track_time_interval')
|
|
def test_set_scan_interval_via_config(self, mock_track):
|
|
"""Test the setting of the scan interval via configuration."""
|
|
def platform_setup(hass, config, add_devices, discovery_info=None):
|
|
"""Test the platform setup."""
|
|
add_devices([MockEntity(should_poll=True)])
|
|
|
|
loader.set_component('test_domain.platform',
|
|
MockPlatform(platform_setup))
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
|
|
|
component.setup({
|
|
DOMAIN: {
|
|
'platform': 'platform',
|
|
'scan_interval': timedelta(seconds=30),
|
|
}
|
|
})
|
|
|
|
self.hass.block_till_done()
|
|
assert mock_track.called
|
|
assert timedelta(seconds=30) == mock_track.call_args[0][2]
|
|
|
|
def test_set_entity_namespace_via_config(self):
|
|
"""Test setting an entity namespace."""
|
|
def platform_setup(hass, config, add_devices, discovery_info=None):
|
|
"""Test the platform setup."""
|
|
add_devices([
|
|
MockEntity(name='beer'),
|
|
MockEntity(name=None),
|
|
])
|
|
|
|
platform = MockPlatform(platform_setup)
|
|
|
|
loader.set_component('test_domain.platform', platform)
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, self.hass)
|
|
|
|
component.setup({
|
|
DOMAIN: {
|
|
'platform': 'platform',
|
|
'entity_namespace': 'yummy'
|
|
}
|
|
})
|
|
|
|
self.hass.block_till_done()
|
|
|
|
assert sorted(self.hass.states.entity_ids()) == \
|
|
['test_domain.yummy_beer', 'test_domain.yummy_unnamed_device']
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_extract_from_service_available_device(hass):
|
|
"""Test the extraction of entity from service and device is available."""
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
yield from component.async_add_entities([
|
|
MockEntity(name='test_1'),
|
|
MockEntity(name='test_2', available=False),
|
|
MockEntity(name='test_3'),
|
|
MockEntity(name='test_4', available=False),
|
|
])
|
|
|
|
call_1 = ha.ServiceCall('test', 'service')
|
|
|
|
assert ['test_domain.test_1', 'test_domain.test_3'] == \
|
|
sorted(ent.entity_id for ent in
|
|
component.async_extract_from_service(call_1))
|
|
|
|
call_2 = ha.ServiceCall('test', 'service', data={
|
|
'entity_id': ['test_domain.test_3', 'test_domain.test_4'],
|
|
})
|
|
|
|
assert ['test_domain.test_3'] == \
|
|
sorted(ent.entity_id for ent in
|
|
component.async_extract_from_service(call_2))
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_platform_not_ready(hass):
|
|
"""Test that we retry when platform not ready."""
|
|
platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady,
|
|
None])
|
|
loader.set_component('test_domain.mod1', MockPlatform(platform1_setup))
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
|
|
yield from component.async_setup({
|
|
DOMAIN: {
|
|
'platform': 'mod1'
|
|
}
|
|
})
|
|
|
|
assert len(platform1_setup.mock_calls) == 1
|
|
assert 'test_domain.mod1' not in hass.config.components
|
|
|
|
utcnow = dt_util.utcnow()
|
|
|
|
with patch('homeassistant.util.dt.utcnow', return_value=utcnow):
|
|
# Should not trigger attempt 2
|
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=29))
|
|
yield from hass.async_block_till_done()
|
|
assert len(platform1_setup.mock_calls) == 1
|
|
|
|
# Should trigger attempt 2
|
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=30))
|
|
yield from hass.async_block_till_done()
|
|
assert len(platform1_setup.mock_calls) == 2
|
|
assert 'test_domain.mod1' not in hass.config.components
|
|
|
|
# This should not trigger attempt 3
|
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=59))
|
|
yield from hass.async_block_till_done()
|
|
assert len(platform1_setup.mock_calls) == 2
|
|
|
|
# Trigger attempt 3, which succeeds
|
|
async_fire_time_changed(hass, utcnow + timedelta(seconds=60))
|
|
yield from hass.async_block_till_done()
|
|
assert len(platform1_setup.mock_calls) == 3
|
|
assert 'test_domain.mod1' in hass.config.components
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_extract_from_service_returns_all_if_no_entity_id(hass):
|
|
"""Test the extraction of everything from service."""
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
yield from component.async_add_entities([
|
|
MockEntity(name='test_1'),
|
|
MockEntity(name='test_2'),
|
|
])
|
|
|
|
call = ha.ServiceCall('test', 'service')
|
|
|
|
assert ['test_domain.test_1', 'test_domain.test_2'] == \
|
|
sorted(ent.entity_id for ent in
|
|
component.async_extract_from_service(call))
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_extract_from_service_filter_out_non_existing_entities(hass):
|
|
"""Test the extraction of non existing entities from service."""
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
yield from component.async_add_entities([
|
|
MockEntity(name='test_1'),
|
|
MockEntity(name='test_2'),
|
|
])
|
|
|
|
call = ha.ServiceCall('test', 'service', {
|
|
'entity_id': ['test_domain.test_2', 'test_domain.non_exist']
|
|
})
|
|
|
|
assert ['test_domain.test_2'] == \
|
|
[ent.entity_id for ent
|
|
in component.async_extract_from_service(call)]
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_extract_from_service_no_group_expand(hass):
|
|
"""Test not expanding a group."""
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
test_group = yield from group.Group.async_create_group(
|
|
hass, 'test_group', ['light.Ceiling', 'light.Kitchen'])
|
|
yield from component.async_add_entities([test_group])
|
|
|
|
call = ha.ServiceCall('test', 'service', {
|
|
'entity_id': ['group.test_group']
|
|
})
|
|
|
|
extracted = component.async_extract_from_service(call, expand_group=False)
|
|
assert extracted == [test_group]
|
|
|
|
|
|
@asyncio.coroutine
|
|
def test_setup_dependencies_platform(hass):
|
|
"""Test we setup the dependencies of a platform.
|
|
|
|
We're explictely testing that we process dependencies even if a component
|
|
with the same name has already been loaded.
|
|
"""
|
|
loader.set_component('test_component', MockModule('test_component'))
|
|
loader.set_component('test_component2', MockModule('test_component2'))
|
|
loader.set_component(
|
|
'test_domain.test_component',
|
|
MockPlatform(dependencies=['test_component', 'test_component2']))
|
|
|
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
|
|
|
yield from async_setup_component(hass, 'test_component', {})
|
|
|
|
yield from component.async_setup({
|
|
DOMAIN: {
|
|
'platform': 'test_component',
|
|
}
|
|
})
|
|
|
|
assert 'test_component' in hass.config.components
|
|
assert 'test_component2' in hass.config.components
|
|
assert 'test_domain.test_component' in hass.config.components
|