From 407e0c58f90a4d972ed98bd608255f9f70c4da42 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 30 Apr 2019 18:20:38 +0200 Subject: [PATCH] Migrate tests to pytest (#23544) * Migrate tests to pytest * Fixup * Use loop fixture in test_check_config * Lint --- tests/helpers/test_entity_component.py | 367 +++--- tests/helpers/test_entity_platform.py | 390 +++--- tests/helpers/test_event.py | 1423 ++++++++++---------- tests/helpers/test_icon.py | 82 +- tests/helpers/test_init.py | 67 +- tests/helpers/test_intent.py | 39 +- tests/helpers/test_location.py | 89 +- tests/helpers/test_script.py | 1336 +++++++++---------- tests/helpers/test_state.py | 207 ++- tests/helpers/test_sun.py | 366 +++--- tests/helpers/test_temperature.py | 50 +- tests/helpers/test_template.py | 1680 ++++++++++++------------ tests/scripts/test_check_config.py | 265 ++-- tests/scripts/test_init.py | 22 +- tests/test_config.py | 902 +++++++------ tests/util/test_color.py | 531 ++++---- tests/util/test_distance.py | 113 +- tests/util/test_dt.py | 377 +++--- tests/util/test_init.py | 335 ++--- tests/util/test_json.py | 130 +- tests/util/test_pressure.py | 90 +- tests/util/test_ruamel_yaml.py | 77 +- tests/util/test_unit_system.py | 225 ++-- tests/util/test_volume.py | 64 +- tests/util/test_yaml.py | 427 +++--- 25 files changed, 4744 insertions(+), 4910 deletions(-) diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index cb433a16a7c..4fc834171c8 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -1,25 +1,24 @@ """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 asynctest import pytest import homeassistant.core as ha 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.setup import 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, + MockPlatform, MockModule, mock_coro, async_fire_time_changed, MockEntity, MockConfigEntry, mock_entity_platform, mock_integration) @@ -27,178 +26,169 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "test_domain" -class TestHelpersEntityComponent(unittest.TestCase): - """Test homeassistant.helpers.entity_component module.""" +async def test_setting_up_group(hass): + """Set up the setting of a group.""" + assert await async_setup_component(hass, 'group', {'group': {}}) + component = EntityComponent(_LOGGER, DOMAIN, hass, + group_name='everyone') - def setUp(self): # pylint: disable=invalid-name - """Initialize a test Home Assistant instance.""" - self.hass = get_test_home_assistant() + # No group after setup + assert len(hass.states.async_entity_ids()) == 0 - def tearDown(self): # pylint: disable=invalid-name - """Clean up the test Home Assistant instance.""" - self.hass.stop() + await component.async_add_entities([MockEntity()]) + await hass.async_block_till_done() - def test_setting_up_group(self): - """Set up the setting of a group.""" - setup_component(self.hass, 'group', {'group': {}}) - component = EntityComponent(_LOGGER, DOMAIN, self.hass, - group_name='everyone') + # group exists + assert len(hass.states.async_entity_ids()) == 2 + assert hass.states.async_entity_ids('group') == ['group.everyone'] - # No group after setup - assert len(self.hass.states.entity_ids()) == 0 + grp = hass.states.get('group.everyone') - component.add_entities([MockEntity()]) - self.hass.block_till_done() + assert grp.attributes.get('entity_id') == \ + ('test_domain.unnamed_device',) - # group exists - assert len(self.hass.states.entity_ids()) == 2 - assert self.hass.states.entity_ids('group') == ['group.everyone'] + # group extended + await component.async_add_entities([MockEntity(name='goodbye')]) + await hass.async_block_till_done() - group = self.hass.states.get('group.everyone') + assert len(hass.states.async_entity_ids()) == 3 + grp = 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) - - mock_integration(self.hass, - MockModule('test_component', setup=component_setup)) - # mock the dependencies - mock_integration(self.hass, - MockModule('mod2', dependencies=['test_component'])) - mock_entity_platform(self.hass, 'test_domain.mod2', - MockPlatform(platform_setup)) - - 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) - - mock_entity_platform(self.hass, 'test_domain.mod1', - MockPlatform(platform1_setup)) - mock_entity_platform(self.hass, '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'}, {DOMAIN: {}}) - - 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_entities, discovery_info=None): - """Test the platform setup.""" - add_entities([MockEntity(should_poll=True)]) - - mock_entity_platform(self.hass, '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_entities, discovery_info=None): - """Test the platform setup.""" - add_entities([ - MockEntity(name='beer'), - MockEntity(name=None), - ]) - - platform = MockPlatform(platform_setup) - - mock_entity_platform(self.hass, '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'] + # Ordered in order of added to the group + assert grp.attributes.get('entity_id') == \ + ('test_domain.goodbye', 'test_domain.unnamed_device') -@asyncio.coroutine -def test_extract_from_service_available_device(hass): +async def test_setup_loads_platforms(hass): + """Test the loading of the platforms.""" + component_setup = Mock(return_value=True) + platform_setup = Mock(return_value=None) + + mock_integration(hass, MockModule('test_component', setup=component_setup)) + # mock the dependencies + mock_integration(hass, MockModule('mod2', dependencies=['test_component'])) + mock_entity_platform(hass, 'test_domain.mod2', + MockPlatform(platform_setup)) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + assert not component_setup.called + assert not platform_setup.called + + component.setup({ + DOMAIN: { + 'platform': 'mod2', + } + }) + + await hass.async_block_till_done() + assert component_setup.called + assert platform_setup.called + + +async def test_setup_recovers_when_setup_raises(hass): + """Test the setup if exceptions are happening.""" + platform1_setup = Mock(side_effect=Exception('Broken')) + platform2_setup = Mock(return_value=None) + + mock_entity_platform(hass, 'test_domain.mod1', + MockPlatform(platform1_setup)) + mock_entity_platform(hass, 'test_domain.mod2', + MockPlatform(platform2_setup)) + + component = EntityComponent(_LOGGER, DOMAIN, 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'}), + ])) + + await hass.async_block_till_done() + assert platform1_setup.called + assert platform2_setup.called + + +@asynctest.patch('homeassistant.helpers.entity_component.EntityComponent' + '._async_setup_platform', return_value=mock_coro()) +@asynctest.patch('homeassistant.setup.async_setup_component', + return_value=mock_coro(True)) +async def test_setup_does_discovery(mock_setup_component, mock_setup, hass): + """Test setup for discovery.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + + component.setup({}) + + discovery.load_platform(hass, DOMAIN, 'platform_test', + {'msg': 'discovery_info'}, {DOMAIN: {}}) + + await hass.async_block_till_done() + + assert mock_setup.called + assert ('platform_test', {}, {'msg': 'discovery_info'}) == \ + mock_setup.call_args[0] + + +@asynctest.patch('homeassistant.helpers.entity_platform.' + 'async_track_time_interval') +async def test_set_scan_interval_via_config(mock_track, hass): + """Test the setting of the scan interval via configuration.""" + def platform_setup(hass, config, add_entities, discovery_info=None): + """Test the platform setup.""" + add_entities([MockEntity(should_poll=True)]) + + mock_entity_platform(hass, 'test_domain.platform', + MockPlatform(platform_setup)) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + component.setup({ + DOMAIN: { + 'platform': 'platform', + 'scan_interval': timedelta(seconds=30), + } + }) + + await hass.async_block_till_done() + assert mock_track.called + assert timedelta(seconds=30) == mock_track.call_args[0][2] + + +async def test_set_entity_namespace_via_config(hass): + """Test setting an entity namespace.""" + def platform_setup(hass, config, add_entities, discovery_info=None): + """Test the platform setup.""" + add_entities([ + MockEntity(name='beer'), + MockEntity(name=None), + ]) + + platform = MockPlatform(platform_setup) + + mock_entity_platform(hass, 'test_domain.platform', platform) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + component.setup({ + DOMAIN: { + 'platform': 'platform', + 'entity_namespace': 'yummy' + } + }) + + await hass.async_block_till_done() + + assert sorted(hass.states.async_entity_ids()) == \ + ['test_domain.yummy_beer', 'test_domain.yummy_unnamed_device'] + + +async 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([ + await component.async_add_entities([ MockEntity(name='test_1'), MockEntity(name='test_2', available=False), MockEntity(name='test_3'), @@ -209,7 +199,7 @@ def test_extract_from_service_available_device(hass): assert ['test_domain.test_1', 'test_domain.test_3'] == \ sorted(ent.entity_id for ent in - (yield from component.async_extract_from_service(call_1))) + (await component.async_extract_from_service(call_1))) call_2 = ha.ServiceCall('test', 'service', data={ 'entity_id': ['test_domain.test_3', 'test_domain.test_4'], @@ -217,11 +207,10 @@ def test_extract_from_service_available_device(hass): assert ['test_domain.test_3'] == \ sorted(ent.entity_id for ent in - (yield from component.async_extract_from_service(call_2))) + (await component.async_extract_from_service(call_2))) -@asyncio.coroutine -def test_platform_not_ready(hass): +async def test_platform_not_ready(hass): """Test that we retry when platform not ready.""" platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady, None]) @@ -231,7 +220,7 @@ def test_platform_not_ready(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'mod1' } @@ -245,32 +234,31 @@ def test_platform_not_ready(hass): 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() + await 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() + await 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() + await 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() + await 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): +async 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([ + await component.async_add_entities([ MockEntity(name='test_1'), MockEntity(name='test_2'), ]) @@ -279,14 +267,13 @@ def test_extract_from_service_returns_all_if_no_entity_id(hass): assert ['test_domain.test_1', 'test_domain.test_2'] == \ sorted(ent.entity_id for ent in - (yield from component.async_extract_from_service(call))) + (await component.async_extract_from_service(call))) -@asyncio.coroutine -def test_extract_from_service_filter_out_non_existing_entities(hass): +async 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([ + await component.async_add_entities([ MockEntity(name='test_1'), MockEntity(name='test_2'), ]) @@ -297,28 +284,26 @@ def test_extract_from_service_filter_out_non_existing_entities(hass): assert ['test_domain.test_2'] == \ [ent.entity_id for ent - in (yield from component.async_extract_from_service(call))] + in await component.async_extract_from_service(call)] -@asyncio.coroutine -def test_extract_from_service_no_group_expand(hass): +async 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( + test_group = await group.Group.async_create_group( hass, 'test_group', ['light.Ceiling', 'light.Kitchen']) - yield from component.async_add_entities([test_group]) + await component.async_add_entities([test_group]) call = ha.ServiceCall('test', 'service', { 'entity_id': ['group.test_group'] }) - extracted = yield from component.async_extract_from_service( + extracted = await component.async_extract_from_service( call, expand_group=False) assert extracted == [test_group] -@asyncio.coroutine -def test_setup_dependencies_platform(hass): +async 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 @@ -331,7 +316,7 @@ def test_setup_dependencies_platform(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'test_component', } @@ -355,7 +340,7 @@ async def test_setup_entry(hass): assert await component.async_setup_entry(entry) assert len(mock_setup_entry.mock_calls) == 1 - p_hass, p_entry, p_add_entities = mock_setup_entry.mock_calls[0][1] + p_hass, p_entry, _ = mock_setup_entry.mock_calls[0][1] assert p_hass is hass assert p_entry is entry @@ -448,7 +433,7 @@ async def test_set_service_race(hass): await async_setup_component(hass, 'group', {}) component = EntityComponent(_LOGGER, DOMAIN, hass, group_name='yo') - for i in range(2): + for _ in range(2): hass.async_create_task(component.async_add_entities([MockEntity()])) await hass.async_block_till_done() diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 0fed09b7cbc..65c22aa176f 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1,14 +1,14 @@ """Tests for the EntityPlatform helper.""" import asyncio import logging -import unittest from unittest.mock import patch, Mock, MagicMock from datetime import timedelta +import asynctest import pytest from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_component import ( EntityComponent, DEFAULT_SCAN_INTERVAL) from homeassistant.helpers import entity_platform, entity_registry @@ -16,7 +16,7 @@ from homeassistant.helpers import entity_platform, entity_registry import homeassistant.util.dt as dt_util from tests.common import ( - get_test_home_assistant, MockPlatform, fire_time_changed, mock_registry, + MockPlatform, async_fire_time_changed, mock_registry, MockEntity, MockEntityPlatform, MockConfigEntry, mock_entity_platform) _LOGGER = logging.getLogger(__name__) @@ -24,164 +24,158 @@ DOMAIN = "test_domain" PLATFORM = 'test_platform' -class TestHelpersEntityPlatform(unittest.TestCase): - """Test homeassistant.helpers.entity_component module.""" +async def test_polling_only_updates_entities_it_should_poll(hass): + """Test the polling of only updated entities.""" + component = EntityComponent( + _LOGGER, DOMAIN, hass, timedelta(seconds=20)) - def setUp(self): # pylint: disable=invalid-name - """Initialize a test Home Assistant instance.""" - self.hass = get_test_home_assistant() + no_poll_ent = MockEntity(should_poll=False) + no_poll_ent.async_update = Mock() + poll_ent = MockEntity(should_poll=True) + poll_ent.async_update = Mock() - def tearDown(self): # pylint: disable=invalid-name - """Clean up the test Home Assistant instance.""" - self.hass.stop() + await component.async_add_entities([no_poll_ent, poll_ent]) - def test_polling_only_updates_entities_it_should_poll(self): - """Test the polling of only updated entities.""" - component = EntityComponent( - _LOGGER, DOMAIN, self.hass, timedelta(seconds=20)) + no_poll_ent.async_update.reset_mock() + poll_ent.async_update.reset_mock() - no_poll_ent = MockEntity(should_poll=False) - no_poll_ent.async_update = Mock() - poll_ent = MockEntity(should_poll=True) - poll_ent.async_update = Mock() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20)) + await hass.async_block_till_done() - component.add_entities([no_poll_ent, poll_ent]) - - no_poll_ent.async_update.reset_mock() - poll_ent.async_update.reset_mock() - - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=20)) - self.hass.block_till_done() - - assert not no_poll_ent.async_update.called - assert poll_ent.async_update.called - - def test_polling_updates_entities_with_exception(self): - """Test the updated entities that not break with an exception.""" - component = EntityComponent( - _LOGGER, DOMAIN, self.hass, timedelta(seconds=20)) - - update_ok = [] - update_err = [] - - def update_mock(): - """Mock normal update.""" - update_ok.append(None) - - def update_mock_err(): - """Mock error update.""" - update_err.append(None) - raise AssertionError("Fake error update") - - ent1 = MockEntity(should_poll=True) - ent1.update = update_mock_err - ent2 = MockEntity(should_poll=True) - ent2.update = update_mock - ent3 = MockEntity(should_poll=True) - ent3.update = update_mock - ent4 = MockEntity(should_poll=True) - ent4.update = update_mock - - component.add_entities([ent1, ent2, ent3, ent4]) - - update_ok.clear() - update_err.clear() - - fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=20)) - self.hass.block_till_done() - - assert len(update_ok) == 3 - assert len(update_err) == 1 - - def test_update_state_adds_entities(self): - """Test if updating poll entities cause an entity to be added works.""" - component = EntityComponent(_LOGGER, DOMAIN, self.hass) - - ent1 = MockEntity() - ent2 = MockEntity(should_poll=True) - - component.add_entities([ent2]) - assert 1 == len(self.hass.states.entity_ids()) - ent2.update = lambda *_: component.add_entities([ent1]) - - fire_time_changed( - self.hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL - ) - self.hass.block_till_done() - - assert 2 == len(self.hass.states.entity_ids()) - - def test_update_state_adds_entities_with_update_before_add_true(self): - """Test if call update before add to state machine.""" - component = EntityComponent(_LOGGER, DOMAIN, self.hass) - - ent = MockEntity() - ent.update = Mock(spec_set=True) - - component.add_entities([ent], True) - self.hass.block_till_done() - - assert 1 == len(self.hass.states.entity_ids()) - assert ent.update.called - - def test_update_state_adds_entities_with_update_before_add_false(self): - """Test if not call update before add to state machine.""" - component = EntityComponent(_LOGGER, DOMAIN, self.hass) - - ent = MockEntity() - ent.update = Mock(spec_set=True) - - component.add_entities([ent], False) - self.hass.block_till_done() - - assert 1 == len(self.hass.states.entity_ids()) - assert not ent.update.called - - @patch('homeassistant.helpers.entity_platform.' - 'async_track_time_interval') - def test_set_scan_interval_via_platform(self, mock_track): - """Test the setting of the scan interval via platform.""" - def platform_setup(hass, config, add_entities, discovery_info=None): - """Test the platform setup.""" - add_entities([MockEntity(should_poll=True)]) - - platform = MockPlatform(platform_setup) - platform.SCAN_INTERVAL = timedelta(seconds=30) - - mock_entity_platform(self.hass, 'test_domain.platform', platform) - - component = EntityComponent(_LOGGER, DOMAIN, self.hass) - - component.setup({ - DOMAIN: { - 'platform': 'platform', - } - }) - - self.hass.block_till_done() - assert mock_track.called - assert timedelta(seconds=30) == mock_track.call_args[0][2] - - def test_adding_entities_with_generator_and_thread_callback(self): - """Test generator in add_entities that calls thread method. - - We should make sure we resolve the generator to a list before passing - it into an async context. - """ - component = EntityComponent(_LOGGER, DOMAIN, self.hass) - - def create_entity(number): - """Create entity helper.""" - entity = MockEntity() - entity.entity_id = generate_entity_id(DOMAIN + '.{}', - 'Number', hass=self.hass) - return entity - - component.add_entities(create_entity(i) for i in range(2)) + assert not no_poll_ent.async_update.called + assert poll_ent.async_update.called -@asyncio.coroutine -def test_platform_warn_slow_setup(hass): +async def test_polling_updates_entities_with_exception(hass): + """Test the updated entities that not break with an exception.""" + component = EntityComponent( + _LOGGER, DOMAIN, hass, timedelta(seconds=20)) + + update_ok = [] + update_err = [] + + def update_mock(): + """Mock normal update.""" + update_ok.append(None) + + def update_mock_err(): + """Mock error update.""" + update_err.append(None) + raise AssertionError("Fake error update") + + ent1 = MockEntity(should_poll=True) + ent1.update = update_mock_err + ent2 = MockEntity(should_poll=True) + ent2.update = update_mock + ent3 = MockEntity(should_poll=True) + ent3.update = update_mock + ent4 = MockEntity(should_poll=True) + ent4.update = update_mock + + await component.async_add_entities([ent1, ent2, ent3, ent4]) + + update_ok.clear() + update_err.clear() + + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20)) + await hass.async_block_till_done() + + assert len(update_ok) == 3 + assert len(update_err) == 1 + + +async def test_update_state_adds_entities(hass): + """Test if updating poll entities cause an entity to be added works.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + + ent1 = MockEntity() + ent2 = MockEntity(should_poll=True) + + await component.async_add_entities([ent2]) + assert len(hass.states.async_entity_ids()) == 1 + ent2.update = lambda *_: component.add_entities([ent1]) + + async_fire_time_changed( + hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL + ) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids()) == 2 + + +async def test_update_state_adds_entities_with_update_before_add_true(hass): + """Test if call update before add to state machine.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + + ent = MockEntity() + ent.update = Mock(spec_set=True) + + await component.async_add_entities([ent], True) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids()) == 1 + assert ent.update.called + + +async def test_update_state_adds_entities_with_update_before_add_false(hass): + """Test if not call update before add to state machine.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + + ent = MockEntity() + ent.update = Mock(spec_set=True) + + await component.async_add_entities([ent], False) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids()) == 1 + assert not ent.update.called + + +@asynctest.patch('homeassistant.helpers.entity_platform.' + 'async_track_time_interval') +async def test_set_scan_interval_via_platform(mock_track, hass): + """Test the setting of the scan interval via platform.""" + def platform_setup(hass, config, add_entities, discovery_info=None): + """Test the platform setup.""" + add_entities([MockEntity(should_poll=True)]) + + platform = MockPlatform(platform_setup) + platform.SCAN_INTERVAL = timedelta(seconds=30) + + mock_entity_platform(hass, 'test_domain.platform', platform) + + component = EntityComponent(_LOGGER, DOMAIN, hass) + + component.setup({ + DOMAIN: { + 'platform': 'platform', + } + }) + + await hass.async_block_till_done() + assert mock_track.called + assert timedelta(seconds=30) == mock_track.call_args[0][2] + + +async def test_adding_entities_with_generator_and_thread_callback(hass): + """Test generator in add_entities that calls thread method. + + We should make sure we resolve the generator to a list before passing + it into an async context. + """ + component = EntityComponent(_LOGGER, DOMAIN, hass) + + def create_entity(number): + """Create entity helper.""" + entity = MockEntity() + entity.entity_id = async_generate_entity_id(DOMAIN + '.{}', + 'Number', hass=hass) + return entity + + await component.async_add_entities(create_entity(i) for i in range(2)) + + +async def test_platform_warn_slow_setup(hass): """Warn we log when platform setup takes a long time.""" platform = MockPlatform() @@ -191,7 +185,7 @@ def test_platform_warn_slow_setup(hass): with patch.object(hass.loop, 'call_later', MagicMock()) \ as mock_call: - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'platform', } @@ -208,21 +202,19 @@ def test_platform_warn_slow_setup(hass): assert mock_call().cancel.called -@asyncio.coroutine -def test_platform_error_slow_setup(hass, caplog): +async def test_platform_error_slow_setup(hass, caplog): """Don't block startup more than SLOW_SETUP_MAX_WAIT.""" with patch.object(entity_platform, 'SLOW_SETUP_MAX_WAIT', 0): called = [] - @asyncio.coroutine - def setup_platform(*args): + async def setup_platform(*args): called.append(1) - yield from asyncio.sleep(1, loop=hass.loop) + await asyncio.sleep(1, loop=hass.loop) platform = MockPlatform(async_setup_platform=setup_platform) component = EntityComponent(_LOGGER, DOMAIN, hass) mock_entity_platform(hass, 'test_domain.test_platform', platform) - yield from component.async_setup({ + await component.async_setup({ DOMAIN: { 'platform': 'test_platform', } @@ -232,23 +224,21 @@ def test_platform_error_slow_setup(hass, caplog): assert 'test_platform is taking longer than 0 seconds' in caplog.text -@asyncio.coroutine -def test_updated_state_used_for_entity_id(hass): +async def test_updated_state_used_for_entity_id(hass): """Test that first update results used for entity ID generation.""" component = EntityComponent(_LOGGER, DOMAIN, hass) class MockEntityNameFetcher(MockEntity): """Mock entity that fetches a friendly name.""" - @asyncio.coroutine - def async_update(self): + async def async_update(self): """Mock update that assigns a name.""" self._values['name'] = "Living Room" - yield from component.async_add_entities([MockEntityNameFetcher()], True) + await component.async_add_entities([MockEntityNameFetcher()], True) entity_ids = hass.states.async_entity_ids() - assert 1 == len(entity_ids) + assert len(entity_ids) == 1 assert entity_ids[0] == "test_domain.living_room" @@ -374,8 +364,7 @@ async def test_parallel_updates_sync_platform_with_constant(hass): assert entity.parallel_updates._value == 2 -@asyncio.coroutine -def test_raise_error_on_update(hass): +async def test_raise_error_on_update(hass): """Test the add entity if they raise an error on update.""" updates = [] component = EntityComponent(_LOGGER, DOMAIN, hass) @@ -389,63 +378,58 @@ def test_raise_error_on_update(hass): entity1.update = _raise entity2.update = lambda: updates.append(1) - yield from component.async_add_entities([entity1, entity2], True) + await component.async_add_entities([entity1, entity2], True) assert len(updates) == 1 assert 1 in updates -@asyncio.coroutine -def test_async_remove_with_platform(hass): +async def test_async_remove_with_platform(hass): """Remove an entity from a platform.""" component = EntityComponent(_LOGGER, DOMAIN, hass) entity1 = MockEntity(name='test_1') - yield from component.async_add_entities([entity1]) + await component.async_add_entities([entity1]) assert len(hass.states.async_entity_ids()) == 1 - yield from entity1.async_remove() + await entity1.async_remove() assert len(hass.states.async_entity_ids()) == 0 -@asyncio.coroutine -def test_not_adding_duplicate_entities_with_unique_id(hass): +async def test_not_adding_duplicate_entities_with_unique_id(hass): """Test for not adding duplicate entities.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(name='test1', unique_id='not_very_unique')]) assert len(hass.states.async_entity_ids()) == 1 - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(name='test2', unique_id='not_very_unique')]) assert len(hass.states.async_entity_ids()) == 1 -@asyncio.coroutine -def test_using_prescribed_entity_id(hass): +async def test_using_prescribed_entity_id(hass): """Test for using predefined entity ID.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(name='bla', entity_id='hello.world')]) assert 'hello.world' in hass.states.async_entity_ids() -@asyncio.coroutine -def test_using_prescribed_entity_id_with_unique_id(hass): +async def test_using_prescribed_entity_id_with_unique_id(hass): """Test for ammending predefined entity ID because currently exists.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(entity_id='test_domain.world')]) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(entity_id='test_domain.world', unique_id='bla')]) assert 'test_domain.world_2' in hass.states.async_entity_ids() -@asyncio.coroutine -def test_using_prescribed_entity_id_which_is_registered(hass): +async def test_using_prescribed_entity_id_which_is_registered(hass): """Test not allowing predefined entity ID that already registered.""" component = EntityComponent(_LOGGER, DOMAIN, hass) registry = mock_registry(hass) @@ -454,14 +438,13 @@ def test_using_prescribed_entity_id_which_is_registered(hass): DOMAIN, 'test', '1234', suggested_object_id='world') # This entity_id will be rewritten - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(entity_id='test_domain.world')]) assert 'test_domain.world_2' in hass.states.async_entity_ids() -@asyncio.coroutine -def test_name_which_conflict_with_registered(hass): +async def test_name_which_conflict_with_registered(hass): """Test not generating conflicting entity ID based on name.""" component = EntityComponent(_LOGGER, DOMAIN, hass) registry = mock_registry(hass) @@ -470,24 +453,22 @@ def test_name_which_conflict_with_registered(hass): registry.async_get_or_create( DOMAIN, 'test', '1234', suggested_object_id='world') - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(name='world')]) assert 'test_domain.world_2' in hass.states.async_entity_ids() -@asyncio.coroutine -def test_entity_with_name_and_entity_id_getting_registered(hass): +async def test_entity_with_name_and_entity_id_getting_registered(hass): """Ensure that entity ID is used for registration.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(unique_id='1234', name='bla', entity_id='test_domain.world')]) assert 'test_domain.world' in hass.states.async_entity_ids() -@asyncio.coroutine -def test_overriding_name_from_registry(hass): +async def test_overriding_name_from_registry(hass): """Test that we can override a name via the Entity Registry.""" component = EntityComponent(_LOGGER, DOMAIN, hass) mock_registry(hass, { @@ -499,7 +480,7 @@ def test_overriding_name_from_registry(hass): name='Overridden' ) }) - yield from component.async_add_entities([ + await component.async_add_entities([ MockEntity(unique_id='1234', name='Device Name')]) state = hass.states.get('test_domain.world') @@ -507,18 +488,16 @@ def test_overriding_name_from_registry(hass): assert state.name == 'Overridden' -@asyncio.coroutine -def test_registry_respect_entity_namespace(hass): +async def test_registry_respect_entity_namespace(hass): """Test that the registry respects entity namespace.""" mock_registry(hass) platform = MockEntityPlatform(hass, entity_namespace='ns') entity = MockEntity(unique_id='1234', name='Device Name') - yield from platform.async_add_entities([entity]) + await platform.async_add_entities([entity]) assert entity.entity_id == 'test_domain.ns_device_name' -@asyncio.coroutine -def test_registry_respect_entity_disabled(hass): +async def test_registry_respect_entity_disabled(hass): """Test that the registry respects entity disabled.""" mock_registry(hass, { 'test_domain.world': entity_registry.RegistryEntry( @@ -531,7 +510,7 @@ def test_registry_respect_entity_disabled(hass): }) platform = MockEntityPlatform(hass) entity = MockEntity(unique_id='1234') - yield from platform.async_add_entities([entity]) + await platform.async_add_entities([entity]) assert entity.entity_id is None assert hass.states.async_entity_ids() == [] @@ -643,12 +622,11 @@ async def test_reset_cancels_retry_setup(hass): assert ent_platform._async_cancel_retry_setup is None -@asyncio.coroutine -def test_not_fails_with_adding_empty_entities_(hass): +async def test_not_fails_with_adding_empty_entities_(hass): """Test for not fails on empty entities list.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - yield from component.async_add_entities([]) + await component.async_add_entities([]) assert len(hass.states.async_entity_ids()) == 0 diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index dd5744bbb52..0756bab2eec 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -1,763 +1,752 @@ """Test event helpers.""" # pylint: disable=protected-access import asyncio -import unittest from datetime import datetime, timedelta +from unittest.mock import patch from astral import Astral import pytest from homeassistant.core import callback -from homeassistant.setup import setup_component +from homeassistant.setup import async_setup_component import homeassistant.core as ha from homeassistant.const import MATCH_ALL from homeassistant.helpers.event import ( async_call_later, - call_later, - track_point_in_utc_time, - track_point_in_time, - track_utc_time_change, - track_time_change, - track_state_change, - track_time_interval, - track_template, - track_same_state, - track_sunrise, - track_sunset, + async_track_point_in_time, + async_track_point_in_utc_time, + async_track_same_state, + async_track_state_change, + async_track_sunrise, + async_track_sunset, + async_track_template, + async_track_time_change, + async_track_time_interval, + async_track_utc_time_change, ) from homeassistant.helpers.template import Template from homeassistant.components import sun import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant, fire_time_changed -from unittest.mock import patch - - -class TestEventHelpers(unittest.TestCase): - """Test the Home Assistant event helpers.""" - - # pylint: disable=invalid-name - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - - # pylint: disable=invalid-name - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_track_point_in_time(self): - """Test track point in time.""" - before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) - birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) - after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) - - runs = [] - - track_point_in_utc_time( - self.hass, callback(lambda x: runs.append(1)), birthday_paulus) - - self._send_time_changed(before_birthday) - self.hass.block_till_done() - assert 0 == len(runs) - - self._send_time_changed(birthday_paulus) - self.hass.block_till_done() - assert 1 == len(runs) - - # A point in time tracker will only fire once, this should do nothing - self._send_time_changed(birthday_paulus) - self.hass.block_till_done() - assert 1 == len(runs) - - track_point_in_time( - self.hass, callback(lambda x: runs.append(1)), birthday_paulus) - - self._send_time_changed(after_birthday) - self.hass.block_till_done() - assert 2 == len(runs) - - unsub = track_point_in_time( - self.hass, callback(lambda x: runs.append(1)), birthday_paulus) - unsub() - - self._send_time_changed(after_birthday) - self.hass.block_till_done() - assert 2 == len(runs) - - def test_track_state_change(self): - """Test track_state_change.""" - # 2 lists to track how often our callbacks get called - specific_runs = [] - wildcard_runs = [] - wildercard_runs = [] - - def specific_run_callback(entity_id, old_state, new_state): - specific_runs.append(1) - - track_state_change( - self.hass, 'light.Bowl', specific_run_callback, 'on', 'off') - - @ha.callback - def wildcard_run_callback(entity_id, old_state, new_state): - wildcard_runs.append((old_state, new_state)) - - track_state_change(self.hass, 'light.Bowl', wildcard_run_callback) - - @asyncio.coroutine - def wildercard_run_callback(entity_id, old_state, new_state): - wildercard_runs.append((old_state, new_state)) - - track_state_change(self.hass, MATCH_ALL, wildercard_run_callback) - - # Adding state to state machine - self.hass.states.set("light.Bowl", "on") - self.hass.block_till_done() - assert 0 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - assert wildcard_runs[-1][0] is None - assert wildcard_runs[-1][1] is not None - - # Set same state should not trigger a state change/listener - self.hass.states.set('light.Bowl', 'on') - self.hass.block_till_done() - assert 0 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - - # State change off -> on - self.hass.states.set('light.Bowl', 'off') - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 2 == len(wildcard_runs) - assert 2 == len(wildercard_runs) - - # State change off -> off - self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 3 == len(wildcard_runs) - assert 3 == len(wildercard_runs) - - # State change off -> on - self.hass.states.set('light.Bowl', 'on') - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 4 == len(wildcard_runs) - assert 4 == len(wildercard_runs) - - self.hass.states.remove('light.bowl') - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 5 == len(wildcard_runs) - assert 5 == len(wildercard_runs) - assert wildcard_runs[-1][0] is not None - assert wildcard_runs[-1][1] is None - assert wildercard_runs[-1][0] is not None - assert wildercard_runs[-1][1] is None - - # Set state for different entity id - self.hass.states.set('switch.kitchen', 'on') - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 5 == len(wildcard_runs) - assert 6 == len(wildercard_runs) - - def test_track_template(self): - """Test tracking template.""" - specific_runs = [] - wildcard_runs = [] - wildercard_runs = [] - - template_condition = Template( - "{{states.switch.test.state == 'on'}}", - self.hass - ) - template_condition_var = Template( - "{{states.switch.test.state == 'on' and test == 5}}", - self.hass - ) - - self.hass.states.set('switch.test', 'off') - - def specific_run_callback(entity_id, old_state, new_state): - specific_runs.append(1) - - track_template(self.hass, template_condition, specific_run_callback) - - @ha.callback - def wildcard_run_callback(entity_id, old_state, new_state): - wildcard_runs.append((old_state, new_state)) - - track_template(self.hass, template_condition, wildcard_run_callback) - - @asyncio.coroutine - def wildercard_run_callback(entity_id, old_state, new_state): - wildercard_runs.append((old_state, new_state)) - - track_template( - self.hass, template_condition_var, wildercard_run_callback, - {'test': 5}) - - self.hass.states.set('switch.test', 'on') - self.hass.block_till_done() - - assert 1 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - - self.hass.states.set('switch.test', 'on') - self.hass.block_till_done() - - assert 1 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - - self.hass.states.set('switch.test', 'off') - self.hass.block_till_done() - - assert 1 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - - self.hass.states.set('switch.test', 'off') - self.hass.block_till_done() - - assert 1 == len(specific_runs) - assert 1 == len(wildcard_runs) - assert 1 == len(wildercard_runs) - - self.hass.states.set('switch.test', 'on') - self.hass.block_till_done() - - assert 2 == len(specific_runs) - assert 2 == len(wildcard_runs) - assert 2 == len(wildercard_runs) - - def test_track_same_state_simple_trigger(self): - """Test track_same_change with trigger simple.""" - thread_runs = [] - callback_runs = [] - coroutine_runs = [] - period = timedelta(minutes=1) - - def thread_run_callback(): - thread_runs.append(1) - - track_same_state( - self.hass, period, thread_run_callback, - lambda _, _2, to_s: to_s.state == 'on', - entity_ids='light.Bowl') - - @ha.callback - def callback_run_callback(): - callback_runs.append(1) - - track_same_state( - self.hass, period, callback_run_callback, - lambda _, _2, to_s: to_s.state == 'on', - entity_ids='light.Bowl') - - @asyncio.coroutine - def coroutine_run_callback(): - coroutine_runs.append(1) - - track_same_state( - self.hass, period, coroutine_run_callback, - lambda _, _2, to_s: to_s.state == 'on') - - # Adding state to state machine - self.hass.states.set("light.Bowl", "on") - self.hass.block_till_done() - assert 0 == len(thread_runs) - assert 0 == len(callback_runs) - assert 0 == len(coroutine_runs) - - # change time to track and see if they trigger - future = dt_util.utcnow() + period - fire_time_changed(self.hass, future) - self.hass.block_till_done() - assert 1 == len(thread_runs) - assert 1 == len(callback_runs) - assert 1 == len(coroutine_runs) - - def test_track_same_state_simple_no_trigger(self): - """Test track_same_change with no trigger.""" - callback_runs = [] - period = timedelta(minutes=1) - - @ha.callback - def callback_run_callback(): - callback_runs.append(1) - - track_same_state( - self.hass, period, callback_run_callback, - lambda _, _2, to_s: to_s.state == 'on', - entity_ids='light.Bowl') - - # Adding state to state machine - self.hass.states.set("light.Bowl", "on") - self.hass.block_till_done() - assert 0 == len(callback_runs) - - # Change state on state machine - self.hass.states.set("light.Bowl", "off") - self.hass.block_till_done() - assert 0 == len(callback_runs) - - # change time to track and see if they trigger - future = dt_util.utcnow() + period - fire_time_changed(self.hass, future) - self.hass.block_till_done() - assert 0 == len(callback_runs) - - def test_track_same_state_simple_trigger_check_funct(self): - """Test track_same_change with trigger and check funct.""" - callback_runs = [] - check_func = [] - period = timedelta(minutes=1) - - @ha.callback - def callback_run_callback(): - callback_runs.append(1) - - @ha.callback - def async_check_func(entity, from_s, to_s): - check_func.append((entity, from_s, to_s)) - return True - - track_same_state( - self.hass, period, callback_run_callback, - entity_ids='light.Bowl', async_check_same_func=async_check_func) - - # Adding state to state machine - self.hass.states.set("light.Bowl", "on") - self.hass.block_till_done() - assert 0 == len(callback_runs) - assert 'on' == check_func[-1][2].state - assert 'light.bowl' == check_func[-1][0] - - # change time to track and see if they trigger - future = dt_util.utcnow() + period - fire_time_changed(self.hass, future) - self.hass.block_till_done() - assert 1 == len(callback_runs) - - def test_track_time_interval(self): - """Test tracking time interval.""" - specific_runs = [] - - utc_now = dt_util.utcnow() - unsub = track_time_interval( - self.hass, lambda x: specific_runs.append(1), - timedelta(seconds=10) - ) - - self._send_time_changed(utc_now + timedelta(seconds=5)) - self.hass.block_till_done() - assert 0 == len(specific_runs) - - self._send_time_changed(utc_now + timedelta(seconds=13)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed(utc_now + timedelta(minutes=20)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - - unsub() - - self._send_time_changed(utc_now + timedelta(seconds=30)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - - def test_track_sunrise(self): - """Test track the sunrise.""" - latitude = 32.87336 - longitude = 117.22743 - - # Setup sun component - self.hass.config.latitude = latitude - self.hass.config.longitude = longitude - setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) - - # Get next sunrise/sunset - astral = Astral() - utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) - utc_today = utc_now.date() - - mod = -1 - while True: - next_rising = (astral.sunrise_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_rising > utc_now: - break - mod += 1 - - # Track sunrise - runs = [] - with patch('homeassistant.util.dt.utcnow', return_value=utc_now): - unsub = track_sunrise(self.hass, lambda: runs.append(1)) - - offset_runs = [] - offset = timedelta(minutes=30) - with patch('homeassistant.util.dt.utcnow', return_value=utc_now): - unsub2 = track_sunrise(self.hass, lambda: offset_runs.append(1), - offset) - - # run tests - self._send_time_changed(next_rising - offset) - self.hass.block_till_done() - assert 0 == len(runs) - assert 0 == len(offset_runs) - - self._send_time_changed(next_rising) - self.hass.block_till_done() - assert 1 == len(runs) - assert 0 == len(offset_runs) - - self._send_time_changed(next_rising + offset) - self.hass.block_till_done() - assert 1 == len(runs) - assert 1 == len(offset_runs) - - unsub() - unsub2() - - self._send_time_changed(next_rising + offset) - self.hass.block_till_done() - assert 1 == len(runs) - assert 1 == len(offset_runs) - - def test_track_sunset(self): - """Test track the sunset.""" - latitude = 32.87336 - longitude = 117.22743 - - # Setup sun component - self.hass.config.latitude = latitude - self.hass.config.longitude = longitude - setup_component(self.hass, sun.DOMAIN, { - sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) - - # Get next sunrise/sunset - astral = Astral() - utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) - utc_today = utc_now.date() - - mod = -1 - while True: - next_setting = (astral.sunset_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_setting > utc_now: - break - mod += 1 - - # Track sunset - runs = [] - with patch('homeassistant.util.dt.utcnow', return_value=utc_now): - unsub = track_sunset(self.hass, lambda: runs.append(1)) - - offset_runs = [] - offset = timedelta(minutes=30) - with patch('homeassistant.util.dt.utcnow', return_value=utc_now): - unsub2 = track_sunset( - self.hass, lambda: offset_runs.append(1), offset) - - # Run tests - self._send_time_changed(next_setting - offset) - self.hass.block_till_done() - assert 0 == len(runs) - assert 0 == len(offset_runs) - - self._send_time_changed(next_setting) - self.hass.block_till_done() - assert 1 == len(runs) - assert 0 == len(offset_runs) - - self._send_time_changed(next_setting + offset) - self.hass.block_till_done() - assert 1 == len(runs) - assert 1 == len(offset_runs) - - unsub() - unsub2() - - self._send_time_changed(next_setting + offset) - self.hass.block_till_done() - assert 1 == len(runs) - assert 1 == len(offset_runs) - - def _send_time_changed(self, now): - """Send a time changed event.""" - self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) - - -class TestTrackTimeChange(unittest.TestCase): - """Test track time change methods.""" - - def setUp(self): - """Set up the tests.""" - self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE - self.hass = get_test_home_assistant() - - def tearDown(self): - """Stop everything that was started.""" - dt_util.set_default_time_zone(self.orig_default_time_zone) - self.hass.stop() - - def _send_time_changed(self, now): - """Send a time changed event.""" - self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) - - def test_track_time_change(self): - """Test tracking time change.""" - wildcard_runs = [] - specific_runs = [] - - unsub = track_time_change(self.hass, - lambda x: wildcard_runs.append(1)) - unsub_utc = track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), second=[0, 30]) - - self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 1 == len(wildcard_runs) - - self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - assert 2 == len(wildcard_runs) - - self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - assert 3 == len(wildcard_runs) - - unsub() - unsub_utc() - - self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - assert 3 == len(wildcard_runs) - - def test_periodic_task_minute(self): - """Test periodic tasks per minute.""" - specific_runs = [] - - unsub = track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), minute='/5', - second=0) - - self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed(datetime(2014, 5, 24, 12, 3, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) +from tests.common import async_fire_time_changed - unsub() +DEFAULT_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE - self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - def test_periodic_task_hour(self): - """Test periodic tasks per hour.""" - specific_runs = [] +def teardown(): + """Stop everything that was started.""" + dt_util.set_default_time_zone(DEFAULT_TIME_ZONE) - unsub = track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), hour='/2', - minute=0, second=0) - self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) +def _send_time_changed(hass, now): + """Send a time changed event.""" + hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now}) - self._send_time_changed(datetime(2014, 5, 24, 23, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - self._send_time_changed(datetime(2014, 5, 25, 0, 0, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) +async def test_track_point_in_time(hass): + """Test track point in time.""" + before_birthday = datetime(1985, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + birthday_paulus = datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) + after_birthday = datetime(1987, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) - self._send_time_changed(datetime(2014, 5, 25, 1, 0, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) + runs = [] - self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.block_till_done() - assert 3 == len(specific_runs) - - unsub() - - self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.block_till_done() - assert 3 == len(specific_runs) + async_track_point_in_utc_time( + hass, callback(lambda x: runs.append(1)), birthday_paulus) + + _send_time_changed(hass, before_birthday) + await hass.async_block_till_done() + assert len(runs) == 0 + + _send_time_changed(hass, birthday_paulus) + await hass.async_block_till_done() + assert len(runs) == 1 + + # A point in time tracker will only fire once, this should do nothing + _send_time_changed(hass, birthday_paulus) + await hass.async_block_till_done() + assert len(runs) == 1 + + async_track_point_in_utc_time( + hass, callback(lambda x: runs.append(1)), birthday_paulus) + + _send_time_changed(hass, after_birthday) + await hass.async_block_till_done() + assert len(runs) == 2 + + unsub = async_track_point_in_time( + hass, callback(lambda x: runs.append(1)), birthday_paulus) + unsub() + + _send_time_changed(hass, after_birthday) + await hass.async_block_till_done() + assert len(runs) == 2 + + +async def test_track_state_change(hass): + """Test track_state_change.""" + # 2 lists to track how often our callbacks get called + specific_runs = [] + wildcard_runs = [] + wildercard_runs = [] + + def specific_run_callback(entity_id, old_state, new_state): + specific_runs.append(1) + + async_track_state_change( + hass, 'light.Bowl', specific_run_callback, 'on', 'off') + + @ha.callback + def wildcard_run_callback(entity_id, old_state, new_state): + wildcard_runs.append((old_state, new_state)) + + async_track_state_change(hass, 'light.Bowl', wildcard_run_callback) - def test_periodic_task_wrong_input(self): - """Test periodic tasks with wrong input.""" - specific_runs = [] - - with pytest.raises(ValueError): - track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), hour='/two') - - self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) - self.hass.block_till_done() - assert 0 == len(specific_runs) + @asyncio.coroutine + def wildercard_run_callback(entity_id, old_state, new_state): + wildercard_runs.append((old_state, new_state)) - def test_periodic_task_clock_rollback(self): - """Test periodic tasks with the time rolling backwards.""" - specific_runs = [] + async_track_state_change(hass, MATCH_ALL, wildercard_run_callback) + + # Adding state to state machine + hass.states.async_set("light.Bowl", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 0 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 + assert wildcard_runs[-1][0] is None + assert wildcard_runs[-1][1] is not None - unsub = track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), hour='/2', minute=0, - second=0) + # Set same state should not trigger a state change/listener + hass.states.async_set('light.Bowl', 'on') + await hass.async_block_till_done() + assert len(specific_runs) == 0 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 - self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed(datetime(2014, 5, 24, 23, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) + # State change off -> on + hass.states.async_set('light.Bowl', 'off') + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 2 + assert len(wildercard_runs) == 2 - self._send_time_changed(datetime(2014, 5, 24, 0, 0, 0)) - self.hass.block_till_done() - assert 3 == len(specific_runs) - - self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.block_till_done() - assert 4 == len(specific_runs) + # State change off -> off + hass.states.async_set('light.Bowl', 'off', {"some_attr": 1}) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 3 + assert len(wildercard_runs) == 3 + + # State change off -> on + hass.states.async_set('light.Bowl', 'on') + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 4 + assert len(wildercard_runs) == 4 + + hass.states.async_remove('light.bowl') + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 5 + assert len(wildercard_runs) == 5 + assert wildcard_runs[-1][0] is not None + assert wildcard_runs[-1][1] is None + assert wildercard_runs[-1][0] is not None + assert wildercard_runs[-1][1] is None + + # Set state for different entity id + hass.states.async_set('switch.kitchen', 'on') + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 5 + assert len(wildercard_runs) == 6 + + +async def test_track_template(hass): + """Test tracking template.""" + specific_runs = [] + wildcard_runs = [] + wildercard_runs = [] + + template_condition = Template( + "{{states.switch.test.state == 'on'}}", + hass + ) + template_condition_var = Template( + "{{states.switch.test.state == 'on' and test == 5}}", + hass + ) + + hass.states.async_set('switch.test', 'off') + + def specific_run_callback(entity_id, old_state, new_state): + specific_runs.append(1) + + async_track_template(hass, template_condition, specific_run_callback) + + @ha.callback + def wildcard_run_callback(entity_id, old_state, new_state): + wildcard_runs.append((old_state, new_state)) + + async_track_template(hass, template_condition, wildcard_run_callback) + + @asyncio.coroutine + def wildercard_run_callback(entity_id, old_state, new_state): + wildercard_runs.append((old_state, new_state)) + + async_track_template( + hass, template_condition_var, wildercard_run_callback, + {'test': 5}) + + hass.states.async_set('switch.test', 'on') + await hass.async_block_till_done() + + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 + + hass.states.async_set('switch.test', 'on') + await hass.async_block_till_done() + + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 + + hass.states.async_set('switch.test', 'off') + await hass.async_block_till_done() + + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 + + hass.states.async_set('switch.test', 'off') + await hass.async_block_till_done() + + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 1 + assert len(wildercard_runs) == 1 + + hass.states.async_set('switch.test', 'on') + await hass.async_block_till_done() + + assert len(specific_runs) == 2 + assert len(wildcard_runs) == 2 + assert len(wildercard_runs) == 2 + + +async def test_track_same_state_simple_trigger(hass): + """Test track_same_change with trigger simple.""" + thread_runs = [] + callback_runs = [] + coroutine_runs = [] + period = timedelta(minutes=1) + + def thread_run_callback(): + thread_runs.append(1) + + async_track_same_state( + hass, period, thread_run_callback, + lambda _, _2, to_s: to_s.state == 'on', + entity_ids='light.Bowl') + + @ha.callback + def callback_run_callback(): + callback_runs.append(1) + + async_track_same_state( + hass, period, callback_run_callback, + lambda _, _2, to_s: to_s.state == 'on', + entity_ids='light.Bowl') + + @asyncio.coroutine + def coroutine_run_callback(): + coroutine_runs.append(1) + + async_track_same_state( + hass, period, coroutine_run_callback, + lambda _, _2, to_s: to_s.state == 'on') + + # Adding state to state machine + hass.states.async_set("light.Bowl", "on") + await hass.async_block_till_done() + assert len(thread_runs) == 0 + assert len(callback_runs) == 0 + assert len(coroutine_runs) == 0 + + # change time to track and see if they trigger + future = dt_util.utcnow() + period + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert len(thread_runs) == 1 + assert len(callback_runs) == 1 + assert len(coroutine_runs) == 1 - unsub() - self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.block_till_done() - assert 4 == len(specific_runs) - - def test_periodic_task_duplicate_time(self): - """Test periodic tasks not triggering on duplicate time.""" - specific_runs = [] +async def test_track_same_state_simple_no_trigger(hass): + """Test track_same_change with no trigger.""" + callback_runs = [] + period = timedelta(minutes=1) + + @ha.callback + def callback_run_callback(): + callback_runs.append(1) + + async_track_same_state( + hass, period, callback_run_callback, + lambda _, _2, to_s: to_s.state == 'on', + entity_ids='light.Bowl') + + # Adding state to state machine + hass.states.async_set("light.Bowl", "on") + await hass.async_block_till_done() + assert len(callback_runs) == 0 + + # Change state on state machine + hass.states.async_set("light.Bowl", "off") + await hass.async_block_till_done() + assert len(callback_runs) == 0 + + # change time to track and see if they trigger + future = dt_util.utcnow() + period + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert len(callback_runs) == 0 + + +async def test_track_same_state_simple_trigger_check_funct(hass): + """Test track_same_change with trigger and check funct.""" + callback_runs = [] + check_func = [] + period = timedelta(minutes=1) + + @ha.callback + def callback_run_callback(): + callback_runs.append(1) + + @ha.callback + def async_check_func(entity, from_s, to_s): + check_func.append((entity, from_s, to_s)) + return True + + async_track_same_state( + hass, period, callback_run_callback, + entity_ids='light.Bowl', async_check_same_func=async_check_func) + + # Adding state to state machine + hass.states.async_set("light.Bowl", "on") + await hass.async_block_till_done() + assert len(callback_runs) == 0 + assert check_func[-1][2].state == 'on' + assert check_func[-1][0] == 'light.bowl' + + # change time to track and see if they trigger + future = dt_util.utcnow() + period + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + assert len(callback_runs) == 1 + + +async def test_track_time_interval(hass): + """Test tracking time interval.""" + specific_runs = [] + + utc_now = dt_util.utcnow() + unsub = async_track_time_interval( + hass, lambda x: specific_runs.append(1), + timedelta(seconds=10) + ) + + _send_time_changed(hass, utc_now + timedelta(seconds=5)) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + _send_time_changed(hass, utc_now + timedelta(seconds=13)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, utc_now + timedelta(minutes=20)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + unsub() + + _send_time_changed(hass, utc_now + timedelta(seconds=30)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + +async def test_track_sunrise(hass): + """Test track the sunrise.""" + latitude = 32.87336 + longitude = 117.22743 + + # Setup sun component + hass.config.latitude = latitude + hass.config.longitude = longitude + assert await async_setup_component(hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) + + # Get next sunrise/sunset + astral = Astral() + utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) + utc_today = utc_now.date() + + mod = -1 + while True: + next_rising = (astral.sunrise_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_rising > utc_now: + break + mod += 1 + + # Track sunrise + runs = [] + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub = async_track_sunrise(hass, lambda: runs.append(1)) + + offset_runs = [] + offset = timedelta(minutes=30) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub2 = async_track_sunrise(hass, lambda: offset_runs.append(1), + offset) + + # run tests + _send_time_changed(hass, next_rising - offset) + await hass.async_block_till_done() + assert len(runs) == 0 + assert len(offset_runs) == 0 + + _send_time_changed(hass, next_rising) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 0 + + _send_time_changed(hass, next_rising + offset) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 1 + + unsub() + unsub2() + + _send_time_changed(hass, next_rising + offset) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 1 + + +async def test_track_sunset(hass): + """Test track the sunset.""" + latitude = 32.87336 + longitude = 117.22743 + + # Setup sun component + hass.config.latitude = latitude + hass.config.longitude = longitude + assert await async_setup_component(hass, sun.DOMAIN, { + sun.DOMAIN: {sun.CONF_ELEVATION: 0}}) + + # Get next sunrise/sunset + astral = Astral() + utc_now = datetime(2014, 5, 24, 12, 0, 0, tzinfo=dt_util.UTC) + utc_today = utc_now.date() + + mod = -1 + while True: + next_setting = (astral.sunset_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_setting > utc_now: + break + mod += 1 + + # Track sunset + runs = [] + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub = async_track_sunset(hass, lambda: runs.append(1)) + + offset_runs = [] + offset = timedelta(minutes=30) + with patch('homeassistant.util.dt.utcnow', return_value=utc_now): + unsub2 = async_track_sunset( + hass, lambda: offset_runs.append(1), offset) + + # Run tests + _send_time_changed(hass, next_setting - offset) + await hass.async_block_till_done() + assert len(runs) == 0 + assert len(offset_runs) == 0 + + _send_time_changed(hass, next_setting) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 0 + + _send_time_changed(hass, next_setting + offset) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 1 + + unsub() + unsub2() + + _send_time_changed(hass, next_setting + offset) + await hass.async_block_till_done() + assert len(runs) == 1 + assert len(offset_runs) == 1 + + +async def test_async_track_time_change(hass): + """Test tracking time change.""" + wildcard_runs = [] + specific_runs = [] + + unsub = async_track_time_change(hass, + lambda x: wildcard_runs.append(1)) + unsub_utc = async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), second=[0, 30]) + + _send_time_changed(hass, datetime(2014, 5, 24, 12, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 1 - unsub = track_utc_time_change( - self.hass, lambda x: specific_runs.append(1), hour='/2', minute=0, - second=0) + _send_time_changed(hass, datetime(2014, 5, 24, 12, 0, 15)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert len(wildcard_runs) == 2 - self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) + _send_time_changed(hass, datetime(2014, 5, 24, 12, 0, 30)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + assert len(wildcard_runs) == 3 - self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.block_till_done() - assert 1 == len(specific_runs) + unsub() + unsub_utc() - self._send_time_changed(datetime(2014, 5, 25, 0, 0, 0)) - self.hass.block_till_done() - assert 2 == len(specific_runs) + _send_time_changed(hass, datetime(2014, 5, 24, 12, 0, 30)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + assert len(wildcard_runs) == 3 - unsub() - - def test_periodic_task_entering_dst(self): - """Test periodic task behavior when entering dst.""" - tz = dt_util.get_time_zone('Europe/Vienna') - dt_util.set_default_time_zone(tz) - specific_runs = [] - unsub = track_time_change( - self.hass, lambda x: specific_runs.append(1), hour=2, minute=30, - second=0) +async def test_periodic_task_minute(hass): + """Test periodic tasks per minute.""" + specific_runs = [] - self._send_time_changed( - tz.localize(datetime(2018, 3, 25, 1, 50, 0))) - self.hass.block_till_done() - assert 0 == len(specific_runs) - - self._send_time_changed( - tz.localize(datetime(2018, 3, 25, 3, 50, 0))) - self.hass.block_till_done() - assert 0 == len(specific_runs) - - self._send_time_changed( - tz.localize(datetime(2018, 3, 26, 1, 50, 0))) - self.hass.block_till_done() - assert 0 == len(specific_runs) + unsub = async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), minute='/5', + second=0) - self._send_time_changed( - tz.localize(datetime(2018, 3, 26, 2, 50, 0))) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - unsub() - - def test_periodic_task_leaving_dst(self): - """Test periodic task behavior when leaving dst.""" - tz = dt_util.get_time_zone('Europe/Vienna') - dt_util.set_default_time_zone(tz) - specific_runs = [] - - unsub = track_time_change( - self.hass, lambda x: specific_runs.append(1), hour=2, minute=30, - second=0) - - self._send_time_changed( - tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False)) - self.hass.block_till_done() - assert 0 == len(specific_runs) - - self._send_time_changed( - tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed( - tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True)) - self.hass.block_till_done() - assert 1 == len(specific_runs) - - self._send_time_changed( - tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True)) - self.hass.block_till_done() - assert 2 == len(specific_runs) - - unsub() - - def test_call_later(self): - """Test calling an action later.""" - def action(): pass - now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) - - with patch('homeassistant.helpers.event' - '.async_track_point_in_utc_time') as mock, \ - patch('homeassistant.util.dt.utcnow', return_value=now): - call_later(self.hass, 3, action) - - assert len(mock.mock_calls) == 1 - p_hass, p_action, p_point = mock.mock_calls[0][1] - assert p_hass is self.hass - assert p_action is action - assert p_point == now + timedelta(seconds=3) - - -@asyncio.coroutine -def test_async_call_later(hass): + _send_time_changed(hass, datetime(2014, 5, 24, 12, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 12, 3, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 12, 5, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + unsub() + + _send_time_changed(hass, datetime(2014, 5, 24, 12, 5, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + +async def test_periodic_task_hour(hass): + """Test periodic tasks per hour.""" + specific_runs = [] + + unsub = async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), hour='/2', + minute=0, second=0) + + _send_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 23, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 25, 0, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + _send_time_changed(hass, datetime(2014, 5, 25, 1, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + _send_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 3 + + unsub() + + _send_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 3 + + +async def test_periodic_task_wrong_input(hass): + """Test periodic tasks with wrong input.""" + specific_runs = [] + + with pytest.raises(ValueError): + async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), hour='/two') + + _send_time_changed(hass, datetime(2014, 5, 2, 0, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + +async def test_periodic_task_clock_rollback(hass): + """Test periodic tasks with the time rolling backwards.""" + specific_runs = [] + + unsub = async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), hour='/2', minute=0, + second=0) + + _send_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 23, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + _send_time_changed(hass, datetime(2014, 5, 24, 0, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 3 + + _send_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 4 + + unsub() + + _send_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 4 + + +async def test_periodic_task_duplicate_time(hass): + """Test periodic tasks not triggering on duplicate time.""" + specific_runs = [] + + unsub = async_track_utc_time_change( + hass, lambda x: specific_runs.append(1), hour='/2', minute=0, + second=0) + + _send_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, datetime(2014, 5, 25, 0, 0, 0)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + unsub() + + +async def test_periodic_task_entering_dst(hass): + """Test periodic task behavior when entering dst.""" + tz = dt_util.get_time_zone('Europe/Vienna') + dt_util.set_default_time_zone(tz) + specific_runs = [] + + unsub = async_track_time_change( + hass, lambda x: specific_runs.append(1), hour=2, minute=30, + second=0) + + _send_time_changed(hass, tz.localize(datetime(2018, 3, 25, 1, 50, 0))) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + _send_time_changed(hass, tz.localize(datetime(2018, 3, 25, 3, 50, 0))) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + _send_time_changed(hass, tz.localize(datetime(2018, 3, 26, 1, 50, 0))) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + _send_time_changed(hass, tz.localize(datetime(2018, 3, 26, 2, 50, 0))) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + unsub() + + +async def test_periodic_task_leaving_dst(hass): + """Test periodic task behavior when leaving dst.""" + tz = dt_util.get_time_zone('Europe/Vienna') + dt_util.set_default_time_zone(tz) + specific_runs = [] + + unsub = async_track_time_change( + hass, lambda x: specific_runs.append(1), hour=2, minute=30, + second=0) + + _send_time_changed(hass, tz.localize(datetime(2018, 10, 28, 2, 5, 0), + is_dst=False)) + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + _send_time_changed(hass, tz.localize(datetime(2018, 10, 28, 2, 55, 0), + is_dst=False)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, tz.localize(datetime(2018, 10, 28, 2, 5, 0), + is_dst=True)) + await hass.async_block_till_done() + assert len(specific_runs) == 1 + + _send_time_changed(hass, tz.localize(datetime(2018, 10, 28, 2, 55, 0), + is_dst=True)) + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + unsub() + + +async def test_call_later(hass): """Test calling an action later.""" - def action(): pass + def action(): + pass + now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) + + with patch('homeassistant.helpers.event' + '.async_track_point_in_utc_time') as mock, \ + patch('homeassistant.util.dt.utcnow', return_value=now): + async_call_later(hass, 3, action) + + assert len(mock.mock_calls) == 1 + p_hass, p_action, p_point = mock.mock_calls[0][1] + assert p_hass is hass + assert p_action is action + assert p_point == now + timedelta(seconds=3) + + +async def test_async_call_later(hass): + """Test calling an action later.""" + def action(): + pass now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC) with patch('homeassistant.helpers.event' diff --git a/tests/helpers/test_icon.py b/tests/helpers/test_icon.py index 417dded790a..2168974b783 100644 --- a/tests/helpers/test_icon.py +++ b/tests/helpers/test_icon.py @@ -1,53 +1,43 @@ """Test Home Assistant icon util methods.""" -import unittest -class TestIconUtil(unittest.TestCase): - """Test icon util methods.""" +def test_battery_icon(): + """Test icon generator for battery sensor.""" + from homeassistant.helpers.icon import icon_for_battery_level - def test_battery_icon(self): - """Test icon generator for battery sensor.""" - from homeassistant.helpers.icon import icon_for_battery_level + assert icon_for_battery_level(None, True) == 'mdi:battery-unknown' + assert icon_for_battery_level(None, False) == 'mdi:battery-unknown' - assert 'mdi:battery-unknown' == \ - icon_for_battery_level(None, True) - assert 'mdi:battery-unknown' == \ - icon_for_battery_level(None, False) + assert icon_for_battery_level(5, True) == 'mdi:battery-outline' + assert icon_for_battery_level(5, False) == 'mdi:battery-alert' - assert 'mdi:battery-outline' == \ - icon_for_battery_level(5, True) - assert 'mdi:battery-alert' == \ - icon_for_battery_level(5, False) + assert icon_for_battery_level(100, True) == 'mdi:battery-charging-100' + assert icon_for_battery_level(100, False) == 'mdi:battery' - assert 'mdi:battery-charging-100' == \ - icon_for_battery_level(100, True) - assert 'mdi:battery' == \ - icon_for_battery_level(100, False) - - iconbase = 'mdi:battery' - for level in range(0, 100, 5): - print('Level: %d. icon: %s, charging: %s' - % (level, icon_for_battery_level(level, False), - icon_for_battery_level(level, True))) - if level <= 10: - postfix_charging = '-outline' - elif level <= 30: - postfix_charging = '-charging-20' - elif level <= 50: - postfix_charging = '-charging-40' - elif level <= 70: - postfix_charging = '-charging-60' - elif level <= 90: - postfix_charging = '-charging-80' - else: - postfix_charging = '-charging-100' - if 5 < level < 95: - postfix = '-{}'.format(int(round(level / 10 - .01)) * 10) - elif level <= 5: - postfix = '-alert' - else: - postfix = '' - assert iconbase + postfix == \ - icon_for_battery_level(level, False) - assert iconbase + postfix_charging == \ - icon_for_battery_level(level, True) + iconbase = 'mdi:battery' + for level in range(0, 100, 5): + print('Level: %d. icon: %s, charging: %s' + % (level, icon_for_battery_level(level, False), + icon_for_battery_level(level, True))) + if level <= 10: + postfix_charging = '-outline' + elif level <= 30: + postfix_charging = '-charging-20' + elif level <= 50: + postfix_charging = '-charging-40' + elif level <= 70: + postfix_charging = '-charging-60' + elif level <= 90: + postfix_charging = '-charging-80' + else: + postfix_charging = '-charging-100' + if 5 < level < 95: + postfix = '-{}'.format(int(round(level / 10 - .01)) * 10) + elif level <= 5: + postfix = '-alert' + else: + postfix = '' + assert iconbase + postfix == \ + icon_for_battery_level(level, False) + assert iconbase + postfix_charging == \ + icon_for_battery_level(level, True) diff --git a/tests/helpers/test_init.py b/tests/helpers/test_init.py index 6af28e686f0..104801c84bb 100644 --- a/tests/helpers/test_init.py +++ b/tests/helpers/test_init.py @@ -1,50 +1,35 @@ """Test component helpers.""" # pylint: disable=protected-access from collections import OrderedDict -import unittest from homeassistant import helpers -from tests.common import get_test_home_assistant + +def test_extract_domain_configs(): + """Test the extraction of domain configuration.""" + config = { + 'zone': None, + 'zoner': None, + 'zone ': None, + 'zone Hallo': None, + 'zone 100': None, + } + + assert set(['zone', 'zone Hallo', 'zone 100']) == \ + set(helpers.extract_domain_configs(config, 'zone')) -class TestHelpers(unittest.TestCase): - """Tests homeassistant.helpers module.""" +def test_config_per_platform(): + """Test config per platform method.""" + config = OrderedDict([ + ('zone', {'platform': 'hello'}), + ('zoner', None), + ('zone Hallo', [1, {'platform': 'hello 2'}]), + ('zone 100', None), + ]) - # pylint: disable=invalid-name - def setUp(self): - """Init needed objects.""" - self.hass = get_test_home_assistant() - - # pylint: disable=invalid-name - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_extract_domain_configs(self): - """Test the extraction of domain configuration.""" - config = { - 'zone': None, - 'zoner': None, - 'zone ': None, - 'zone Hallo': None, - 'zone 100': None, - } - - assert set(['zone', 'zone Hallo', 'zone 100']) == \ - set(helpers.extract_domain_configs(config, 'zone')) - - def test_config_per_platform(self): - """Test config per platform method.""" - config = OrderedDict([ - ('zone', {'platform': 'hello'}), - ('zoner', None), - ('zone Hallo', [1, {'platform': 'hello 2'}]), - ('zone 100', None), - ]) - - assert [ - ('hello', config['zone']), - (None, 1), - ('hello 2', config['zone Hallo'][1]), - ] == list(helpers.config_per_platform(config, 'zone')) + assert [ + ('hello', config['zone']), + (None, 1), + ('hello 2', config['zone Hallo'][1]), + ] == list(helpers.config_per_platform(config, 'zone')) diff --git a/tests/helpers/test_intent.py b/tests/helpers/test_intent.py index 1a5b63fbab9..671c6f0d5ac 100644 --- a/tests/helpers/test_intent.py +++ b/tests/helpers/test_intent.py @@ -1,11 +1,11 @@ """Tests for the intent helpers.""" -import unittest import voluptuous as vol +import pytest + from homeassistant.core import State from homeassistant.helpers import (intent, config_validation as cv) -import pytest class MockIntentHandler(intent.IntentHandler): @@ -25,23 +25,20 @@ def test_async_match_state(): assert state is state1 -class TestIntentHandler(unittest.TestCase): - """Test the Home Assistant event helpers.""" +def test_async_validate_slots(): + """Test async_validate_slots of IntentHandler.""" + handler1 = MockIntentHandler({ + vol.Required('name'): cv.string, + }) - def test_async_validate_slots(self): - """Test async_validate_slots of IntentHandler.""" - handler1 = MockIntentHandler({ - vol.Required('name'): cv.string, - }) - - with pytest.raises(vol.error.MultipleInvalid): - handler1.async_validate_slots({}) - with pytest.raises(vol.error.MultipleInvalid): - handler1.async_validate_slots({'name': 1}) - with pytest.raises(vol.error.MultipleInvalid): - handler1.async_validate_slots({'name': 'kitchen'}) - handler1.async_validate_slots({'name': {'value': 'kitchen'}}) - handler1.async_validate_slots({ - 'name': {'value': 'kitchen'}, - 'probability': {'value': '0.5'} - }) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({}) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({'name': 1}) + with pytest.raises(vol.error.MultipleInvalid): + handler1.async_validate_slots({'name': 'kitchen'}) + handler1.async_validate_slots({'name': {'value': 'kitchen'}}) + handler1.async_validate_slots({ + 'name': {'value': 'kitchen'}, + 'probability': {'value': '0.5'} + }) diff --git a/tests/helpers/test_location.py b/tests/helpers/test_location.py index 5ff7abdbcdd..c48afde5f12 100644 --- a/tests/helpers/test_location.py +++ b/tests/helpers/test_location.py @@ -1,58 +1,57 @@ """Tests Home Assistant location helpers.""" -import unittest - from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import State from homeassistant.helpers import location -class TestHelpersLocation(unittest.TestCase): +def test_has_location_with_invalid_states(): """Set up the tests.""" - - def test_has_location_with_invalid_states(self): - """Set up the tests.""" - for state in (None, 1, "hello", object): - assert not location.has_location(state) - - def test_has_location_with_states_with_invalid_locations(self): - """Set up the tests.""" - state = State('hello.world', 'invalid', { - ATTR_LATITUDE: 'no number', - ATTR_LONGITUDE: 123.12 - }) + for state in (None, 1, "hello", object): assert not location.has_location(state) - def test_has_location_with_states_with_valid_location(self): - """Set up the tests.""" - state = State('hello.world', 'invalid', { - ATTR_LATITUDE: 123.12, - ATTR_LONGITUDE: 123.12 - }) - assert location.has_location(state) - def test_closest_with_no_states_with_location(self): - """Set up the tests.""" - state = State('light.test', 'on') - state2 = State('light.test', 'on', { - ATTR_LATITUDE: 'invalid', - ATTR_LONGITUDE: 123.45, - }) - state3 = State('light.test', 'on', { - ATTR_LONGITUDE: 123.45, - }) +def test_has_location_with_states_with_invalid_locations(): + """Set up the tests.""" + state = State('hello.world', 'invalid', { + ATTR_LATITUDE: 'no number', + ATTR_LONGITUDE: 123.12 + }) + assert not location.has_location(state) - assert \ - location.closest(123.45, 123.45, [state, state2, state3]) is None - def test_closest_returns_closest(self): - """Test .""" - state = State('light.test', 'on', { - ATTR_LATITUDE: 124.45, - ATTR_LONGITUDE: 124.45, - }) - state2 = State('light.test', 'on', { - ATTR_LATITUDE: 125.45, - ATTR_LONGITUDE: 125.45, - }) +def test_has_location_with_states_with_valid_location(): + """Set up the tests.""" + state = State('hello.world', 'invalid', { + ATTR_LATITUDE: 123.12, + ATTR_LONGITUDE: 123.12 + }) + assert location.has_location(state) - assert state == location.closest(123.45, 123.45, [state, state2]) + +def test_closest_with_no_states_with_location(): + """Set up the tests.""" + state = State('light.test', 'on') + state2 = State('light.test', 'on', { + ATTR_LATITUDE: 'invalid', + ATTR_LONGITUDE: 123.45, + }) + state3 = State('light.test', 'on', { + ATTR_LONGITUDE: 123.45, + }) + + assert \ + location.closest(123.45, 123.45, [state, state2, state3]) is None + + +def test_closest_returns_closest(): + """Test .""" + state = State('light.test', 'on', { + ATTR_LATITUDE: 124.45, + ATTR_LONGITUDE: 124.45, + }) + state2 = State('light.test', 'on', { + ATTR_LATITUDE: 125.45, + ATTR_LONGITUDE: 125.45, + }) + + assert state == location.closest(123.45, 123.45, [state, state2]) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index d04044d9b60..f9cd49ade1d 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1,9 +1,10 @@ """The tests for the Script component.""" # pylint: disable=protected-access from datetime import timedelta +import functools as ft from unittest import mock -import unittest +import asynctest import jinja2 import voluptuous as vol import pytest @@ -11,139 +12,130 @@ import pytest from homeassistant import exceptions from homeassistant.core import Context, callback # Otherwise can't test just this file (import order issue) -import homeassistant.components # noqa import homeassistant.util.dt as dt_util from homeassistant.helpers import script, config_validation as cv -from tests.common import fire_time_changed, get_test_home_assistant +from tests.common import async_fire_time_changed ENTITY_ID = 'script.test' -class TestScriptHelper(unittest.TestCase): - """Test the Script component.""" +async def test_firing_event(hass): + """Test the firing of events.""" + event = 'test_event' + context = Context() + calls = [] - # pylint: disable=invalid-name - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() + @callback + def record_event(event): + """Add recorded event to set.""" + calls.append(event) - # pylint: disable=invalid-name - def tearDown(self): - """Stop down everything that was started.""" - self.hass.stop() + hass.bus.async_listen(event, record_event) - def test_firing_event(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': { - 'hello': 'world' - } - })) - - script_obj.run(context=context) - - self.hass.block_till_done() - - assert len(calls) == 1 - assert calls[0].context is context - assert calls[0].data.get('hello') == 'world' - assert not script_obj.can_cancel - - 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'] + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA({ + 'event': event, + 'event_data': { + 'hello': 'world' } - assert not script_obj.can_cancel + })) - def test_calling_service(self): - """Test the calling of a service.""" - calls = [] - context = Context() + await script_obj.async_run(context=context) - @callback - def record_call(service): - """Add recorded event to set.""" - calls.append(service) + await hass.async_block_till_done() - self.hass.services.register('test', 'script', record_call) + assert len(calls) == 1 + assert calls[0].context is context + assert calls[0].data.get('hello') == 'world' + assert not script_obj.can_cancel - script.call_from_config(self.hass, { + +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_calling_service(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': 'test.script', 'data': { 'hello': 'world' } - }, context=context) + }, context=context)) - self.hass.block_till_done() + await hass.async_block_till_done() - assert len(calls) == 1 - assert calls[0].context is context - assert calls[0].data.get('hello') == 'world' + assert len(calls) == 1 + assert calls[0].context is context + assert calls[0].data.get('hello') == 'world' - def test_calling_service_template(self): - """Test the calling of a service.""" - calls = [] - context = Context() - @callback - def record_call(service): - """Add recorded event to set.""" - calls.append(service) +async def test_calling_service_template(hass): + """Test the calling of a service.""" + calls = [] + context = Context() - self.hass.services.register('test', 'script', record_call) + @callback + def record_call(service): + """Add recorded event to set.""" + calls.append(service) - script.call_from_config(self.hass, { + 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 @@ -159,626 +151,644 @@ class TestScriptHelper(unittest.TestCase): {% endif %} """ } - }, {'is_world': 'yes'}, context=context) - - self.hass.block_till_done() - - assert len(calls) == 1 - assert calls[0].context is context - assert calls[0].data.get('hello') == 'world' - - 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 test_delay_template(self): - """Test the delay as a template.""" - event = 'test_event' - events = [] - 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': '00:00:{{ 5 }}', 'alias': delay_alias}, - {'event': event}])) - - script_obj.run() - 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 - - def test_delay_invalid_template(self): - """Test the delay as a template that fails.""" - event = 'test_event' - events = [] - - @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': '{{ invalid_delay }}'}, - {'delay': {'seconds': 5}}, - {'event': event}])) - - with mock.patch.object(script, '_LOGGER') as mock_logger: - script_obj.run() - self.hass.block_till_done() - assert mock_logger.error.called - - assert not script_obj.is_running - assert len(events) == 1 - - def test_delay_complex_template(self): - """Test the delay with a working complex template.""" - event = 'test_event' - events = [] - 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() - 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 - - def test_delay_complex_invalid_template(self): - """Test the delay with a complex template that fails.""" - event = 'test_event' - events = [] - - @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': '{{ invalid_delay }}' - }}, - {'delay': { - 'seconds': '{{ 5 }}' - }}, - {'event': event}])) - - with mock.patch.object(script, '_LOGGER') as mock_logger: - script_obj.run() - self.hass.block_till_done() - assert mock_logger.error.called - - assert not script_obj.is_running - assert len(events) == 1 - - def test_cancel_while_delay(self): - """Test the cancelling while the delay is present.""" - event = 'test_event' - events = [] - - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) - - self.hass.bus.listen(event, record_event) + }, {'is_world': 'yes'}, context=context)) - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'delay': {'seconds': 5}}, - {'event': event}])) - - script_obj.run() - self.hass.block_till_done() + await hass.async_block_till_done() - assert script_obj.is_running - assert len(events) == 0 - - script_obj.stop() + assert len(calls) == 1 + assert calls[0].context is context + assert calls[0].data.get('hello') == 'world' - assert not script_obj.is_running - # Make sure the script is really stopped. - future = dt_util.utcnow() + timedelta(seconds=5) - fire_time_changed(self.hass, future) - self.hass.block_till_done() +async def test_delay(hass): + """Test the delay.""" + event = 'test_event' + events = [] + context = Context() + delay_alias = 'delay step' - assert not script_obj.is_running - assert len(events) == 0 + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - def test_wait_template(self): - """Test the wait template.""" - event = 'test_event' - events = [] - context = Context() - wait_alias = 'wait step' + hass.bus.async_listen(event, record_event) - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': {'seconds': 5}, 'alias': delay_alias}, + {'event': event}])) - self.hass.bus.listen(event, record_event) + await script_obj.async_run(context=context) + await hass.async_block_till_done() - self.hass.states.set('switch.test', 'on') + assert script_obj.is_running + assert script_obj.can_cancel + assert script_obj.last_action == delay_alias + assert len(events) == 1 - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'wait_template': "{{states.switch.test.state == 'off'}}", - 'alias': wait_alias}, - {'event': event}])) + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - script_obj.run(context=context) - 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 - 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() +async def test_delay_template(hass): + """Test the delay as a template.""" + event = 'test_event' + events = [] + delay_alias = 'delay step' - assert not script_obj.is_running - assert len(events) == 2 - assert events[0].context is context - assert events[1].context is context + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - def test_wait_template_cancel(self): - """Test the wait template cancel action.""" - event = 'test_event' - events = [] - wait_alias = 'wait step' + hass.bus.async_listen(event, record_event) - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': '00:00:{{ 5 }}', 'alias': delay_alias}, + {'event': event}])) - self.hass.bus.listen(event, record_event) + await script_obj.async_run() + await hass.async_block_till_done() - self.hass.states.set('switch.test', 'on') + assert script_obj.is_running + assert script_obj.can_cancel + assert script_obj.last_action == delay_alias + assert len(events) == 1 - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'wait_template': "{{states.switch.test.state == 'off'}}", - 'alias': wait_alias}, - {'event': event}])) + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - script_obj.run() - self.hass.block_till_done() + assert not script_obj.is_running + assert len(events) == 2 - assert script_obj.is_running - assert script_obj.can_cancel - assert script_obj.last_action == wait_alias - assert len(events) == 1 - script_obj.stop() +async def test_delay_invalid_template(hass): + """Test the delay as a template that fails.""" + event = 'test_event' + events = [] - assert not script_obj.is_running - assert len(events) == 1 + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - self.hass.states.set('switch.test', 'off') - self.hass.block_till_done() + hass.bus.async_listen(event, record_event) - assert not script_obj.is_running - assert len(events) == 1 + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': '{{ invalid_delay }}'}, + {'delay': {'seconds': 5}}, + {'event': event}])) - def test_wait_template_not_schedule(self): - """Test the wait template with correct condition.""" - event = 'test_event' - events = [] + with mock.patch.object(script, '_LOGGER') as mock_logger: + await script_obj.async_run() + await hass.async_block_till_done() + assert mock_logger.error.called - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) + assert not script_obj.is_running + assert len(events) == 1 - self.hass.bus.listen(event, record_event) - self.hass.states.set('switch.test', 'on') +async def test_delay_complex_template(hass): + """Test the delay with a working complex template.""" + event = 'test_event' + events = [] + delay_alias = 'delay step' - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'wait_template': "{{states.switch.test.state == 'on'}}"}, - {'event': event}])) + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - script_obj.run() - self.hass.block_till_done() + hass.bus.async_listen(event, record_event) - assert not script_obj.is_running - assert script_obj.can_cancel - assert len(events) == 2 + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': { + 'seconds': '{{ 5 }}'}, + 'alias': delay_alias}, + {'event': event}])) - def test_wait_template_timeout_halt(self): - """Test the wait template, halt on timeout.""" - event = 'test_event' - events = [] - wait_alias = 'wait step' + await script_obj.async_run() + await hass.async_block_till_done() - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) + assert script_obj.is_running + assert script_obj.can_cancel + assert script_obj.last_action == delay_alias + assert len(events) == 1 - self.hass.bus.listen(event, record_event) + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - self.hass.states.set('switch.test', 'on') + assert not script_obj.is_running + assert len(events) == 2 - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - { - 'wait_template': "{{states.switch.test.state == 'off'}}", - 'continue_on_timeout': False, - 'timeout': 5, - 'alias': wait_alias + +async def test_delay_complex_invalid_template(hass): + """Test the delay with a complex template that fails.""" + event = 'test_event' + events = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + hass.bus.async_listen(event, record_event) + + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': { + 'seconds': '{{ invalid_delay }}' + }}, + {'delay': { + 'seconds': '{{ 5 }}' + }}, + {'event': event}])) + + with mock.patch.object(script, '_LOGGER') as mock_logger: + await script_obj.async_run() + await hass.async_block_till_done() + assert mock_logger.error.called + + assert not script_obj.is_running + assert len(events) == 1 + + +async def test_cancel_while_delay(hass): + """Test the cancelling while the delay is present.""" + event = 'test_event' + events = [] + + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) + + hass.bus.async_listen(event, record_event) + + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'delay': {'seconds': 5}}, + {'event': event}])) + + await script_obj.async_run() + await hass.async_block_till_done() + + assert script_obj.is_running + assert len(events) == 0 + + script_obj.async_stop() + + assert not script_obj.is_running + + # Make sure the script is really stopped. + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert not script_obj.is_running + assert len(events) == 0 + + +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 test_wait_template_cancel(hass): + """Test the wait template cancel action.""" + event = 'test_event' + events = [] + 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() + 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 + + script_obj.async_stop() + + assert not script_obj.is_running + 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) == 1 + + +async def test_wait_template_not_schedule(hass): + """Test the wait template with correct condition.""" + event = 'test_event' + events = [] + + @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 == 'on'}}"}, + {'event': event}])) + + await script_obj.async_run() + await hass.async_block_till_done() + + assert not script_obj.is_running + assert script_obj.can_cancel + assert len(events) == 2 + + +async def test_wait_template_timeout_halt(hass): + """Test the wait template, halt on timeout.""" + event = 'test_event' + events = [] + 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'}}", + 'continue_on_timeout': False, + 'timeout': 5, + 'alias': wait_alias + }, + {'event': event}])) + + await script_obj.async_run() + 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 + + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert not script_obj.is_running + assert len(events) == 1 + + +async def test_wait_template_timeout_continue(hass): + """Test the wait template with continuing the script.""" + event = 'test_event' + events = [] + 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'}}", + 'timeout': 5, + 'continue_on_timeout': True, + 'alias': wait_alias + }, + {'event': event}])) + + await script_obj.async_run() + 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 + + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert not script_obj.is_running + assert len(events) == 2 + + +async def test_wait_template_timeout_default(hass): + """Test the wait template with default contiune.""" + event = 'test_event' + events = [] + 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'}}", + 'timeout': 5, + 'alias': wait_alias + }, + {'event': event}])) + + await script_obj.async_run() + 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 + + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert not script_obj.is_running + assert len(events) == 2 + + +async def test_wait_template_variables(hass): + """Test the wait template with variables.""" + event = 'test_event' + events = [] + 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': "{{is_state(data, 'off')}}", + 'alias': wait_alias}, + {'event': event}])) + + await script_obj.async_run({ + 'data': 'switch.test' + }) + 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 + + +async def test_passing_variables_to_script(hass): + """Test if we can pass variables to script.""" + calls = [] + + @callback + def record_call(service): + """Add recorded event to set.""" + calls.append(service) + + hass.services.async_register('test', 'script', record_call) + + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + { + 'service': 'test.script', + 'data_template': { + 'hello': '{{ greeting }}', }, - {'event': event}])) - - script_obj.run() - 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 - - 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) == 1 - - def test_wait_template_timeout_continue(self): - """Test the wait template with continuing the script.""" - event = 'test_event' - events = [] - 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'}}", - 'timeout': 5, - 'continue_on_timeout': True, - 'alias': wait_alias + }, + {'delay': '{{ delay_period }}'}, + { + 'service': 'test.script', + 'data_template': { + 'hello': '{{ greeting2 }}', }, - {'event': event}])) + }])) - script_obj.run() - self.hass.block_till_done() + await script_obj.async_run({ + 'greeting': 'world', + 'greeting2': 'universe', + 'delay_period': '00:00:05' + }) - assert script_obj.is_running - assert script_obj.can_cancel - assert script_obj.last_action == wait_alias - assert len(events) == 1 + await hass.async_block_till_done() - future = dt_util.utcnow() + timedelta(seconds=5) - fire_time_changed(self.hass, future) - self.hass.block_till_done() + assert script_obj.is_running + assert len(calls) == 1 + assert calls[-1].data['hello'] == 'world' - assert not script_obj.is_running - assert len(events) == 2 + future = dt_util.utcnow() + timedelta(seconds=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - def test_wait_template_timeout_default(self): - """Test the wait template with default contiune.""" - event = 'test_event' - events = [] - wait_alias = 'wait step' + assert not script_obj.is_running + assert len(calls) == 2 + assert calls[-1].data['hello'] == 'universe' - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) - self.hass.bus.listen(event, record_event) +async def test_condition(hass): + """Test if we can use conditions in a script.""" + event = 'test_event' + events = [] - self.hass.states.set('switch.test', 'on') + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - { - 'wait_template': "{{states.switch.test.state == 'off'}}", - 'timeout': 5, - 'alias': wait_alias - }, - {'event': event}])) + hass.bus.async_listen(event, record_event) - script_obj.run() - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello') - assert script_obj.is_running - assert script_obj.can_cancel - assert script_obj.last_action == wait_alias - assert len(events) == 1 + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state == "hello" }}', + }, + {'event': event}, + ])) - future = dt_util.utcnow() + timedelta(seconds=5) - fire_time_changed(self.hass, future) - self.hass.block_till_done() + await script_obj.async_run() + await hass.async_block_till_done() + assert len(events) == 2 - assert not script_obj.is_running - assert len(events) == 2 + hass.states.async_set('test.entity', 'goodbye') - def test_wait_template_variables(self): - """Test the wait template with variables.""" - event = 'test_event' - events = [] - wait_alias = 'wait step' + await script_obj.async_run() + await hass.async_block_till_done() + assert len(events) == 3 - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) - self.hass.bus.listen(event, record_event) +@asynctest.patch('homeassistant.helpers.script.condition.async_from_config') +async def test_condition_created_once(async_from_config, hass): + """Test that the conditions do not get created multiple times.""" + event = 'test_event' + events = [] - self.hass.states.set('switch.test', 'on') + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'wait_template': "{{is_state(data, 'off')}}", - 'alias': wait_alias}, - {'event': event}])) + hass.bus.async_listen(event, record_event) - script_obj.run({ - 'data': 'switch.test' - }) - self.hass.block_till_done() + hass.states.async_set('test.entity', 'hello') - assert script_obj.is_running - assert script_obj.can_cancel - assert script_obj.last_action == wait_alias - assert len(events) == 1 + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state == "hello" }}', + }, + {'event': event}, + ])) - self.hass.states.set('switch.test', 'off') - self.hass.block_till_done() + await script_obj.async_run() + await script_obj.async_run() + await hass.async_block_till_done() + assert async_from_config.call_count == 1 + assert len(script_obj._config_cache) == 1 - assert not script_obj.is_running - assert len(events) == 2 - def test_passing_variables_to_script(self): - """Test if we can pass variables to script.""" - calls = [] +async def test_all_conditions_cached(hass): + """Test that multiple conditions get cached.""" + event = 'test_event' + events = [] - @callback - def record_call(service): - """Add recorded event to set.""" - calls.append(service) + @callback + def record_event(event): + """Add recorded event to set.""" + events.append(event) - self.hass.services.register('test', 'script', record_call) + hass.bus.async_listen(event, record_event) - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - { - 'service': 'test.script', - 'data_template': { - 'hello': '{{ greeting }}', - }, - }, - {'delay': '{{ delay_period }}'}, - { - 'service': 'test.script', - 'data_template': { - 'hello': '{{ greeting2 }}', - }, - }])) + hass.states.async_set('test.entity', 'hello') - script_obj.run({ - 'greeting': 'world', - 'greeting2': 'universe', - 'delay_period': '00:00:05' - }) + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state == "hello" }}', + }, + { + 'condition': 'template', + 'value_template': '{{ states.test.entity.state != "hello" }}', + }, + {'event': event}, + ])) - self.hass.block_till_done() + await script_obj.async_run() + await hass.async_block_till_done() + assert len(script_obj._config_cache) == 2 - assert script_obj.is_running - assert len(calls) == 1 - assert calls[-1].data['hello'] == 'world' - future = dt_util.utcnow() + timedelta(seconds=5) - fire_time_changed(self.hass, future) - self.hass.block_till_done() +async def test_last_triggered(hass): + """Test the last_triggered.""" + event = 'test_event' - assert not script_obj.is_running - assert len(calls) == 2 - assert calls[-1].data['hello'] == 'universe' + script_obj = script.Script(hass, cv.SCRIPT_SCHEMA([ + {'event': event}, + {'delay': {'seconds': 5}}, + {'event': event}])) - def test_condition(self): - """Test if we can use conditions in a script.""" - event = 'test_event' - events = [] + assert script_obj.last_triggered is None - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) + time = dt_util.utcnow() + with mock.patch('homeassistant.helpers.script.date_util.utcnow', + return_value=time): + await script_obj.async_run() + await hass.async_block_till_done() - self.hass.bus.listen(event, record_event) - - self.hass.states.set('test.entity', 'hello') - - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - { - 'condition': 'template', - 'value_template': '{{ states.test.entity.state == "hello" }}', - }, - {'event': event}, - ])) - - script_obj.run() - self.hass.block_till_done() - assert len(events) == 2 - - self.hass.states.set('test.entity', 'goodbye') - - script_obj.run() - self.hass.block_till_done() - assert len(events) == 3 - - @mock.patch('homeassistant.helpers.script.condition.async_from_config') - def test_condition_created_once(self, async_from_config): - """Test that the conditions do not get created multiple times.""" - event = 'test_event' - events = [] - - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) - - self.hass.bus.listen(event, record_event) - - self.hass.states.set('test.entity', 'hello') - - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - { - 'condition': 'template', - 'value_template': '{{ states.test.entity.state == "hello" }}', - }, - {'event': event}, - ])) - - script_obj.run() - script_obj.run() - self.hass.block_till_done() - assert async_from_config.call_count == 1 - assert len(script_obj._config_cache) == 1 - - def test_all_conditions_cached(self): - """Test that multiple conditions get cached.""" - event = 'test_event' - events = [] - - @callback - def record_event(event): - """Add recorded event to set.""" - events.append(event) - - self.hass.bus.listen(event, record_event) - - self.hass.states.set('test.entity', 'hello') - - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - { - 'condition': 'template', - 'value_template': '{{ states.test.entity.state == "hello" }}', - }, - { - 'condition': 'template', - 'value_template': '{{ states.test.entity.state != "hello" }}', - }, - {'event': event}, - ])) - - script_obj.run() - self.hass.block_till_done() - assert len(script_obj._config_cache) == 2 - - def test_last_triggered(self): - """Test the last_triggered.""" - event = 'test_event' - - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'delay': {'seconds': 5}}, - {'event': event}])) - - assert script_obj.last_triggered is None - - time = dt_util.utcnow() - with mock.patch('homeassistant.helpers.script.date_util.utcnow', - return_value=time): - script_obj.run() - self.hass.block_till_done() - - assert script_obj.last_triggered == time + assert script_obj.last_triggered == time async def test_propagate_error_service_not_found(hass): @@ -873,18 +883,20 @@ def test_log_exception(): script_obj._exception_step = 1 for exc, msg in ( - (vol.Invalid("Invalid number"), 'Invalid data'), - (exceptions.TemplateError(jinja2.TemplateError('Unclosed bracket')), - 'Error rendering template'), - (exceptions.Unauthorized(), 'Unauthorized'), - (exceptions.ServiceNotFound('light', 'turn_on'), 'Service not found'), - (ValueError("Cannot parse JSON"), 'Unknown error'), + (vol.Invalid("Invalid number"), 'Invalid data'), + (exceptions.TemplateError( + jinja2.TemplateError('Unclosed bracket')), + 'Error rendering template'), + (exceptions.Unauthorized(), 'Unauthorized'), + (exceptions.ServiceNotFound('light', 'turn_on'), + 'Service not found'), + (ValueError("Cannot parse JSON"), 'Unknown error'), ): logger = mock.Mock() script_obj.async_log_exception(logger, 'Test error', exc) assert len(logger.mock_calls) == 1 - p_format, p_msg_base, p_error_desc, p_action_type, p_step, p_error = \ + _, _, p_error_desc, p_action_type, p_step, p_error = \ logger.mock_calls[0][1] assert p_error_desc == msg diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 10b053528ab..bc4e50f611c 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -1,13 +1,12 @@ """Test state helpers.""" import asyncio from datetime import timedelta -import unittest from unittest.mock import patch +import pytest + import homeassistant.core as ha -from homeassistant.setup import async_setup_component from homeassistant.const import (SERVICE_TURN_ON, SERVICE_TURN_OFF) -from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.util import dt as dt_util from homeassistant.helpers import state from homeassistant.const import ( @@ -18,8 +17,7 @@ from homeassistant.const import ( from homeassistant.components.sun import (STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) -from tests.common import get_test_home_assistant, mock_service -import pytest +from tests.common import async_mock_service @asyncio.coroutine @@ -82,141 +80,134 @@ def test_call_to_component(hass): context=context) -class TestStateHelpers(unittest.TestCase): - """Test the Home Assistant event helpers.""" +async def test_get_changed_since(hass): + """Test get_changed_since.""" + point1 = dt_util.utcnow() + point2 = point1 + timedelta(seconds=5) + point3 = point2 + timedelta(seconds=5) - def setUp(self): # pylint: disable=invalid-name - """Run when tests are started.""" - self.hass = get_test_home_assistant() - run_coroutine_threadsafe(async_setup_component( - self.hass, 'homeassistant', {}), self.hass.loop).result() + with patch('homeassistant.core.dt_util.utcnow', return_value=point1): + hass.states.async_set('light.test', 'on') + state1 = hass.states.get('light.test') - def tearDown(self): # pylint: disable=invalid-name - """Stop when tests are finished.""" - self.hass.stop() + with patch('homeassistant.core.dt_util.utcnow', return_value=point2): + hass.states.async_set('light.test2', 'on') + state2 = hass.states.get('light.test2') - def test_get_changed_since(self): - """Test get_changed_since.""" - point1 = dt_util.utcnow() - point2 = point1 + timedelta(seconds=5) - point3 = point2 + timedelta(seconds=5) + with patch('homeassistant.core.dt_util.utcnow', return_value=point3): + hass.states.async_set('light.test3', 'on') + state3 = hass.states.get('light.test3') - with patch('homeassistant.core.dt_util.utcnow', return_value=point1): - self.hass.states.set('light.test', 'on') - state1 = self.hass.states.get('light.test') + assert [state2, state3] == \ + state.get_changed_since([state1, state2, state3], point2) - with patch('homeassistant.core.dt_util.utcnow', return_value=point2): - self.hass.states.set('light.test2', 'on') - state2 = self.hass.states.get('light.test2') - with patch('homeassistant.core.dt_util.utcnow', return_value=point3): - self.hass.states.set('light.test3', 'on') - state3 = self.hass.states.get('light.test3') +async def test_reproduce_with_no_entity(hass): + """Test reproduce_state with no entity.""" + calls = async_mock_service(hass, 'light', SERVICE_TURN_ON) - assert [state2, state3] == \ - state.get_changed_since([state1, state2, state3], point2) + await state.async_reproduce_state(hass, ha.State('light.test', 'on')) - def test_reproduce_with_no_entity(self): - """Test reproduce_state with no entity.""" - calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) + await hass.async_block_till_done() - state.reproduce_state(self.hass, ha.State('light.test', 'on')) + assert len(calls) == 0 + assert hass.states.get('light.test') is None - self.hass.block_till_done() - assert len(calls) == 0 - assert self.hass.states.get('light.test') is None +async def test_reproduce_turn_on(hass): + """Test reproduce_state with SERVICE_TURN_ON.""" + calls = async_mock_service(hass, 'light', SERVICE_TURN_ON) - def test_reproduce_turn_on(self): - """Test reproduce_state with SERVICE_TURN_ON.""" - calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) + hass.states.async_set('light.test', 'off') - self.hass.states.set('light.test', 'off') + await state.async_reproduce_state(hass, ha.State('light.test', 'on')) - state.reproduce_state(self.hass, ha.State('light.test', 'on')) + await hass.async_block_till_done() - self.hass.block_till_done() + assert len(calls) > 0 + last_call = calls[-1] + assert last_call.domain == 'light' + assert SERVICE_TURN_ON == last_call.service + assert ['light.test'] == last_call.data.get('entity_id') - assert len(calls) > 0 - last_call = calls[-1] - assert 'light' == last_call.domain - assert SERVICE_TURN_ON == last_call.service - assert ['light.test'] == last_call.data.get('entity_id') - def test_reproduce_turn_off(self): - """Test reproduce_state with SERVICE_TURN_OFF.""" - calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF) +async def test_reproduce_turn_off(hass): + """Test reproduce_state with SERVICE_TURN_OFF.""" + calls = async_mock_service(hass, 'light', SERVICE_TURN_OFF) - self.hass.states.set('light.test', 'on') + hass.states.async_set('light.test', 'on') - state.reproduce_state(self.hass, ha.State('light.test', 'off')) + await state.async_reproduce_state(hass, ha.State('light.test', 'off')) - self.hass.block_till_done() + await hass.async_block_till_done() - assert len(calls) > 0 - last_call = calls[-1] - assert 'light' == last_call.domain - assert SERVICE_TURN_OFF == last_call.service - assert ['light.test'] == last_call.data.get('entity_id') + assert len(calls) > 0 + last_call = calls[-1] + assert last_call.domain == 'light' + assert SERVICE_TURN_OFF == last_call.service + assert ['light.test'] == last_call.data.get('entity_id') - def test_reproduce_complex_data(self): - """Test reproduce_state with complex service data.""" - calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) - self.hass.states.set('light.test', 'off') +async def test_reproduce_complex_data(hass): + """Test reproduce_state with complex service data.""" + calls = async_mock_service(hass, 'light', SERVICE_TURN_ON) - complex_data = ['hello', {'11': '22'}] + hass.states.async_set('light.test', 'off') - state.reproduce_state(self.hass, ha.State('light.test', 'on', { - 'complex': complex_data - })) + complex_data = ['hello', {'11': '22'}] - self.hass.block_till_done() + await state.async_reproduce_state(hass, ha.State('light.test', 'on', { + 'complex': complex_data + })) - assert len(calls) > 0 - last_call = calls[-1] - assert 'light' == last_call.domain - assert SERVICE_TURN_ON == last_call.service - assert complex_data == last_call.data.get('complex') + await hass.async_block_till_done() - def test_reproduce_bad_state(self): - """Test reproduce_state with bad state.""" - calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) + assert len(calls) > 0 + last_call = calls[-1] + assert last_call.domain == 'light' + assert SERVICE_TURN_ON == last_call.service + assert complex_data == last_call.data.get('complex') - self.hass.states.set('light.test', 'off') - state.reproduce_state(self.hass, ha.State('light.test', 'bad')) +async def test_reproduce_bad_state(hass): + """Test reproduce_state with bad state.""" + calls = async_mock_service(hass, 'light', SERVICE_TURN_ON) - self.hass.block_till_done() + hass.states.async_set('light.test', 'off') - assert len(calls) == 0 - assert 'off' == self.hass.states.get('light.test').state + await state.async_reproduce_state(hass, ha.State('light.test', 'bad')) - def test_as_number_states(self): - """Test state_as_number with states.""" - zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED, - STATE_BELOW_HORIZON, STATE_NOT_HOME) - one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON, - STATE_HOME) - for _state in zero_states: - assert 0 == state.state_as_number( - ha.State('domain.test', _state, {})) - for _state in one_states: - assert 1 == state.state_as_number( - ha.State('domain.test', _state, {})) + await hass.async_block_till_done() - def test_as_number_coercion(self): - """Test state_as_number with number.""" - for _state in ('0', '0.0', 0, 0.0): - assert 0.0 == state.state_as_number( - ha.State('domain.test', _state, {})) - for _state in ('1', '1.0', 1, 1.0): - assert 1.0 == state.state_as_number( - ha.State('domain.test', _state, {})) + assert len(calls) == 0 + assert hass.states.get('light.test').state == 'off' - def test_as_number_invalid_cases(self): - """Test state_as_number with invalid cases.""" - for _state in ('', 'foo', 'foo.bar', None, False, True, object, - object()): - with pytest.raises(ValueError): - state.state_as_number(ha.State('domain.test', _state, {})) + +async def test_as_number_states(hass): + """Test state_as_number with states.""" + zero_states = (STATE_OFF, STATE_CLOSED, STATE_UNLOCKED, + STATE_BELOW_HORIZON, STATE_NOT_HOME) + one_states = (STATE_ON, STATE_OPEN, STATE_LOCKED, STATE_ABOVE_HORIZON, + STATE_HOME) + for _state in zero_states: + assert state.state_as_number(ha.State('domain.test', _state, {})) == 0 + for _state in one_states: + assert state.state_as_number(ha.State('domain.test', _state, {})) == 1 + + +async def test_as_number_coercion(hass): + """Test state_as_number with number.""" + for _state in ('0', '0.0', 0, 0.0): + assert state.state_as_number( + ha.State('domain.test', _state, {})) == 0.0 + for _state in ('1', '1.0', 1, 1.0): + assert state.state_as_number( + ha.State('domain.test', _state, {})) == 1.0 + + +async def test_as_number_invalid_cases(hass): + """Test state_as_number with invalid cases.""" + for _state in ('', 'foo', 'foo.bar', None, False, True, object, + object()): + with pytest.raises(ValueError): + state.state_as_number(ha.State('domain.test', _state, {})) diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py index af639e69432..51978194b03 100644 --- a/tests/helpers/test_sun.py +++ b/tests/helpers/test_sun.py @@ -1,6 +1,5 @@ """The tests for the Sun helpers.""" # pylint: disable=protected-access -import unittest from unittest.mock import patch from datetime import timedelta, datetime @@ -8,223 +7,214 @@ from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET import homeassistant.util.dt as dt_util import homeassistant.helpers.sun as sun -from tests.common import get_test_home_assistant + +def test_next_events(hass): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral + + astral = Astral() + utc_today = utc_now.date() + + latitude = hass.config.latitude + longitude = hass.config.longitude + + mod = -1 + while True: + next_dawn = (astral.dawn_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_dawn > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_dusk = (astral.dusk_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_dusk > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_midnight = (astral.solar_midnight_utc( + utc_today + timedelta(days=mod), longitude)) + if next_midnight > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_noon = (astral.solar_noon_utc( + utc_today + timedelta(days=mod), longitude)) + if next_noon > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_rising = (astral.sunrise_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_rising > utc_now: + break + mod += 1 + + mod = -1 + while True: + next_setting = (astral.sunset_utc( + utc_today + timedelta(days=mod), latitude, longitude)) + if next_setting > utc_now: + break + mod += 1 + + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + assert next_dawn == sun.get_astral_event_next( + hass, 'dawn') + assert next_dusk == sun.get_astral_event_next( + hass, 'dusk') + assert next_midnight == sun.get_astral_event_next( + hass, 'solar_midnight') + assert next_noon == sun.get_astral_event_next( + hass, 'solar_noon') + assert next_rising == sun.get_astral_event_next( + hass, SUN_EVENT_SUNRISE) + assert next_setting == sun.get_astral_event_next( + hass, SUN_EVENT_SUNSET) -# pylint: disable=invalid-name -class TestSun(unittest.TestCase): - """Test the sun helpers.""" +def test_date_events(hass): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() + astral = Astral() + utc_today = utc_now.date() - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() + latitude = hass.config.latitude + longitude = hass.config.longitude - def test_next_events(self): - """Test retrieving next sun events.""" - utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) - from astral import Astral + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) - astral = Astral() - utc_today = utc_now.date() + assert dawn == sun.get_astral_event_date( + hass, 'dawn', utc_today) + assert dusk == sun.get_astral_event_date( + hass, 'dusk', utc_today) + assert midnight == sun.get_astral_event_date( + hass, 'solar_midnight', utc_today) + assert noon == sun.get_astral_event_date( + hass, 'solar_noon', utc_today) + assert sunrise == sun.get_astral_event_date( + hass, SUN_EVENT_SUNRISE, utc_today) + assert sunset == sun.get_astral_event_date( + hass, SUN_EVENT_SUNSET, utc_today) - latitude = self.hass.config.latitude - longitude = self.hass.config.longitude - mod = -1 - while True: - next_dawn = (astral.dawn_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_dawn > utc_now: - break - mod += 1 +def test_date_events_default_date(hass): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral - mod = -1 - while True: - next_dusk = (astral.dusk_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_dusk > utc_now: - break - mod += 1 + astral = Astral() + utc_today = utc_now.date() - mod = -1 - while True: - next_midnight = (astral.solar_midnight_utc( - utc_today + timedelta(days=mod), longitude)) - if next_midnight > utc_now: - break - mod += 1 + latitude = hass.config.latitude + longitude = hass.config.longitude - mod = -1 - while True: - next_noon = (astral.solar_noon_utc( - utc_today + timedelta(days=mod), longitude)) - if next_noon > utc_now: - break - mod += 1 - - mod = -1 - while True: - next_rising = (astral.sunrise_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_rising > utc_now: - break - mod += 1 - - mod = -1 - while True: - next_setting = (astral.sunset_utc( - utc_today + timedelta(days=mod), latitude, longitude)) - if next_setting > utc_now: - break - mod += 1 - - with patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=utc_now): - assert next_dawn == sun.get_astral_event_next( - self.hass, 'dawn') - assert next_dusk == sun.get_astral_event_next( - self.hass, 'dusk') - assert next_midnight == sun.get_astral_event_next( - self.hass, 'solar_midnight') - assert next_noon == sun.get_astral_event_next( - self.hass, 'solar_noon') - assert next_rising == sun.get_astral_event_next( - self.hass, SUN_EVENT_SUNRISE) - assert next_setting == sun.get_astral_event_next( - self.hass, SUN_EVENT_SUNSET) - - def test_date_events(self): - """Test retrieving next sun events.""" - utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) - from astral import Astral - - astral = Astral() - utc_today = utc_now.date() - - latitude = self.hass.config.latitude - longitude = self.hass.config.longitude - - dawn = astral.dawn_utc(utc_today, latitude, longitude) - dusk = astral.dusk_utc(utc_today, latitude, longitude) - midnight = astral.solar_midnight_utc(utc_today, longitude) - noon = astral.solar_noon_utc(utc_today, longitude) - sunrise = astral.sunrise_utc(utc_today, latitude, longitude) - sunset = astral.sunset_utc(utc_today, latitude, longitude) + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) + with patch('homeassistant.util.dt.now', return_value=utc_now): assert dawn == sun.get_astral_event_date( - self.hass, 'dawn', utc_today) + hass, 'dawn', utc_today) assert dusk == sun.get_astral_event_date( - self.hass, 'dusk', utc_today) + hass, 'dusk', utc_today) assert midnight == sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_today) + hass, 'solar_midnight', utc_today) assert noon == sun.get_astral_event_date( - self.hass, 'solar_noon', utc_today) + hass, 'solar_noon', utc_today) assert sunrise == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNRISE, utc_today) + hass, SUN_EVENT_SUNRISE, utc_today) assert sunset == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNSET, utc_today) + hass, SUN_EVENT_SUNSET, utc_today) - def test_date_events_default_date(self): - """Test retrieving next sun events.""" - utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) - from astral import Astral - astral = Astral() - utc_today = utc_now.date() +def test_date_events_accepts_datetime(hass): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) + from astral import Astral - latitude = self.hass.config.latitude - longitude = self.hass.config.longitude + astral = Astral() + utc_today = utc_now.date() - dawn = astral.dawn_utc(utc_today, latitude, longitude) - dusk = astral.dusk_utc(utc_today, latitude, longitude) - midnight = astral.solar_midnight_utc(utc_today, longitude) - noon = astral.solar_noon_utc(utc_today, longitude) - sunrise = astral.sunrise_utc(utc_today, latitude, longitude) - sunset = astral.sunset_utc(utc_today, latitude, longitude) + latitude = hass.config.latitude + longitude = hass.config.longitude - with patch('homeassistant.util.dt.now', return_value=utc_now): - assert dawn == sun.get_astral_event_date( - self.hass, 'dawn', utc_today) - assert dusk == sun.get_astral_event_date( - self.hass, 'dusk', utc_today) - assert midnight == sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_today) - assert noon == sun.get_astral_event_date( - self.hass, 'solar_noon', utc_today) - assert sunrise == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNRISE, utc_today) - assert sunset == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNSET, utc_today) + dawn = astral.dawn_utc(utc_today, latitude, longitude) + dusk = astral.dusk_utc(utc_today, latitude, longitude) + midnight = astral.solar_midnight_utc(utc_today, longitude) + noon = astral.solar_noon_utc(utc_today, longitude) + sunrise = astral.sunrise_utc(utc_today, latitude, longitude) + sunset = astral.sunset_utc(utc_today, latitude, longitude) - def test_date_events_accepts_datetime(self): - """Test retrieving next sun events.""" - utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC) - from astral import Astral + assert dawn == sun.get_astral_event_date( + hass, 'dawn', utc_now) + assert dusk == sun.get_astral_event_date( + hass, 'dusk', utc_now) + assert midnight == sun.get_astral_event_date( + hass, 'solar_midnight', utc_now) + assert noon == sun.get_astral_event_date( + hass, 'solar_noon', utc_now) + assert sunrise == sun.get_astral_event_date( + hass, SUN_EVENT_SUNRISE, utc_now) + assert sunset == sun.get_astral_event_date( + hass, SUN_EVENT_SUNSET, utc_now) - astral = Astral() - utc_today = utc_now.date() - latitude = self.hass.config.latitude - longitude = self.hass.config.longitude +def test_is_up(hass): + """Test retrieving next sun events.""" + utc_now = datetime(2016, 11, 1, 12, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + assert not sun.is_up(hass) - dawn = astral.dawn_utc(utc_today, latitude, longitude) - dusk = astral.dusk_utc(utc_today, latitude, longitude) - midnight = astral.solar_midnight_utc(utc_today, longitude) - noon = astral.solar_noon_utc(utc_today, longitude) - sunrise = astral.sunrise_utc(utc_today, latitude, longitude) - sunset = astral.sunset_utc(utc_today, latitude, longitude) + utc_now = datetime(2016, 11, 1, 18, 0, 0, tzinfo=dt_util.UTC) + with patch('homeassistant.helpers.condition.dt_util.utcnow', + return_value=utc_now): + assert sun.is_up(hass) - assert dawn == sun.get_astral_event_date( - self.hass, 'dawn', utc_now) - assert dusk == sun.get_astral_event_date( - self.hass, 'dusk', utc_now) - assert midnight == sun.get_astral_event_date( - self.hass, 'solar_midnight', utc_now) - assert noon == sun.get_astral_event_date( - self.hass, 'solar_noon', utc_now) - assert sunrise == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNRISE, utc_now) - assert sunset == sun.get_astral_event_date( - self.hass, SUN_EVENT_SUNSET, utc_now) - def test_is_up(self): - """Test retrieving next sun events.""" - utc_now = datetime(2016, 11, 1, 12, 0, 0, tzinfo=dt_util.UTC) - with patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=utc_now): - assert not sun.is_up(self.hass) +def test_norway_in_june(hass): + """Test location in Norway where the sun doesn't set in summer.""" + hass.config.latitude = 69.6 + hass.config.longitude = 18.8 - utc_now = datetime(2016, 11, 1, 18, 0, 0, tzinfo=dt_util.UTC) - with patch('homeassistant.helpers.condition.dt_util.utcnow', - return_value=utc_now): - assert sun.is_up(self.hass) + june = datetime(2016, 6, 1, tzinfo=dt_util.UTC) - def test_norway_in_june(self): - """Test location in Norway where the sun doesn't set in summer.""" - self.hass.config.latitude = 69.6 - self.hass.config.longitude = 18.8 + print(sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, + datetime(2017, 7, 25))) + print(sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, + datetime(2017, 7, 25))) - june = datetime(2016, 6, 1, tzinfo=dt_util.UTC) + print(sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, + datetime(2017, 7, 26))) + print(sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, + datetime(2017, 7, 26))) - print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, - datetime(2017, 7, 25))) - print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, - datetime(2017, 7, 25))) - - print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, - datetime(2017, 7, 26))) - print(sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, - datetime(2017, 7, 26))) - - assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNRISE, june) \ - == datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) - assert sun.get_astral_event_next(self.hass, SUN_EVENT_SUNSET, june) \ - == datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) - assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNRISE, june) \ - is None - assert sun.get_astral_event_date(self.hass, SUN_EVENT_SUNSET, june) \ - is None + assert sun.get_astral_event_next(hass, SUN_EVENT_SUNRISE, june) \ + == datetime(2016, 7, 25, 23, 23, 39, tzinfo=dt_util.UTC) + assert sun.get_astral_event_next(hass, SUN_EVENT_SUNSET, june) \ + == datetime(2016, 7, 26, 22, 19, 1, tzinfo=dt_util.UTC) + assert sun.get_astral_event_date(hass, SUN_EVENT_SUNRISE, june) \ + is None + assert sun.get_astral_event_date(hass, SUN_EVENT_SUNSET, june) \ + is None diff --git a/tests/helpers/test_temperature.py b/tests/helpers/test_temperature.py index f6bee7b12ae..a506288b627 100644 --- a/tests/helpers/test_temperature.py +++ b/tests/helpers/test_temperature.py @@ -1,50 +1,34 @@ """Tests Home Assistant temperature helpers.""" -import unittest - -from tests.common import get_test_home_assistant +import pytest from homeassistant.const import ( TEMP_CELSIUS, PRECISION_WHOLE, TEMP_FAHRENHEIT, PRECISION_HALVES, PRECISION_TENTHS) from homeassistant.helpers.temperature import display_temp -from homeassistant.util.unit_system import METRIC_SYSTEM -import pytest TEMP = 24.636626 -class TestHelpersTemperature(unittest.TestCase): - """Set up the temperature tests.""" +def test_temperature_not_a_number(hass): + """Test that temperature is a number.""" + temp = "Temperature" + with pytest.raises(Exception) as exception: + display_temp(hass, temp, TEMP_CELSIUS, PRECISION_HALVES) - def setUp(self): - """Set up the tests.""" - self.hass = get_test_home_assistant() - self.hass.config.unit_system = METRIC_SYSTEM + assert "Temperature is not a number: {}".format(temp) \ + in str(exception) - def tearDown(self): - """Stop down stuff we started.""" - self.hass.stop() - def test_temperature_not_a_number(self): - """Test that temperature is a number.""" - temp = "Temperature" - with pytest.raises(Exception) as exception: - display_temp(self.hass, temp, TEMP_CELSIUS, PRECISION_HALVES) +def test_celsius_halves(hass): + """Test temperature to celsius rounding to halves.""" + assert display_temp(hass, TEMP, TEMP_CELSIUS, PRECISION_HALVES) == 24.5 - assert "Temperature is not a number: {}".format(temp) \ - in str(exception) - def test_celsius_halves(self): - """Test temperature to celsius rounding to halves.""" - assert 24.5 == display_temp( - self.hass, TEMP, TEMP_CELSIUS, PRECISION_HALVES) +def test_celsius_tenths(hass): + """Test temperature to celsius rounding to tenths.""" + assert display_temp(hass, TEMP, TEMP_CELSIUS, PRECISION_TENTHS) == 24.6 - def test_celsius_tenths(self): - """Test temperature to celsius rounding to tenths.""" - assert 24.6 == display_temp( - self.hass, TEMP, TEMP_CELSIUS, PRECISION_TENTHS) - def test_fahrenheit_wholes(self): - """Test temperature to fahrenheit rounding to wholes.""" - assert -4 == display_temp( - self.hass, TEMP, TEMP_FAHRENHEIT, PRECISION_WHOLE) +def test_fahrenheit_wholes(hass): + """Test temperature to fahrenheit rounding to wholes.""" + assert display_temp(hass, TEMP, TEMP_FAHRENHEIT, PRECISION_WHOLE) == -4 diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index e0aeb09976d..d543bf65bb7 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1,12 +1,12 @@ """Test Home Assistant template helper methods.""" -import asyncio from datetime import datetime -import unittest import random import math -import pytz from unittest.mock import patch +import pytest +import pytz + from homeassistant.components import group from homeassistant.exceptions import TemplateError from homeassistant.helpers import template @@ -21,1021 +21,1030 @@ from homeassistant.const import ( ) import homeassistant.util.dt as dt_util -from tests.common import get_test_home_assistant -import pytest + +def _set_up_units(hass): + """Set up the tests.""" + hass.config.units = UnitSystem('custom', TEMP_CELSIUS, + LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS, PRESSURE_PA) -class TestHelpersTemplate(unittest.TestCase): - """Test the Template.""" +def test_referring_states_by_entity_id(hass): + """Test referring states by entity id.""" + hass.states.async_set('test.object', 'happy') + assert template.Template( + '{{ states.test.object.state }}', hass).async_render() == 'happy' - # pylint: disable=invalid-name - def setUp(self): - """Set up the tests.""" - self.hass = get_test_home_assistant() - self.hass.config.units = UnitSystem('custom', TEMP_CELSIUS, - LENGTH_METERS, VOLUME_LITERS, - MASS_GRAMS, PRESSURE_PA) - # pylint: disable=invalid-name - def tearDown(self): - """Stop down stuff we started.""" - self.hass.stop() +def test_iterating_all_states(hass): + """Test iterating all states.""" + hass.states.async_set('test.object', 'happy') + hass.states.async_set('sensor.temperature', 10) - def test_referring_states_by_entity_id(self): - """Test referring states by entity id.""" - self.hass.states.set('test.object', 'happy') - assert 'happy' == \ - template.Template( - '{{ states.test.object.state }}', self.hass).render() + assert template.Template( + '{% for state in states %}{{ state.state }}{% endfor %}', + hass).async_render() == '10happy' - def test_iterating_all_states(self): - """Test iterating all states.""" - self.hass.states.set('test.object', 'happy') - self.hass.states.set('sensor.temperature', 10) - assert '10happy' == \ - template.Template( - '{% for state in states %}{{ state.state }}{% endfor %}', - self.hass).render() +def test_iterating_domain_states(hass): + """Test iterating domain states.""" + hass.states.async_set('test.object', 'happy') + hass.states.async_set('sensor.back_door', 'open') + hass.states.async_set('sensor.temperature', 10) - def test_iterating_domain_states(self): - """Test iterating domain states.""" - self.hass.states.set('test.object', 'happy') - self.hass.states.set('sensor.back_door', 'open') - self.hass.states.set('sensor.temperature', 10) - - assert 'open10' == \ - template.Template(""" + assert template.Template(""" {% for state in states.sensor %}{{ state.state }}{% endfor %} - """, self.hass).render() + """, hass).async_render() == 'open10' - def test_float(self): - """Test float.""" - self.hass.states.set('sensor.temperature', '12') - assert '12.0' == \ - template.Template( - '{{ float(states.sensor.temperature.state) }}', - self.hass).render() +def test_float(hass): + """Test float.""" + hass.states.async_set('sensor.temperature', '12') - assert 'True' == \ - template.Template( - '{{ float(states.sensor.temperature.state) > 11 }}', - self.hass).render() + assert template.Template( + '{{ float(states.sensor.temperature.state) }}', + hass).async_render() == '12.0' - def test_rounding_value(self): - """Test rounding value.""" - self.hass.states.set('sensor.temperature', 12.78) + assert template.Template( + '{{ float(states.sensor.temperature.state) > 11 }}', + hass).async_render() == 'True' - assert '12.8' == \ - template.Template( - '{{ states.sensor.temperature.state | round(1) }}', - self.hass).render() - assert '128' == \ - template.Template( - '{{ states.sensor.temperature.state | multiply(10) | round }}', - self.hass).render() +def test_rounding_value(hass): + """Test rounding value.""" + hass.states.async_set('sensor.temperature', 12.78) - assert '12.7' == \ - template.Template( - '{{ states.sensor.temperature.state | round(1, "floor") }}', - self.hass).render() + assert template.Template( + '{{ states.sensor.temperature.state | round(1) }}', + hass).async_render() == '12.8' - assert '12.8' == \ - template.Template( - '{{ states.sensor.temperature.state | round(1, "ceil") }}', - self.hass).render() + assert template.Template( + '{{ states.sensor.temperature.state | multiply(10) | round }}', + hass).async_render() == '128' - def test_rounding_value_get_original_value_on_error(self): - """Test rounding value get original value on error.""" - assert 'None' == \ - template.Template('{{ None | round }}', self.hass).render() + assert template.Template( + '{{ states.sensor.temperature.state | round(1, "floor") }}', + hass).async_render() == '12.7' - assert 'no_number' == \ - template.Template( - '{{ "no_number" | round }}', self.hass).render() + assert template.Template( + '{{ states.sensor.temperature.state | round(1, "ceil") }}', + hass).async_render() == '12.8' - def test_multiply(self): - """Test multiply.""" - tests = { - None: 'None', - 10: '100', - '"abcd"': 'abcd' - } - for inp, out in tests.items(): - assert out == \ - template.Template('{{ %s | multiply(10) | round }}' % inp, - self.hass).render() +def test_rounding_value_get_original_value_on_error(hass): + """Test rounding value get original value on error.""" + assert template.Template('{{ None | round }}', hass).async_render() == \ + 'None' - def test_logarithm(self): - """Test logarithm.""" - tests = [ - (4, 2, '2.0'), - (1000, 10, '3.0'), - (math.e, '', '1.0'), - ('"invalid"', '_', 'invalid'), - (10, '"invalid"', '10.0'), - ] + assert template.Template( + '{{ "no_number" | round }}', hass).async_render() == 'no_number' - for value, base, expected in tests: - assert expected == \ - template.Template( - '{{ %s | log(%s) | round(1) }}' % (value, base), - self.hass).render() - assert expected == \ - template.Template( - '{{ log(%s, %s) | round(1) }}' % (value, base), - self.hass).render() +def test_multiply(hass): + """Test multiply.""" + tests = { + None: 'None', + 10: '100', + '"abcd"': 'abcd' + } - def test_sine(self): - """Test sine.""" - tests = [ - (0, '0.0'), - (math.pi / 2, '1.0'), - (math.pi, '0.0'), - (math.pi * 1.5, '-1.0'), - (math.pi / 10, '0.309') - ] + for inp, out in tests.items(): + assert template.Template('{{ %s | multiply(10) | round }}' % inp, + hass).async_render() == out - for value, expected in tests: - assert expected == \ - template.Template( - '{{ %s | sin | round(3) }}' % value, - self.hass).render() - def test_cos(self): - """Test cosine.""" - tests = [ - (0, '1.0'), - (math.pi / 2, '0.0'), - (math.pi, '-1.0'), - (math.pi * 1.5, '-0.0'), - (math.pi / 10, '0.951') - ] +def test_logarithm(hass): + """Test logarithm.""" + tests = [ + (4, 2, '2.0'), + (1000, 10, '3.0'), + (math.e, '', '1.0'), + ('"invalid"', '_', 'invalid'), + (10, '"invalid"', '10.0'), + ] - for value, expected in tests: - assert expected == \ - template.Template( - '{{ %s | cos | round(3) }}' % value, - self.hass).render() + for value, base, expected in tests: + assert template.Template( + '{{ %s | log(%s) | round(1) }}' % (value, base), + hass).async_render() == expected - def test_tan(self): - """Test tangent.""" - tests = [ - (0, '0.0'), - (math.pi, '-0.0'), - (math.pi / 180 * 45, '1.0'), - (math.pi / 180 * 90, '1.633123935319537e+16'), - (math.pi / 180 * 135, '-1.0') - ] + assert template.Template( + '{{ log(%s, %s) | round(1) }}' % (value, base), + hass).async_render() == expected - for value, expected in tests: - assert expected == \ - template.Template( - '{{ %s | tan | round(3) }}' % value, - self.hass).render() - def test_sqrt(self): - """Test square root.""" - tests = [ - (0, '0.0'), - (1, '1.0'), - (2, '1.414'), - (10, '3.162'), - (100, '10.0'), - ] +def test_sine(hass): + """Test sine.""" + tests = [ + (0, '0.0'), + (math.pi / 2, '1.0'), + (math.pi, '0.0'), + (math.pi * 1.5, '-1.0'), + (math.pi / 10, '0.309') + ] - for value, expected in tests: - assert expected == \ - template.Template( - '{{ %s | sqrt | round(3) }}' % value, - self.hass).render() + for value, expected in tests: + assert template.Template( + '{{ %s | sin | round(3) }}' % value, + hass).async_render() == expected - def test_strptime(self): - """Test the parse timestamp method.""" - tests = [ - ('2016-10-19 15:22:05.588122 UTC', - '%Y-%m-%d %H:%M:%S.%f %Z', None), - ('2016-10-19 15:22:05.588122+0100', - '%Y-%m-%d %H:%M:%S.%f%z', None), - ('2016-10-19 15:22:05.588122', - '%Y-%m-%d %H:%M:%S.%f', None), - ('2016-10-19', '%Y-%m-%d', None), - ('2016', '%Y', None), - ('15:22:05', '%H:%M:%S', None), - ('1469119144', '%Y', '1469119144'), - ('invalid', '%Y', 'invalid') - ] - for inp, fmt, expected in tests: - if expected is None: - expected = datetime.strptime(inp, fmt) +def test_cos(hass): + """Test cosine.""" + tests = [ + (0, '1.0'), + (math.pi / 2, '0.0'), + (math.pi, '-1.0'), + (math.pi * 1.5, '-0.0'), + (math.pi / 10, '0.951') + ] - temp = '{{ strptime(\'%s\', \'%s\') }}' % (inp, fmt) + for value, expected in tests: + assert template.Template( + '{{ %s | cos | round(3) }}' % value, + hass).async_render() == expected - assert str(expected) == \ - template.Template(temp, self.hass).render() - def test_timestamp_custom(self): - """Test the timestamps to custom filter.""" - now = dt_util.utcnow() - tests = [ - (None, None, None, 'None'), - (1469119144, None, True, '2016-07-21 16:39:04'), - (1469119144, '%Y', True, '2016'), - (1469119144, 'invalid', True, 'invalid'), - (dt_util.as_timestamp(now), None, False, - now.strftime('%Y-%m-%d %H:%M:%S')) - ] +def test_tan(hass): + """Test tangent.""" + tests = [ + (0, '0.0'), + (math.pi, '-0.0'), + (math.pi / 180 * 45, '1.0'), + (math.pi / 180 * 90, '1.633123935319537e+16'), + (math.pi / 180 * 135, '-1.0') + ] - for inp, fmt, local, out in tests: - if fmt: - fil = 'timestamp_custom(\'{}\')'.format(fmt) - elif fmt and local: - fil = 'timestamp_custom(\'{0}\', {1})'.format(fmt, local) - else: - fil = 'timestamp_custom' + for value, expected in tests: + assert template.Template( + '{{ %s | tan | round(3) }}' % value, + hass).async_render() == expected - assert out == template.Template( - '{{ %s | %s }}' % (inp, fil), self.hass).render() - def test_timestamp_local(self): - """Test the timestamps to local filter.""" - tests = { - None: 'None', - 1469119144: '2016-07-21 16:39:04', - } +def test_sqrt(hass): + """Test square root.""" + tests = [ + (0, '0.0'), + (1, '1.0'), + (2, '1.414'), + (10, '3.162'), + (100, '10.0'), + ] - for inp, out in tests.items(): - assert out == \ - template.Template('{{ %s | timestamp_local }}' % inp, - self.hass).render() + for value, expected in tests: + assert template.Template( + '{{ %s | sqrt | round(3) }}' % value, + hass).async_render() == expected - def test_min(self): - """Test the min filter.""" - assert '1' == \ - template.Template('{{ [1, 2, 3] | min }}', - self.hass).render() - def test_max(self): - """Test the max filter.""" - assert '3' == \ - template.Template('{{ [1, 2, 3] | max }}', - self.hass).render() +def test_strptime(hass): + """Test the parse timestamp method.""" + tests = [ + ('2016-10-19 15:22:05.588122 UTC', + '%Y-%m-%d %H:%M:%S.%f %Z', None), + ('2016-10-19 15:22:05.588122+0100', + '%Y-%m-%d %H:%M:%S.%f%z', None), + ('2016-10-19 15:22:05.588122', + '%Y-%m-%d %H:%M:%S.%f', None), + ('2016-10-19', '%Y-%m-%d', None), + ('2016', '%Y', None), + ('15:22:05', '%H:%M:%S', None), + ('1469119144', '%Y', '1469119144'), + ('invalid', '%Y', 'invalid') + ] - def test_base64_encode(self): - """Test the base64_encode filter.""" - self.assertEqual( - 'aG9tZWFzc2lzdGFudA==', - template.Template('{{ "homeassistant" | base64_encode }}', - self.hass).render()) + for inp, fmt, expected in tests: + if expected is None: + expected = datetime.strptime(inp, fmt) - def test_base64_decode(self): - """Test the base64_decode filter.""" - self.assertEqual( - 'homeassistant', - template.Template('{{ "aG9tZWFzc2lzdGFudA==" | base64_decode }}', - self.hass).render()) + temp = '{{ strptime(\'%s\', \'%s\') }}' % (inp, fmt) - def test_ordinal(self): - """Test the ordinal filter.""" - tests = [ - (1, '1st'), - (2, '2nd'), - (3, '3rd'), - (4, '4th'), - (5, '5th'), - ] + assert template.Template(temp, hass).async_render() == str(expected) - for value, expected in tests: - self.assertEqual( - expected, - template.Template( - '{{ %s | ordinal }}' % value, - self.hass).render()) - def test_timestamp_utc(self): - """Test the timestamps to local filter.""" - now = dt_util.utcnow() - tests = { - None: 'None', - 1469119144: '2016-07-21 16:39:04', - dt_util.as_timestamp(now): - now.strftime('%Y-%m-%d %H:%M:%S') - } +def test_timestamp_custom(hass): + """Test the timestamps to custom filter.""" + now = dt_util.utcnow() + tests = [ + (None, None, None, 'None'), + (1469119144, None, True, '2016-07-21 16:39:04'), + (1469119144, '%Y', True, '2016'), + (1469119144, 'invalid', True, 'invalid'), + (dt_util.as_timestamp(now), None, False, + now.strftime('%Y-%m-%d %H:%M:%S')) + ] - for inp, out in tests.items(): - assert out == \ - template.Template('{{ %s | timestamp_utc }}' % inp, - self.hass).render() + for inp, fmt, local, out in tests: + if fmt: + fil = 'timestamp_custom(\'{}\')'.format(fmt) + elif fmt and local: + fil = 'timestamp_custom(\'{0}\', {1})'.format(fmt, local) + else: + fil = 'timestamp_custom' - def test_as_timestamp(self): - """Test the as_timestamp function.""" - assert "None" == \ - template.Template( - '{{ as_timestamp("invalid") }}', self.hass).render() - self.hass.mock = None - assert "None" == \ - template.Template('{{ as_timestamp(states.mock) }}', - self.hass).render() + assert template.Template( + '{{ %s | %s }}' % (inp, fil), hass).async_render() == out - tpl = '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' \ - '"%Y-%m-%dT%H:%M:%S%z")) }}' - assert "1706951424.0" == \ - template.Template(tpl, self.hass).render() - @patch.object(random, 'choice') - def test_random_every_time(self, test_choice): - """Ensure the random filter runs every time, not just once.""" - tpl = template.Template('{{ [1,2] | random }}', self.hass) - test_choice.return_value = 'foo' - assert 'foo' == tpl.render() - test_choice.return_value = 'bar' - assert 'bar' == tpl.render() +def test_timestamp_local(hass): + """Test the timestamps to local filter.""" + tests = { + None: 'None', + 1469119144: '2016-07-21 16:39:04', + } - def test_passing_vars_as_keywords(self): - """Test passing variables as keywords.""" - assert '127' == \ - template.Template('{{ hello }}', self.hass).render(hello=127) + for inp, out in tests.items(): + assert template.Template('{{ %s | timestamp_local }}' % inp, + hass).async_render() == out - def test_passing_vars_as_vars(self): - """Test passing variables as variables.""" - assert '127' == \ - template.Template('{{ hello }}', self.hass).render({'hello': 127}) - def test_passing_vars_as_list(self): - """Test passing variables as list.""" - assert "['foo', 'bar']" == \ - template.render_complex(template.Template('{{ hello }}', - self.hass), {'hello': ['foo', 'bar']}) +def test_min(hass): + """Test the min filter.""" + assert template.Template('{{ [1, 2, 3] | min }}', + hass).async_render() == '1' - def test_passing_vars_as_list_element(self): - """Test passing variables as list.""" - assert 'bar' == \ - template.render_complex(template.Template('{{ hello[1] }}', - self.hass), - {'hello': ['foo', 'bar']}) - def test_passing_vars_as_dict_element(self): - """Test passing variables as list.""" - assert 'bar' == \ - template.render_complex(template.Template('{{ hello.foo }}', - self.hass), - {'hello': {'foo': 'bar'}}) +def test_max(hass): + """Test the max filter.""" + assert template.Template('{{ [1, 2, 3] | max }}', + hass).async_render() == '3' - def test_passing_vars_as_dict(self): - """Test passing variables as list.""" - assert "{'foo': 'bar'}" == \ - template.render_complex(template.Template('{{ hello }}', - self.hass), {'hello': {'foo': 'bar'}}) - def test_render_with_possible_json_value_with_valid_json(self): - """Render with possible JSON value with valid JSON.""" - tpl = template.Template('{{ value_json.hello }}', self.hass) - assert 'world' == \ - tpl.render_with_possible_json_value('{"hello": "world"}') +def test_base64_encode(hass): + """Test the base64_encode filter.""" + assert template.Template('{{ "homeassistant" | base64_encode }}', + hass).async_render() == 'aG9tZWFzc2lzdGFudA==' - def test_render_with_possible_json_value_with_invalid_json(self): - """Render with possible JSON value with invalid JSON.""" - tpl = template.Template('{{ value_json }}', self.hass) - assert '' == \ - tpl.render_with_possible_json_value('{ I AM NOT JSON }') - def test_render_with_possible_json_value_with_template_error_value(self): - """Render with possible JSON value with template error value.""" - tpl = template.Template('{{ non_existing.variable }}', self.hass) - assert '-' == \ - tpl.render_with_possible_json_value('hello', '-') +def test_base64_decode(hass): + """Test the base64_decode filter.""" + assert template.Template('{{ "aG9tZWFzc2lzdGFudA==" | base64_decode }}', + hass).async_render() == 'homeassistant' - def test_render_with_possible_json_value_with_missing_json_value(self): - """Render with possible JSON value with unknown JSON object.""" - tpl = template.Template('{{ value_json.goodbye }}', self.hass) - assert '' == \ - tpl.render_with_possible_json_value('{"hello": "world"}') - def test_render_with_possible_json_value_valid_with_is_defined(self): - """Render with possible JSON value with known JSON object.""" - tpl = template.Template('{{ value_json.hello|is_defined }}', self.hass) - assert 'world' == \ - tpl.render_with_possible_json_value('{"hello": "world"}') +def test_ordinal(hass): + """Test the ordinal filter.""" + tests = [ + (1, '1st'), + (2, '2nd'), + (3, '3rd'), + (4, '4th'), + (5, '5th'), + ] - def test_render_with_possible_json_value_undefined_json(self): - """Render with possible JSON value with unknown JSON object.""" - tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) - assert '{"hello": "world"}' == \ - tpl.render_with_possible_json_value('{"hello": "world"}') + for value, expected in tests: + assert template.Template( + '{{ %s | ordinal }}' % value, + hass).async_render() == expected - def test_render_with_possible_json_value_undefined_json_error_value(self): - """Render with possible JSON value with unknown JSON object.""" - tpl = template.Template('{{ value_json.bye|is_defined }}', self.hass) - assert '' == \ - tpl.render_with_possible_json_value('{"hello": "world"}', '') - def test_render_with_possible_json_value_non_string_value(self): - """Render with possible JSON value with non-string value.""" - tpl = template.Template(""" +def test_timestamp_utc(hass): + """Test the timestamps to local filter.""" + now = dt_util.utcnow() + tests = { + None: 'None', + 1469119144: '2016-07-21 16:39:04', + dt_util.as_timestamp(now): + now.strftime('%Y-%m-%d %H:%M:%S') + } + + for inp, out in tests.items(): + assert template.Template('{{ %s | timestamp_utc }}' % inp, + hass).async_render() == out + + +def test_as_timestamp(hass): + """Test the as_timestamp function.""" + assert template.Template('{{ as_timestamp("invalid") }}', + hass).async_render() == "None" + hass.mock = None + assert template.Template('{{ as_timestamp(states.mock) }}', + hass).async_render() == "None" + + tpl = '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' \ + '"%Y-%m-%dT%H:%M:%S%z")) }}' + assert template.Template(tpl, hass).async_render() == "1706951424.0" + + +@patch.object(random, 'choice') +def test_random_every_time(test_choice, hass): + """Ensure the random filter runs every time, not just once.""" + tpl = template.Template('{{ [1,2] | random }}', hass) + test_choice.return_value = 'foo' + assert tpl.async_render() == 'foo' + test_choice.return_value = 'bar' + assert tpl.async_render() == 'bar' + + +def test_passing_vars_as_keywords(hass): + """Test passing variables as keywords.""" + assert template.Template( + '{{ hello }}', hass).async_render(hello=127) == '127' + + +def test_passing_vars_as_vars(hass): + """Test passing variables as variables.""" + assert template.Template( + '{{ hello }}', hass).async_render({'hello': 127}) == '127' + + +def test_passing_vars_as_list(hass): + """Test passing variables as list.""" + assert template.render_complex( + template.Template('{{ hello }}', + hass), {'hello': ['foo', 'bar']}) == "['foo', 'bar']" + + +def test_passing_vars_as_list_element(hass): + """Test passing variables as list.""" + assert template.render_complex(template.Template('{{ hello[1] }}', + hass), + {'hello': ['foo', 'bar']}) == 'bar' + + +def test_passing_vars_as_dict_element(hass): + """Test passing variables as list.""" + assert template.render_complex(template.Template('{{ hello.foo }}', + hass), + {'hello': {'foo': 'bar'}}) == 'bar' + + +def test_passing_vars_as_dict(hass): + """Test passing variables as list.""" + assert template.render_complex( + template.Template('{{ hello }}', + hass), {'hello': {'foo': 'bar'}}) == "{'foo': 'bar'}" + + +def test_render_with_possible_json_value_with_valid_json(hass): + """Render with possible JSON value with valid JSON.""" + tpl = template.Template('{{ value_json.hello }}', hass) + assert tpl.async_render_with_possible_json_value( + '{"hello": "world"}') == 'world' + + +def test_render_with_possible_json_value_with_invalid_json(hass): + """Render with possible JSON value with invalid JSON.""" + tpl = template.Template('{{ value_json }}', hass) + assert tpl.async_render_with_possible_json_value('{ I AM NOT JSON }') == '' + + +def test_render_with_possible_json_value_with_template_error_value(hass): + """Render with possible JSON value with template error value.""" + tpl = template.Template('{{ non_existing.variable }}', hass) + assert tpl.async_render_with_possible_json_value('hello', '-') == '-' + + +def test_render_with_possible_json_value_with_missing_json_value(hass): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.goodbye }}', hass) + assert tpl.async_render_with_possible_json_value( + '{"hello": "world"}') == '' + + +def test_render_with_possible_json_value_valid_with_is_defined(hass): + """Render with possible JSON value with known JSON object.""" + tpl = template.Template('{{ value_json.hello|is_defined }}', hass) + assert tpl.async_render_with_possible_json_value( + '{"hello": "world"}') == 'world' + + +def test_render_with_possible_json_value_undefined_json(hass): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.bye|is_defined }}', hass) + assert tpl.async_render_with_possible_json_value( + '{"hello": "world"}') == '{"hello": "world"}' + + +def test_render_with_possible_json_value_undefined_json_error_value(hass): + """Render with possible JSON value with unknown JSON object.""" + tpl = template.Template('{{ value_json.bye|is_defined }}', hass) + assert tpl.async_render_with_possible_json_value( + '{"hello": "world"}', '') == '' + + +def test_render_with_possible_json_value_non_string_value(hass): + """Render with possible JSON value with non-string value.""" + tpl = template.Template(""" {{ strptime(value~'+0000', '%Y-%m-%d %H:%M:%S%z') }} - """, self.hass) - value = datetime(2019, 1, 18, 12, 13, 14) - expected = str(pytz.utc.localize(value)) - assert expected == \ - tpl.render_with_possible_json_value(value) + """, hass) + value = datetime(2019, 1, 18, 12, 13, 14) + expected = str(pytz.utc.localize(value)) + assert tpl.async_render_with_possible_json_value(value) == expected - def test_raise_exception_on_error(self): - """Test raising an exception on error.""" - with pytest.raises(TemplateError): - template.Template('{{ invalid_syntax').ensure_valid() - def test_if_state_exists(self): - """Test if state exists works.""" - self.hass.states.set('test.object', 'available') - tpl = template.Template( - '{% if states.test.object %}exists{% else %}not exists{% endif %}', - self.hass) - assert 'exists' == tpl.render() +def test_raise_exception_on_error(hass): + """Test raising an exception on error.""" + with pytest.raises(TemplateError): + template.Template('{{ invalid_syntax').ensure_valid() - def test_is_state(self): - """Test is_state method.""" - self.hass.states.set('test.object', 'available') - tpl = template.Template(""" + +def test_if_state_exists(hass): + """Test if state exists works.""" + hass.states.async_set('test.object', 'available') + tpl = template.Template( + '{% if states.test.object %}exists{% else %}not exists{% endif %}', + hass) + assert tpl.async_render() == 'exists' + + +def test_is_state(hass): + """Test is_state method.""" + hass.states.async_set('test.object', 'available') + tpl = template.Template(""" {% if is_state("test.object", "available") %}yes{% else %}no{% endif %} - """, self.hass) - assert 'yes' == tpl.render() + """, hass) + assert tpl.async_render() == 'yes' - tpl = template.Template(""" + tpl = template.Template(""" {{ is_state("test.noobject", "available") }} - """, self.hass) - assert 'False' == tpl.render() + """, hass) + assert tpl.async_render() == 'False' - def test_is_state_attr(self): - """Test is_state_attr method.""" - self.hass.states.set('test.object', 'available', {'mode': 'on'}) - tpl = template.Template(""" + +def test_is_state_attr(hass): + """Test is_state_attr method.""" + hass.states.async_set('test.object', 'available', {'mode': 'on'}) + tpl = template.Template(""" {% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %} - """, self.hass) - assert 'yes' == tpl.render() + """, hass) + assert tpl.async_render() == 'yes' - tpl = template.Template(""" + tpl = template.Template(""" {{ is_state_attr("test.noobject", "mode", "on") }} - """, self.hass) - assert 'False' == tpl.render() + """, hass) + assert tpl.async_render() == 'False' - def test_state_attr(self): - """Test state_attr method.""" - self.hass.states.set('test.object', 'available', {'mode': 'on'}) - tpl = template.Template(""" + +def test_state_attr(hass): + """Test state_attr method.""" + hass.states.async_set('test.object', 'available', {'mode': 'on'}) + tpl = template.Template(""" {% if state_attr("test.object", "mode") == "on" %}yes{% else %}no{% endif %} - """, self.hass) - assert 'yes' == tpl.render() + """, hass) + assert tpl.async_render() == 'yes' - tpl = template.Template(""" + tpl = template.Template(""" {{ state_attr("test.noobject", "mode") == None }} - """, self.hass) - assert 'True' == tpl.render() + """, hass) + assert tpl.async_render() == 'True' - def test_states_function(self): - """Test using states as a function.""" - self.hass.states.set('test.object', 'available') - tpl = template.Template('{{ states("test.object") }}', self.hass) - assert 'available' == tpl.render() - tpl2 = template.Template('{{ states("test.object2") }}', self.hass) - assert 'unknown' == tpl2.render() +def test_states_function(hass): + """Test using states as a function.""" + hass.states.async_set('test.object', 'available') + tpl = template.Template('{{ states("test.object") }}', hass) + assert tpl.async_render() == 'available' - @patch('homeassistant.helpers.template.TemplateEnvironment.' - 'is_safe_callable', return_value=True) - def test_now(self, mock_is_safe): - """Test now method.""" - now = dt_util.now() - with patch.dict(template.ENV.globals, {'now': lambda: now}): - assert now.isoformat() == \ - template.Template('{{ now().isoformat() }}', - self.hass).render() + tpl2 = template.Template('{{ states("test.object2") }}', hass) + assert tpl2.async_render() == 'unknown' - @patch('homeassistant.helpers.template.TemplateEnvironment.' - 'is_safe_callable', return_value=True) - def test_utcnow(self, mock_is_safe): - """Test utcnow method.""" - now = dt_util.utcnow() - with patch.dict(template.ENV.globals, {'utcnow': lambda: now}): - assert now.isoformat() == \ - template.Template('{{ utcnow().isoformat() }}', - self.hass).render() - def test_regex_match(self): - """Test regex_match method.""" - tpl = template.Template(r""" +@patch('homeassistant.helpers.template.TemplateEnvironment.' + 'is_safe_callable', return_value=True) +def test_now(mock_is_safe, hass): + """Test now method.""" + now = dt_util.now() + with patch.dict(template.ENV.globals, {'now': lambda: now}): + assert now.isoformat() == \ + template.Template('{{ now().isoformat() }}', + hass).async_render() + + +@patch('homeassistant.helpers.template.TemplateEnvironment.' + 'is_safe_callable', return_value=True) +def test_utcnow(mock_is_safe, hass): + """Test utcnow method.""" + now = dt_util.utcnow() + with patch.dict(template.ENV.globals, {'utcnow': lambda: now}): + assert now.isoformat() == \ + template.Template('{{ utcnow().isoformat() }}', + hass).async_render() + + +def test_regex_match(hass): + """Test regex_match method.""" + tpl = template.Template(r""" {{ '123-456-7890' | regex_match('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, self.hass) - assert 'True' == tpl.render() + """, hass) + assert tpl.async_render() == 'True' - tpl = template.Template(""" + tpl = template.Template(""" {{ 'home assistant test' | regex_match('Home', True) }} - """, self.hass) - assert 'True' == tpl.render() + """, hass) + assert tpl.async_render() == 'True' - tpl = template.Template(""" - {{ 'Another home assistant test' | regex_match('home') }} - """, self.hass) - assert 'False' == tpl.render() + tpl = template.Template(""" + {{ 'Another home assistant test' | regex_match('home') }} + """, hass) + assert tpl.async_render() == 'False' - def test_regex_search(self): - """Test regex_search method.""" - tpl = template.Template(r""" + +def test_regex_search(hass): + """Test regex_search method.""" + tpl = template.Template(r""" {{ '123-456-7890' | regex_search('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, self.hass) - assert 'True' == tpl.render() + """, hass) + assert tpl.async_render() == 'True' - tpl = template.Template(""" + tpl = template.Template(""" {{ 'home assistant test' | regex_search('Home', True) }} - """, self.hass) - assert 'True' == tpl.render() + """, hass) + assert tpl.async_render() == 'True' - tpl = template.Template(""" - {{ 'Another home assistant test' | regex_search('home') }} - """, self.hass) - assert 'True' == tpl.render() + tpl = template.Template(""" + {{ 'Another home assistant test' | regex_search('home') }} + """, hass) + assert tpl.async_render() == 'True' - def test_regex_replace(self): - """Test regex_replace method.""" - tpl = template.Template(r""" + +def test_regex_replace(hass): + """Test regex_replace method.""" + tpl = template.Template(r""" {{ 'Hello World' | regex_replace('(Hello\\s)',) }} - """, self.hass) - assert 'World' == tpl.render() + """, hass) + assert tpl.async_render() == 'World' - def test_regex_findall_index(self): - """Test regex_findall_index method.""" - tpl = template.Template(""" + +def test_regex_findall_index(hass): + """Test regex_findall_index method.""" + tpl = template.Template(""" {{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 0) }} - """, self.hass) - assert 'JFK' == tpl.render() + """, hass) + assert tpl.async_render() == 'JFK' - tpl = template.Template(""" + tpl = template.Template(""" {{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 1) }} - """, self.hass) - assert 'LHR' == tpl.render() + """, hass) + assert tpl.async_render() == 'LHR' - def test_bitwise_and(self): - """Test bitwise_and method.""" - tpl = template.Template(""" + +def test_bitwise_and(hass): + """Test bitwise_and method.""" + tpl = template.Template(""" {{ 8 | bitwise_and(8) }} - """, self.hass) - assert str(8 & 8) == tpl.render() - tpl = template.Template(""" + """, hass) + assert tpl.async_render() == str(8 & 8) + tpl = template.Template(""" {{ 10 | bitwise_and(2) }} - """, self.hass) - assert str(10 & 2) == tpl.render() - tpl = template.Template(""" + """, hass) + assert tpl.async_render() == str(10 & 2) + tpl = template.Template(""" {{ 8 | bitwise_and(2) }} - """, self.hass) - assert str(8 & 2) == tpl.render() + """, hass) + assert tpl.async_render() == str(8 & 2) - def test_bitwise_or(self): - """Test bitwise_or method.""" - tpl = template.Template(""" + +def test_bitwise_or(hass): + """Test bitwise_or method.""" + tpl = template.Template(""" {{ 8 | bitwise_or(8) }} - """, self.hass) - assert str(8 | 8) == tpl.render() - tpl = template.Template(""" + """, hass) + assert tpl.async_render() == str(8 | 8) + tpl = template.Template(""" {{ 10 | bitwise_or(2) }} - """, self.hass) - assert str(10 | 2) == tpl.render() - tpl = template.Template(""" + """, hass) + assert tpl.async_render() == str(10 | 2) + tpl = template.Template(""" {{ 8 | bitwise_or(2) }} - """, self.hass) - assert str(8 | 2) == tpl.render() + """, hass) + assert tpl.async_render() == str(8 | 2) - def test_distance_function_with_1_state(self): - """Test distance function with 1 state.""" - self.hass.states.set('test.object', 'happy', { - 'latitude': 32.87336, - 'longitude': -117.22943, - }) - tpl = template.Template('{{ distance(states.test.object) | round }}', - self.hass) - assert '187' == tpl.render() - def test_distance_function_with_2_states(self): - """Test distance function with 2 states.""" - self.hass.states.set('test.object', 'happy', { - 'latitude': 32.87336, - 'longitude': -117.22943, - }) - self.hass.states.set('test.object_2', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - tpl = template.Template( - '{{ distance(states.test.object, states.test.object_2) | round }}', - self.hass) - assert '187' == tpl.render() +def test_distance_function_with_1_state(hass): + """Test distance function with 1 state.""" + _set_up_units(hass) + hass.states.async_set('test.object', 'happy', { + 'latitude': 32.87336, + 'longitude': -117.22943, + }) + tpl = template.Template('{{ distance(states.test.object) | round }}', + hass) + assert tpl.async_render() == '187' - def test_distance_function_with_1_coord(self): - """Test distance function with 1 coord.""" - tpl = template.Template( - '{{ distance("32.87336", "-117.22943") | round }}', self.hass) - assert '187' == \ - tpl.render() - def test_distance_function_with_2_coords(self): - """Test distance function with 2 coords.""" - assert '187' == \ - template.Template( - '{{ distance("32.87336", "-117.22943", %s, %s) | round }}' - % (self.hass.config.latitude, self.hass.config.longitude), - self.hass).render() +def test_distance_function_with_2_states(hass): + """Test distance function with 2 states.""" + _set_up_units(hass) + hass.states.async_set('test.object', 'happy', { + 'latitude': 32.87336, + 'longitude': -117.22943, + }) + hass.states.async_set('test.object_2', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) + tpl = template.Template( + '{{ distance(states.test.object, states.test.object_2) | round }}', + hass) + assert tpl.async_render() == '187' - def test_distance_function_with_1_state_1_coord(self): - """Test distance function with 1 state 1 coord.""" - self.hass.states.set('test.object_2', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - tpl = template.Template( - '{{ distance("32.87336", "-117.22943", states.test.object_2) ' - '| round }}', self.hass) - assert '187' == tpl.render() - tpl2 = template.Template( - '{{ distance(states.test.object_2, "32.87336", "-117.22943") ' - '| round }}', self.hass) - assert '187' == tpl2.render() +def test_distance_function_with_1_coord(hass): + """Test distance function with 1 coord.""" + _set_up_units(hass) + tpl = template.Template( + '{{ distance("32.87336", "-117.22943") | round }}', hass) + assert tpl.async_render() == '187' - def test_distance_function_return_None_if_invalid_state(self): - """Test distance function return None if invalid state.""" - self.hass.states.set('test.object_2', 'happy', { - 'latitude': 10, - }) - tpl = template.Template('{{ distance(states.test.object_2) | round }}', - self.hass) - assert 'None' == \ - tpl.render() - def test_distance_function_return_None_if_invalid_coord(self): - """Test distance function return None if invalid coord.""" - assert 'None' == \ - template.Template( - '{{ distance("123", "abc") }}', self.hass).render() +def test_distance_function_with_2_coords(hass): + """Test distance function with 2 coords.""" + _set_up_units(hass) + assert template.Template( + '{{ distance("32.87336", "-117.22943", %s, %s) | round }}' + % (hass.config.latitude, hass.config.longitude), + hass).async_render() == '187' - assert 'None' == \ - template.Template('{{ distance("123") }}', self.hass).render() - self.hass.states.set('test.object_2', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - tpl = template.Template('{{ distance("123", states.test_object_2) }}', - self.hass) - assert 'None' == \ - tpl.render() +def test_distance_function_with_1_state_1_coord(hass): + """Test distance function with 1 state 1 coord.""" + _set_up_units(hass) + hass.states.async_set('test.object_2', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) + tpl = template.Template( + '{{ distance("32.87336", "-117.22943", states.test.object_2) ' + '| round }}', hass) + assert tpl.async_render() == '187' - def test_distance_function_with_2_entity_ids(self): - """Test distance function with 2 entity ids.""" - self.hass.states.set('test.object', 'happy', { - 'latitude': 32.87336, - 'longitude': -117.22943, - }) - self.hass.states.set('test.object_2', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - tpl = template.Template( - '{{ distance("test.object", "test.object_2") | round }}', - self.hass) - assert '187' == tpl.render() + tpl2 = template.Template( + '{{ distance(states.test.object_2, "32.87336", "-117.22943") ' + '| round }}', hass) + assert tpl2.async_render() == '187' - def test_distance_function_with_1_entity_1_coord(self): - """Test distance function with 1 entity_id and 1 coord.""" - self.hass.states.set('test.object', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - tpl = template.Template( - '{{ distance("test.object", "32.87336", "-117.22943") | round }}', - self.hass) - assert '187' == tpl.render() - def test_closest_function_home_vs_domain(self): - """Test closest function home vs domain.""" - self.hass.states.set('test_domain.object', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) +def test_distance_function_return_none_if_invalid_state(hass): + """Test distance function return None if invalid state.""" + hass.states.async_set('test.object_2', 'happy', { + 'latitude': 10, + }) + tpl = template.Template('{{ distance(states.test.object_2) | round }}', + hass) + assert tpl.async_render() == 'None' - self.hass.states.set('not_test_domain.but_closer', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - assert 'test_domain.object' == \ - template.Template('{{ closest(states.test_domain).entity_id }}', - self.hass).render() +def test_distance_function_return_none_if_invalid_coord(hass): + """Test distance function return None if invalid coord.""" + assert template.Template( + '{{ distance("123", "abc") }}', hass).async_render() == 'None' - def test_closest_function_home_vs_all_states(self): - """Test closest function home vs all states.""" - self.hass.states.set('test_domain.object', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) + assert template.Template('{{ distance("123") }}', hass).async_render() == \ + 'None' - self.hass.states.set('test_domain_2.and_closer', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) + hass.states.async_set('test.object_2', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) + tpl = template.Template('{{ distance("123", states.test_object_2) }}', + hass) + assert tpl.async_render() == 'None' - assert 'test_domain_2.and_closer' == \ - template.Template('{{ closest(states).entity_id }}', - self.hass).render() - def test_closest_function_home_vs_group_entity_id(self): - """Test closest function home vs group entity id.""" - self.hass.states.set('test_domain.object', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) +def test_distance_function_with_2_entity_ids(hass): + """Test distance function with 2 entity ids.""" + _set_up_units(hass) + hass.states.async_set('test.object', 'happy', { + 'latitude': 32.87336, + 'longitude': -117.22943, + }) + hass.states.async_set('test.object_2', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) + tpl = template.Template( + '{{ distance("test.object", "test.object_2") | round }}', + hass) + assert tpl.async_render() == '187' - self.hass.states.set('not_in_group.but_closer', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) - group.Group.create_group( - self.hass, 'location group', ['test_domain.object']) +def test_distance_function_with_1_entity_1_coord(hass): + """Test distance function with 1 entity_id and 1 coord.""" + _set_up_units(hass) + hass.states.async_set('test.object', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) + tpl = template.Template( + '{{ distance("test.object", "32.87336", "-117.22943") | round }}', + hass) + assert tpl.async_render() == '187' - assert 'test_domain.object' == \ - template.Template( - '{{ closest("group.location_group").entity_id }}', - self.hass).render() - def test_closest_function_home_vs_group_state(self): - """Test closest function home vs group state.""" - self.hass.states.set('test_domain.object', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) +def test_closest_function_home_vs_domain(hass): + """Test closest function home vs domain.""" + hass.states.async_set('test_domain.object', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - self.hass.states.set('not_in_group.but_closer', 'happy', { - 'latitude': self.hass.config.latitude, - 'longitude': self.hass.config.longitude, - }) + hass.states.async_set('not_test_domain.but_closer', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) - group.Group.create_group( - self.hass, 'location group', ['test_domain.object']) + assert template.Template('{{ closest(states.test_domain).entity_id }}', + hass).async_render() == 'test_domain.object' - assert 'test_domain.object' == \ - template.Template( - '{{ closest(states.group.location_group).entity_id }}', - self.hass).render() - def test_closest_function_to_coord(self): - """Test closest function to coord.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) +def test_closest_function_home_vs_all_states(hass): + """Test closest function home vs all states.""" + hass.states.async_set('test_domain.object', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - self.hass.states.set('test_domain.closest_zone', 'happy', { - 'latitude': self.hass.config.latitude + 0.2, - 'longitude': self.hass.config.longitude + 0.2, - }) + hass.states.async_set('test_domain_2.and_closer', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) - self.hass.states.set('zone.far_away', 'zoning', { - 'latitude': self.hass.config.latitude + 0.3, - 'longitude': self.hass.config.longitude + 0.3, - }) + assert template.Template('{{ closest(states).entity_id }}', + hass).async_render() == 'test_domain_2.and_closer' - tpl = template.Template( - '{{ closest("%s", %s, states.test_domain).entity_id }}' - % (self.hass.config.latitude + 0.3, - self.hass.config.longitude + 0.3), self.hass) - assert 'test_domain.closest_zone' == \ - tpl.render() +async def test_closest_function_home_vs_group_entity_id(hass): + """Test closest function home vs group entity id.""" + hass.states.async_set('test_domain.object', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - def test_closest_function_to_entity_id(self): - """Test closest function to entity id.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) + hass.states.async_set('not_in_group.but_closer', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) - self.hass.states.set('test_domain.closest_zone', 'happy', { - 'latitude': self.hass.config.latitude + 0.2, - 'longitude': self.hass.config.longitude + 0.2, - }) + await group.Group.async_create_group( + hass, 'location group', ['test_domain.object']) - self.hass.states.set('zone.far_away', 'zoning', { - 'latitude': self.hass.config.latitude + 0.3, - 'longitude': self.hass.config.longitude + 0.3, - }) + assert template.Template( + '{{ closest("group.location_group").entity_id }}', + hass).async_render() == 'test_domain.object' - assert 'test_domain.closest_zone' == \ - template.Template( - '{{ closest("zone.far_away", ' - 'states.test_domain).entity_id }}', self.hass).render() - def test_closest_function_to_state(self): - """Test closest function to state.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) +async def test_closest_function_home_vs_group_state(hass): + """Test closest function home vs group state.""" + hass.states.async_set('test_domain.object', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - self.hass.states.set('test_domain.closest_zone', 'happy', { - 'latitude': self.hass.config.latitude + 0.2, - 'longitude': self.hass.config.longitude + 0.2, - }) + hass.states.async_set('not_in_group.but_closer', 'happy', { + 'latitude': hass.config.latitude, + 'longitude': hass.config.longitude, + }) - self.hass.states.set('zone.far_away', 'zoning', { - 'latitude': self.hass.config.latitude + 0.3, - 'longitude': self.hass.config.longitude + 0.3, - }) + await group.Group.async_create_group( + hass, 'location group', ['test_domain.object']) - assert 'test_domain.closest_zone' == \ - template.Template( - '{{ closest(states.zone.far_away, ' - 'states.test_domain).entity_id }}', self.hass).render() + assert template.Template( + '{{ closest(states.group.location_group).entity_id }}', + hass).async_render() == 'test_domain.object' - def test_closest_function_invalid_state(self): - """Test closest function invalid state.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) - for state in ('states.zone.non_existing', '"zone.non_existing"'): - assert 'None' == \ - template.Template('{{ closest(%s, states) }}' % state, - self.hass).render() +def test_closest_function_to_coord(hass): + """Test closest function to coord.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - def test_closest_function_state_with_invalid_location(self): - """Test closest function state with invalid location.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': 'invalid latitude', - 'longitude': self.hass.config.longitude + 0.1, - }) + hass.states.async_set('test_domain.closest_zone', 'happy', { + 'latitude': hass.config.latitude + 0.2, + 'longitude': hass.config.longitude + 0.2, + }) - assert 'None' == \ - template.Template( + hass.states.async_set('zone.far_away', 'zoning', { + 'latitude': hass.config.latitude + 0.3, + 'longitude': hass.config.longitude + 0.3, + }) + + tpl = template.Template( + '{{ closest("%s", %s, states.test_domain).entity_id }}' + % (hass.config.latitude + 0.3, + hass.config.longitude + 0.3), hass) + + assert tpl.async_render() == 'test_domain.closest_zone' + + +def test_closest_function_to_entity_id(hass): + """Test closest function to entity id.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) + + hass.states.async_set('test_domain.closest_zone', 'happy', { + 'latitude': hass.config.latitude + 0.2, + 'longitude': hass.config.longitude + 0.2, + }) + + hass.states.async_set('zone.far_away', 'zoning', { + 'latitude': hass.config.latitude + 0.3, + 'longitude': hass.config.longitude + 0.3, + }) + + assert template.Template( + '{{ closest("zone.far_away", ' + 'states.test_domain).entity_id }}', hass).async_render() == \ + 'test_domain.closest_zone' + + +def test_closest_function_to_state(hass): + """Test closest function to state.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) + + hass.states.async_set('test_domain.closest_zone', 'happy', { + 'latitude': hass.config.latitude + 0.2, + 'longitude': hass.config.longitude + 0.2, + }) + + hass.states.async_set('zone.far_away', 'zoning', { + 'latitude': hass.config.latitude + 0.3, + 'longitude': hass.config.longitude + 0.3, + }) + + assert template.Template( + '{{ closest(states.zone.far_away, ' + 'states.test_domain).entity_id }}', hass).async_render() == \ + 'test_domain.closest_zone' + + +def test_closest_function_invalid_state(hass): + """Test closest function invalid state.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) + + for state in ('states.zone.non_existing', '"zone.non_existing"'): + assert template.Template('{{ closest(%s, states) }}' % state, + hass).async_render() == 'None' + + +def test_closest_function_state_with_invalid_location(hass): + """Test closest function state with invalid location.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': 'invalid latitude', + 'longitude': hass.config.longitude + 0.1, + }) + + assert template.Template( '{{ closest(states.test_domain.closest_home, ' - 'states) }}', self.hass).render() + 'states) }}', hass).async_render() == 'None' - def test_closest_function_invalid_coordinates(self): - """Test closest function invalid coordinates.""" - self.hass.states.set('test_domain.closest_home', 'happy', { - 'latitude': self.hass.config.latitude + 0.1, - 'longitude': self.hass.config.longitude + 0.1, - }) - assert 'None' == \ - template.Template('{{ closest("invalid", "coord", states) }}', - self.hass).render() +def test_closest_function_invalid_coordinates(hass): + """Test closest function invalid coordinates.""" + hass.states.async_set('test_domain.closest_home', 'happy', { + 'latitude': hass.config.latitude + 0.1, + 'longitude': hass.config.longitude + 0.1, + }) - def test_closest_function_no_location_states(self): - """Test closest function without location states.""" - assert '' == \ - template.Template('{{ closest(states).entity_id }}', - self.hass).render() + assert template.Template('{{ closest("invalid", "coord", states) }}', + hass).async_render() == 'None' - def test_extract_entities_none_exclude_stuff(self): - """Test extract entities function with none or exclude stuff.""" - assert [] == template.extract_entities(None) - assert [] == template.extract_entities("mdi:water") +def test_closest_function_no_location_states(hass): + """Test closest function without location states.""" + assert template.Template('{{ closest(states).entity_id }}', + hass).async_render() == '' - assert MATCH_ALL == \ - template.extract_entities( - '{{ closest(states.zone.far_away, ' - 'states.test_domain).entity_id }}') - assert MATCH_ALL == \ - template.extract_entities( - '{{ distance("123", states.test_object_2) }}') +def test_extract_entities_none_exclude_stuff(hass): + """Test extract entities function with none or exclude stuff.""" + assert template.extract_entities(None) == [] - def test_extract_entities_no_match_entities(self): - """Test extract entities function with none entities stuff.""" - assert MATCH_ALL == \ - template.extract_entities( - "{{ value_json.tst | timestamp_custom('%Y' True) }}") + assert template.extract_entities("mdi:water") == [] - assert MATCH_ALL == \ - template.extract_entities(""" + assert template.extract_entities( + '{{ closest(states.zone.far_away, ' + 'states.test_domain).entity_id }}') == MATCH_ALL + + assert template.extract_entities( + '{{ distance("123", states.test_object_2) }}') == MATCH_ALL + + +def test_extract_entities_no_match_entities(hass): + """Test extract entities function with none entities stuff.""" + assert template.extract_entities( + "{{ value_json.tst | timestamp_custom('%Y' True) }}") == MATCH_ALL + + assert template.extract_entities(""" {% for state in states.sensor %} - {{ state.entity_id }}={{ state.state }},d +{{ state.entity_id }}={{ state.state }},d {% endfor %} - """) + """) == MATCH_ALL - def test_extract_entities_match_entities(self): - """Test extract entities function with entities stuff.""" - assert ['device_tracker.phone_1'] == \ - template.extract_entities(""" + +def test_extract_entities_match_entities(hass): + """Test extract entities function with entities stuff.""" + assert template.extract_entities(""" {% if is_state('device_tracker.phone_1', 'home') %} - Ha, Hercules is home! +Ha, Hercules is home! {% else %} - Hercules is at {{ states('device_tracker.phone_1') }}. +Hercules is at {{ states('device_tracker.phone_1') }}. {% endif %} - """) + """) == ['device_tracker.phone_1'] - assert ['binary_sensor.garage_door'] == \ - template.extract_entities(""" + assert template.extract_entities(""" {{ as_timestamp(states.binary_sensor.garage_door.last_changed) }} - """) + """) == ['binary_sensor.garage_door'] - assert ['binary_sensor.garage_door'] == \ - template.extract_entities(""" + assert template.extract_entities(""" {{ states("binary_sensor.garage_door") }} - """) + """) == ['binary_sensor.garage_door'] - assert ['device_tracker.phone_2'] == \ - template.extract_entities(""" + assert template.extract_entities(""" {{ is_state_attr('device_tracker.phone_2', 'battery', 40) }} - """) + """) == ['device_tracker.phone_2'] - assert sorted([ - 'device_tracker.phone_1', - 'device_tracker.phone_2', - ]) == \ - sorted(template.extract_entities(""" + assert sorted([ + 'device_tracker.phone_1', + 'device_tracker.phone_2', + ]) == \ + sorted(template.extract_entities(""" {% if is_state('device_tracker.phone_1', 'home') %} - Ha, Hercules is home! +Ha, Hercules is home! {% elif states.device_tracker.phone_2.attributes.battery < 40 %} - Hercules you power goes done!. +Hercules you power goes done!. {% endif %} - """)) + """)) - assert sorted([ - 'sensor.pick_humidity', - 'sensor.pick_temperature', - ]) == \ - sorted(template.extract_entities(""" + assert sorted([ + 'sensor.pick_humidity', + 'sensor.pick_temperature', + ]) == \ + sorted(template.extract_entities(""" {{ - states.sensor.pick_temperature.state ~ „°C (“ ~ - states.sensor.pick_humidity.state ~ „ %“ +states.sensor.pick_temperature.state ~ „°C (“ ~ +states.sensor.pick_humidity.state ~ „ %“ }} - """)) + """)) - assert sorted([ - 'sensor.luftfeuchtigkeit_mean', - 'input_number.luftfeuchtigkeit', - ]) == \ - sorted(template.extract_entities( - "{% if (states('sensor.luftfeuchtigkeit_mean') | int)" - " > (states('input_number.luftfeuchtigkeit') | int +1.5)" - " %}true{% endif %}" - )) - - def test_extract_entities_with_variables(self): - """Test extract entities function with variables and entities stuff.""" - assert ['input_boolean.switch'] == \ - template.extract_entities( - "{{ is_state('input_boolean.switch', 'off') }}", {}) - - assert ['trigger.entity_id'] == \ - template.extract_entities( - "{{ is_state(trigger.entity_id, 'off') }}", {}) - - assert MATCH_ALL == \ - template.extract_entities( - "{{ is_state(data, 'off') }}", {}) - - assert ['input_boolean.switch'] == \ - template.extract_entities( - "{{ is_state(data, 'off') }}", - {'data': 'input_boolean.switch'}) - - assert ['input_boolean.switch'] == \ - template.extract_entities( - "{{ is_state(trigger.entity_id, 'off') }}", - {'trigger': {'entity_id': 'input_boolean.switch'}}) - - assert MATCH_ALL == \ - template.extract_entities( - "{{ is_state('media_player.' ~ where , 'playing') }}", - {'where': 'livingroom'}) - - def test_jinja_namespace(self): - """Test Jinja's namespace command can be used.""" - test_template = template.Template( - ( - "{% set ns = namespace(a_key='') %}" - "{% set ns.a_key = states.sensor.dummy.state %}" - "{{ ns.a_key }}" - ), - self.hass - ) - - self.hass.states.set('sensor.dummy', 'a value') - assert 'a value' == test_template.render() - - self.hass.states.set('sensor.dummy', 'another value') - assert 'another value' == test_template.render() + assert sorted([ + 'sensor.luftfeuchtigkeit_mean', + 'input_number.luftfeuchtigkeit', + ]) == \ + sorted(template.extract_entities( + "{% if (states('sensor.luftfeuchtigkeit_mean') | int)" + " > (states('input_number.luftfeuchtigkeit') | int +1.5)" + " %}true{% endif %}" + )) -@asyncio.coroutine -def test_state_with_unit(hass): +def test_extract_entities_with_variables(hass): + """Test extract entities function with variables and entities stuff.""" + assert template.extract_entities( + "{{ is_state('input_boolean.switch', 'off') }}", {}) == \ + ['input_boolean.switch'] + + assert template.extract_entities( + "{{ is_state(trigger.entity_id, 'off') }}", {}) == \ + ['trigger.entity_id'] + + assert template.extract_entities( + "{{ is_state(data, 'off') }}", {}) == MATCH_ALL + + assert template.extract_entities( + "{{ is_state(data, 'off') }}", + {'data': 'input_boolean.switch'}) == \ + ['input_boolean.switch'] + + assert template.extract_entities( + "{{ is_state(trigger.entity_id, 'off') }}", + {'trigger': {'entity_id': 'input_boolean.switch'}}) == \ + ['input_boolean.switch'] + + assert template.extract_entities( + "{{ is_state('media_player.' ~ where , 'playing') }}", + {'where': 'livingroom'}) == MATCH_ALL + + +def test_jinja_namespace(hass): + """Test Jinja's namespace command can be used.""" + test_template = template.Template( + ( + "{% set ns = namespace(a_key='') %}" + "{% set ns.a_key = states.sensor.dummy.state %}" + "{{ ns.a_key }}" + ), + hass + ) + + hass.states.async_set('sensor.dummy', 'a value') + assert test_template.async_render() == 'a value' + + hass.states.async_set('sensor.dummy', 'another value') + assert test_template.async_render() == 'another value' + + +async def test_state_with_unit(hass): """Test the state_with_unit property helper.""" hass.states.async_set('sensor.test', '23', { 'unit_of_measurement': 'beers', @@ -1064,8 +1073,7 @@ def test_state_with_unit(hass): assert tpl.async_render() == '' -@asyncio.coroutine -def test_length_of_states(hass): +async def test_length_of_states(hass): """Test fetching the length of states.""" hass.states.async_set('sensor.test', '23') hass.states.async_set('sensor.test2', 'wow') diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index dd0289d44be..b5c147c559f 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -1,8 +1,6 @@ """Test check_config script.""" -import asyncio import logging import os # noqa: F401 pylint: disable=unused-import -import unittest from unittest.mock import patch import homeassistant.scripts.check_config as check_config @@ -36,149 +34,138 @@ def normalize_yaml_files(check_dict): for key in sorted(check_dict['yaml_files'].keys())] -# pylint: disable=unsubscriptable-object -class TestCheckConfig(unittest.TestCase): - """Tests for the homeassistant.scripts.check_config module.""" +# pylint: disable=no-self-use,invalid-name +@patch('os.path.isfile', return_value=True) +def test_bad_core_config(isfile_patch, loop): + """Test a bad core config setup.""" + files = { + YAML_CONFIG_FILE: BAD_CORE_CONFIG, + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir()) + assert res['except'].keys() == {'homeassistant'} + assert res['except']['homeassistant'][1] == {'unit_system': 'bad'} - def setUp(self): - """Prepare the test.""" - # Somewhere in the tests our event loop gets killed, - # this ensures we have one. - try: - asyncio.get_event_loop() - except RuntimeError: - asyncio.set_event_loop(asyncio.new_event_loop()) - # Will allow seeing full diff - self.maxDiff = None # pylint: disable=invalid-name +@patch('os.path.isfile', return_value=True) +def test_config_platform_valid(isfile_patch, loop): + """Test a valid platform setup.""" + files = { + YAML_CONFIG_FILE: BASE_CONFIG + 'light:\n platform: demo', + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir()) + assert res['components'].keys() == {'homeassistant', 'light'} + assert res['components']['light'] == [{'platform': 'demo'}] + assert res['except'] == {} + assert res['secret_cache'] == {} + assert res['secrets'] == {} + assert len(res['yaml_files']) == 1 - # pylint: disable=no-self-use,invalid-name - @patch('os.path.isfile', return_value=True) - def test_bad_core_config(self, isfile_patch): - """Test a bad core config setup.""" - files = { - YAML_CONFIG_FILE: BAD_CORE_CONFIG, + +@patch('os.path.isfile', return_value=True) +def test_component_platform_not_found(isfile_patch, loop): + """Test errors if component or platform not found.""" + # Make sure they don't exist + files = { + YAML_CONFIG_FILE: BASE_CONFIG + 'beer:', + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir()) + assert res['components'].keys() == {'homeassistant'} + assert res['except'] == { + check_config.ERROR_STR: ['Integration not found: beer']} + assert res['secret_cache'] == {} + assert res['secrets'] == {} + assert len(res['yaml_files']) == 1 + + files = { + YAML_CONFIG_FILE: BASE_CONFIG + 'light:\n platform: beer', + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir()) + assert res['components'].keys() == {'homeassistant', 'light'} + assert res['components']['light'] == [] + assert res['except'] == { + check_config.ERROR_STR: [ + 'Integration beer not found when trying to verify its ' + 'light platform.', + ]} + assert res['secret_cache'] == {} + assert res['secrets'] == {} + assert len(res['yaml_files']) == 1 + + +@patch('os.path.isfile', return_value=True) +def test_secrets(isfile_patch, loop): + """Test secrets config checking method.""" + secrets_path = get_test_config_dir('secrets.yaml') + + files = { + get_test_config_dir(YAML_CONFIG_FILE): BASE_CONFIG + ( + 'http:\n' + ' api_password: !secret http_pw'), + secrets_path: ( + 'logger: debug\n' + 'http_pw: abc123'), + } + + with patch_yaml_files(files): + + res = check_config.check(get_test_config_dir(), True) + + assert res['except'] == {} + assert res['components'].keys() == {'homeassistant', 'http'} + assert res['components']['http'] == { + 'api_password': 'abc123', + 'cors_allowed_origins': [], + 'ip_ban_enabled': True, + 'login_attempts_threshold': -1, + 'server_host': '0.0.0.0', + 'server_port': 8123, + 'trusted_networks': [], + 'ssl_profile': 'modern', } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['except'].keys() == {'homeassistant'} - assert res['except']['homeassistant'][1] == {'unit_system': 'bad'} + assert res['secret_cache'] == {secrets_path: {'http_pw': 'abc123'}} + assert res['secrets'] == {'http_pw': 'abc123'} + assert normalize_yaml_files(res) == [ + '.../configuration.yaml', '.../secrets.yaml'] - @patch('os.path.isfile', return_value=True) - def test_config_platform_valid(self, isfile_patch): - """Test a valid platform setup.""" - files = { - YAML_CONFIG_FILE: BASE_CONFIG + 'light:\n platform: demo', - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['components'].keys() == {'homeassistant', 'light'} - assert res['components']['light'] == [{'platform': 'demo'}] - assert res['except'] == {} - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 - @patch('os.path.isfile', return_value=True) - def test_component_platform_not_found(self, isfile_patch): - """Test errors if component or platform not found.""" - # Make sure they don't exist - files = { - YAML_CONFIG_FILE: BASE_CONFIG + 'beer:', - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['components'].keys() == {'homeassistant'} - assert res['except'] == { - check_config.ERROR_STR: ['Integration not found: beer']} - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 +@patch('os.path.isfile', return_value=True) +def test_package_invalid(isfile_patch, loop): + """Test a valid platform setup.""" + files = { + YAML_CONFIG_FILE: BASE_CONFIG + ( + ' packages:\n' + ' p1:\n' + ' group: ["a"]'), + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir()) - files = { - YAML_CONFIG_FILE: BASE_CONFIG + 'light:\n platform: beer', - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - assert res['components'].keys() == {'homeassistant', 'light'} - assert res['components']['light'] == [] - assert res['except'] == { - check_config.ERROR_STR: [ - 'Integration beer not found when trying to verify its ' - 'light platform.', - ]} - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 + assert res['except'].keys() == {'homeassistant.packages.p1.group'} + assert res['except']['homeassistant.packages.p1.group'][1] == \ + {'group': ['a']} + assert len(res['except']) == 1 + assert res['components'].keys() == {'homeassistant'} + assert len(res['components']) == 1 + assert res['secret_cache'] == {} + assert res['secrets'] == {} + assert len(res['yaml_files']) == 1 - @patch('os.path.isfile', return_value=True) - def test_secrets(self, isfile_patch): - """Test secrets config checking method.""" - secrets_path = get_test_config_dir('secrets.yaml') - files = { - get_test_config_dir(YAML_CONFIG_FILE): BASE_CONFIG + ( - 'http:\n' - ' api_password: !secret http_pw'), - secrets_path: ( - 'logger: debug\n' - 'http_pw: abc123'), - } - - with patch_yaml_files(files): - - res = check_config.check(get_test_config_dir(), True) - - assert res['except'] == {} - assert res['components'].keys() == {'homeassistant', 'http'} - assert res['components']['http'] == { - 'api_password': 'abc123', - 'cors_allowed_origins': [], - 'ip_ban_enabled': True, - 'login_attempts_threshold': -1, - 'server_host': '0.0.0.0', - 'server_port': 8123, - 'trusted_networks': [], - 'ssl_profile': 'modern', - } - assert res['secret_cache'] == {secrets_path: {'http_pw': 'abc123'}} - assert res['secrets'] == {'http_pw': 'abc123'} - assert normalize_yaml_files(res) == [ - '.../configuration.yaml', '.../secrets.yaml'] - - @patch('os.path.isfile', return_value=True) - def test_package_invalid(self, isfile_patch): - """Test a valid platform setup.""" - files = { - YAML_CONFIG_FILE: BASE_CONFIG + ( - ' packages:\n' - ' p1:\n' - ' group: ["a"]'), - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir()) - - assert res['except'].keys() == {'homeassistant.packages.p1.group'} - assert res['except']['homeassistant.packages.p1.group'][1] == \ - {'group': ['a']} - assert len(res['except']) == 1 - assert res['components'].keys() == {'homeassistant'} - assert len(res['components']) == 1 - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert len(res['yaml_files']) == 1 - - def test_bootstrap_error(self): - """Test a valid platform setup.""" - files = { - YAML_CONFIG_FILE: BASE_CONFIG + 'automation: !include no.yaml', - } - with patch_yaml_files(files): - res = check_config.check(get_test_config_dir(YAML_CONFIG_FILE)) - err = res['except'].pop(check_config.ERROR_STR) - assert len(err) == 1 - assert res['except'] == {} - assert res['components'] == {} # No components, load failed - assert res['secret_cache'] == {} - assert res['secrets'] == {} - assert res['yaml_files'] == {} +def test_bootstrap_error(loop): + """Test a valid platform setup.""" + files = { + YAML_CONFIG_FILE: BASE_CONFIG + 'automation: !include no.yaml', + } + with patch_yaml_files(files): + res = check_config.check(get_test_config_dir(YAML_CONFIG_FILE)) + err = res['except'].pop(check_config.ERROR_STR) + assert len(err) == 1 + assert res['except'] == {} + assert res['components'] == {} # No components, load failed + assert res['secret_cache'] == {} + assert res['secrets'] == {} + assert res['yaml_files'] == {} diff --git a/tests/scripts/test_init.py b/tests/scripts/test_init.py index bfb98e90f24..38aea0cd992 100644 --- a/tests/scripts/test_init.py +++ b/tests/scripts/test_init.py @@ -1,19 +1,15 @@ """Test script init.""" -import unittest from unittest.mock import patch import homeassistant.scripts as scripts -class TestScripts(unittest.TestCase): - """Tests homeassistant.scripts module.""" - - @patch('homeassistant.scripts.get_default_config_dir', - return_value='/default') - def test_config_per_platform(self, mock_def): - """Test config per platform method.""" - self.assertEqual(scripts.get_default_config_dir(), '/default') - self.assertEqual(scripts.extract_config_dir(), '/default') - self.assertEqual(scripts.extract_config_dir(['']), '/default') - self.assertEqual(scripts.extract_config_dir(['-c', '/arg']), '/arg') - self.assertEqual(scripts.extract_config_dir(['--config', '/a']), '/a') +@patch('homeassistant.scripts.get_default_config_dir', + return_value='/default') +def test_config_per_platform(mock_def): + """Test config per platform method.""" + assert scripts.get_default_config_dir() == '/default' + assert scripts.extract_config_dir() == '/default' + assert scripts.extract_config_dir(['']) == '/default' + assert scripts.extract_config_dir(['-c', '/arg']) == '/arg' + assert scripts.extract_config_dir(['--config', '/a']) == '/a' diff --git a/tests/test_config.py b/tests/test_config.py index e9ca2a6c806..c5711cdfafe 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access import asyncio import os -import unittest import unittest.mock as mock from collections import OrderedDict from ipaddress import ip_network @@ -23,7 +22,6 @@ from homeassistant.const import ( CONF_AUTH_PROVIDERS, CONF_AUTH_MFA_MODULES) from homeassistant.util import location as location_util, dt as dt_util from homeassistant.util.yaml import SECRET_YAML -from homeassistant.util.async_ import run_coroutine_threadsafe from homeassistant.helpers.entity import Entity from homeassistant.components.config.group import ( CONFIG_PATH as GROUP_CONFIG_PATH) @@ -36,7 +34,7 @@ from homeassistant.components.config.customize import ( import homeassistant.scripts.check_config as check_config from tests.common import ( - get_test_config_dir, get_test_home_assistant, patch_yaml_files) + get_test_config_dir, patch_yaml_files) CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) @@ -55,512 +53,508 @@ def create_file(path): pass -class TestConfig(unittest.TestCase): - """Test the configutils.""" +def teardown(): + """Clean up.""" + dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE - # pylint: disable=invalid-name - def setUp(self): - """Initialize a test Home Assistant instance.""" - self.hass = get_test_home_assistant() + if os.path.isfile(YAML_PATH): + os.remove(YAML_PATH) - # pylint: disable=invalid-name - def tearDown(self): - """Clean up.""" - dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE + if os.path.isfile(SECRET_PATH): + os.remove(SECRET_PATH) - if os.path.isfile(YAML_PATH): - os.remove(YAML_PATH) + if os.path.isfile(VERSION_PATH): + os.remove(VERSION_PATH) - if os.path.isfile(SECRET_PATH): - os.remove(SECRET_PATH) + if os.path.isfile(GROUP_PATH): + os.remove(GROUP_PATH) - if os.path.isfile(VERSION_PATH): - os.remove(VERSION_PATH) + if os.path.isfile(AUTOMATIONS_PATH): + os.remove(AUTOMATIONS_PATH) - if os.path.isfile(GROUP_PATH): - os.remove(GROUP_PATH) + if os.path.isfile(SCRIPTS_PATH): + os.remove(SCRIPTS_PATH) - if os.path.isfile(AUTOMATIONS_PATH): - os.remove(AUTOMATIONS_PATH) + if os.path.isfile(CUSTOMIZE_PATH): + os.remove(CUSTOMIZE_PATH) - if os.path.isfile(SCRIPTS_PATH): - os.remove(SCRIPTS_PATH) - if os.path.isfile(CUSTOMIZE_PATH): - os.remove(CUSTOMIZE_PATH) +def test_create_default_config(): + """Test creation of default config.""" + config_util.create_default_config(CONFIG_DIR, False) - self.hass.stop() + assert os.path.isfile(YAML_PATH) + assert os.path.isfile(SECRET_PATH) + assert os.path.isfile(VERSION_PATH) + assert os.path.isfile(GROUP_PATH) + assert os.path.isfile(AUTOMATIONS_PATH) + assert os.path.isfile(CUSTOMIZE_PATH) - # pylint: disable=no-self-use - def test_create_default_config(self): - """Test creation of default config.""" - config_util.create_default_config(CONFIG_DIR, False) - assert os.path.isfile(YAML_PATH) - assert os.path.isfile(SECRET_PATH) - assert os.path.isfile(VERSION_PATH) - assert os.path.isfile(GROUP_PATH) - assert os.path.isfile(AUTOMATIONS_PATH) - assert os.path.isfile(CUSTOMIZE_PATH) +def test_find_config_file_yaml(): + """Test if it finds a YAML config file.""" + create_file(YAML_PATH) - def test_find_config_file_yaml(self): - """Test if it finds a YAML config file.""" - create_file(YAML_PATH) + assert YAML_PATH == config_util.find_config_file(CONFIG_DIR) - assert YAML_PATH == config_util.find_config_file(CONFIG_DIR) - @mock.patch('builtins.print') - def test_ensure_config_exists_creates_config(self, mock_print): - """Test that calling ensure_config_exists. +@mock.patch('builtins.print') +def test_ensure_config_exists_creates_config(mock_print): + """Test that calling ensure_config_exists. - If not creates a new config file. - """ - config_util.ensure_config_exists(CONFIG_DIR, False) + If not creates a new config file. + """ + config_util.ensure_config_exists(CONFIG_DIR, False) - assert os.path.isfile(YAML_PATH) - assert mock_print.called + assert os.path.isfile(YAML_PATH) + assert mock_print.called - def test_ensure_config_exists_uses_existing_config(self): - """Test that calling ensure_config_exists uses existing config.""" - create_file(YAML_PATH) - config_util.ensure_config_exists(CONFIG_DIR, False) - with open(YAML_PATH) as f: - content = f.read() +def test_ensure_config_exists_uses_existing_config(): + """Test that calling ensure_config_exists uses existing config.""" + create_file(YAML_PATH) + config_util.ensure_config_exists(CONFIG_DIR, False) - # File created with create_file are empty - assert '' == content + with open(YAML_PATH) as f: + content = f.read() - def test_load_yaml_config_converts_empty_files_to_dict(self): - """Test that loading an empty file returns an empty dict.""" - create_file(YAML_PATH) + # File created with create_file are empty + assert content == '' - assert isinstance(config_util.load_yaml_config_file(YAML_PATH), dict) - def test_load_yaml_config_raises_error_if_not_dict(self): - """Test error raised when YAML file is not a dict.""" - with open(YAML_PATH, 'w') as f: - f.write('5') +def test_load_yaml_config_converts_empty_files_to_dict(): + """Test that loading an empty file returns an empty dict.""" + create_file(YAML_PATH) - with pytest.raises(HomeAssistantError): - config_util.load_yaml_config_file(YAML_PATH) + assert isinstance(config_util.load_yaml_config_file(YAML_PATH), dict) - def test_load_yaml_config_raises_error_if_malformed_yaml(self): - """Test error raised if invalid YAML.""" - with open(YAML_PATH, 'w') as f: - f.write(':') - with pytest.raises(HomeAssistantError): - config_util.load_yaml_config_file(YAML_PATH) +def test_load_yaml_config_raises_error_if_not_dict(): + """Test error raised when YAML file is not a dict.""" + with open(YAML_PATH, 'w') as f: + f.write('5') - def test_load_yaml_config_raises_error_if_unsafe_yaml(self): - """Test error raised if unsafe YAML.""" - with open(YAML_PATH, 'w') as f: - f.write('hello: !!python/object/apply:os.system') + with pytest.raises(HomeAssistantError): + config_util.load_yaml_config_file(YAML_PATH) - with pytest.raises(HomeAssistantError): - config_util.load_yaml_config_file(YAML_PATH) - def test_load_yaml_config_preserves_key_order(self): - """Test removal of library.""" - with open(YAML_PATH, 'w') as f: - f.write('hello: 2\n') - f.write('world: 1\n') +def test_load_yaml_config_raises_error_if_malformed_yaml(): + """Test error raised if invalid YAML.""" + with open(YAML_PATH, 'w') as f: + f.write(':') - assert [('hello', 2), ('world', 1)] == \ - list(config_util.load_yaml_config_file(YAML_PATH).items()) + with pytest.raises(HomeAssistantError): + config_util.load_yaml_config_file(YAML_PATH) - @mock.patch('homeassistant.util.location.detect_location_info', - return_value=location_util.LocationInfo( - '0.0.0.0', 'US', 'United States', 'CA', 'California', - 'San Diego', '92122', 'America/Los_Angeles', 32.8594, - -117.2073, True)) - @mock.patch('homeassistant.util.location.elevation', return_value=101) - @mock.patch('builtins.print') - def test_create_default_config_detect_location(self, mock_detect, - mock_elev, mock_print): - """Test that detect location sets the correct config keys.""" - config_util.ensure_config_exists(CONFIG_DIR) - config = config_util.load_yaml_config_file(YAML_PATH) +def test_load_yaml_config_raises_error_if_unsafe_yaml(): + """Test error raised if unsafe YAML.""" + with open(YAML_PATH, 'w') as f: + f.write('hello: !!python/object/apply:os.system') - assert DOMAIN in config + with pytest.raises(HomeAssistantError): + config_util.load_yaml_config_file(YAML_PATH) - ha_conf = config[DOMAIN] - expected_values = { - CONF_LATITUDE: 32.8594, - CONF_LONGITUDE: -117.2073, - CONF_ELEVATION: 101, - CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, - CONF_NAME: 'Home', - CONF_TIME_ZONE: 'America/Los_Angeles', - CONF_CUSTOMIZE: OrderedDict(), - } +def test_load_yaml_config_preserves_key_order(): + """Test removal of library.""" + with open(YAML_PATH, 'w') as f: + f.write('hello: 2\n') + f.write('world: 1\n') - assert expected_values == ha_conf - assert mock_print.called + assert [('hello', 2), ('world', 1)] == \ + list(config_util.load_yaml_config_file(YAML_PATH).items()) - @mock.patch('builtins.print') - def test_create_default_config_returns_none_if_write_error(self, - mock_print): - """Test the writing of a default configuration. - Non existing folder returns None. - """ - assert config_util.create_default_config( - os.path.join(CONFIG_DIR, 'non_existing_dir/'), False) is None - assert mock_print.called +@mock.patch('homeassistant.util.location.detect_location_info', + return_value=location_util.LocationInfo( + '0.0.0.0', 'US', 'United States', 'CA', 'California', + 'San Diego', '92122', 'America/Los_Angeles', 32.8594, + -117.2073, True)) +@mock.patch('homeassistant.util.location.elevation', return_value=101) +@mock.patch('builtins.print') +def test_create_default_config_detect_location(mock_detect, + mock_elev, mock_print): + """Test that detect location sets the correct config keys.""" + config_util.ensure_config_exists(CONFIG_DIR) - # pylint: disable=no-self-use - def test_core_config_schema(self): - """Test core config schema.""" - for value in ( - {CONF_UNIT_SYSTEM: 'K'}, - {'time_zone': 'non-exist'}, - {'latitude': '91'}, - {'longitude': -181}, - {'customize': 'bla'}, - {'customize': {'light.sensor': 100}}, - {'customize': {'entity_id': []}}, - ): - with pytest.raises(MultipleInvalid): - config_util.CORE_CONFIG_SCHEMA(value) + config = config_util.load_yaml_config_file(YAML_PATH) - config_util.CORE_CONFIG_SCHEMA({ - 'name': 'Test name', - 'latitude': '-23.45', - 'longitude': '123.45', - CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, - 'customize': { - 'sensor.temperature': { - 'hidden': True, - }, + assert DOMAIN in config + + ha_conf = config[DOMAIN] + + expected_values = { + CONF_LATITUDE: 32.8594, + CONF_LONGITUDE: -117.2073, + CONF_ELEVATION: 101, + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_NAME: 'Home', + CONF_TIME_ZONE: 'America/Los_Angeles', + CONF_CUSTOMIZE: OrderedDict(), + } + + assert expected_values == ha_conf + assert mock_print.called + + +@mock.patch('builtins.print') +def test_create_default_config_returns_none_if_write_error(mock_print): + """Test the writing of a default configuration. + + Non existing folder returns None. + """ + assert config_util.create_default_config( + os.path.join(CONFIG_DIR, 'non_existing_dir/'), False) is None + assert mock_print.called + + +def test_core_config_schema(): + """Test core config schema.""" + for value in ( + {CONF_UNIT_SYSTEM: 'K'}, + {'time_zone': 'non-exist'}, + {'latitude': '91'}, + {'longitude': -181}, + {'customize': 'bla'}, + {'customize': {'light.sensor': 100}}, + {'customize': {'entity_id': []}}, + ): + with pytest.raises(MultipleInvalid): + config_util.CORE_CONFIG_SCHEMA(value) + + config_util.CORE_CONFIG_SCHEMA({ + 'name': 'Test name', + 'latitude': '-23.45', + 'longitude': '123.45', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + 'customize': { + 'sensor.temperature': { + 'hidden': True, }, + }, + }) + + +def test_customize_dict_schema(): + """Test basic customize config validation.""" + values = ( + {ATTR_FRIENDLY_NAME: None}, + {ATTR_HIDDEN: '2'}, + {ATTR_ASSUMED_STATE: '2'}, + ) + + for val in values: + print(val) + with pytest.raises(MultipleInvalid): + config_util.CUSTOMIZE_DICT_SCHEMA(val) + + assert config_util.CUSTOMIZE_DICT_SCHEMA({ + ATTR_FRIENDLY_NAME: 2, + ATTR_HIDDEN: '1', + ATTR_ASSUMED_STATE: '0', + }) == { + ATTR_FRIENDLY_NAME: '2', + ATTR_HIDDEN: True, + ATTR_ASSUMED_STATE: False + } + + +def test_customize_glob_is_ordered(): + """Test that customize_glob preserves order.""" + conf = config_util.CORE_CONFIG_SCHEMA( + {'customize_glob': OrderedDict()}) + assert isinstance(conf['customize_glob'], OrderedDict) + + +async def _compute_state(hass, config): + await config_util.async_process_ha_core_config(hass, config) + + entity = Entity() + entity.entity_id = 'test.test' + entity.hass = hass + entity.schedule_update_ha_state() + + await hass.async_block_till_done() + + return hass.states.get('test.test') + + +async def test_entity_customization(hass): + """Test entity customization through configuration.""" + config = {CONF_LATITUDE: 50, + CONF_LONGITUDE: 50, + CONF_NAME: 'Test', + CONF_CUSTOMIZE: {'test.test': {'hidden': True}}} + + state = await _compute_state(hass, config) + + assert state.attributes['hidden'] + + +@mock.patch('homeassistant.config.shutil') +@mock.patch('homeassistant.config.os') +def test_remove_lib_on_upgrade(mock_os, mock_shutil, hass): + """Test removal of library on upgrade from before 0.50.""" + ha_version = '0.49.0' + mock_os.path.isdir = mock.Mock(return_value=True) + mock_open = mock.mock_open() + with mock.patch('homeassistant.config.open', mock_open, create=True): + opened_file = mock_open.return_value + # pylint: disable=no-member + opened_file.readline.return_value = ha_version + hass.config.path = mock.Mock() + config_util.process_ha_config_upgrade(hass) + hass_path = hass.config.path.return_value + + assert mock_os.path.isdir.call_count == 1 + assert mock_os.path.isdir.call_args == mock.call(hass_path) + assert mock_shutil.rmtree.call_count == 1 + assert mock_shutil.rmtree.call_args == mock.call(hass_path) + + +def test_process_config_upgrade(hass): + """Test update of version on upgrade.""" + ha_version = '0.92.0' + + mock_open = mock.mock_open() + with mock.patch('homeassistant.config.open', mock_open, create=True), \ + mock.patch.object(config_util, '__version__', '0.91.0'): + opened_file = mock_open.return_value + # pylint: disable=no-member + opened_file.readline.return_value = ha_version + + config_util.process_ha_config_upgrade(hass) + + assert opened_file.write.call_count == 1 + assert opened_file.write.call_args == mock.call('0.91.0') + + +def test_config_upgrade_same_version(hass): + """Test no update of version on no upgrade.""" + ha_version = __version__ + + mock_open = mock.mock_open() + with mock.patch('homeassistant.config.open', mock_open, create=True): + opened_file = mock_open.return_value + # pylint: disable=no-member + opened_file.readline.return_value = ha_version + + config_util.process_ha_config_upgrade(hass) + + assert opened_file.write.call_count == 0 + + +@mock.patch('homeassistant.config.find_config_file', mock.Mock()) +def test_config_upgrade_no_file(hass): + """Test update of version on upgrade, with no version file.""" + mock_open = mock.mock_open() + mock_open.side_effect = [FileNotFoundError(), + mock.DEFAULT, + mock.DEFAULT] + with mock.patch('homeassistant.config.open', mock_open, create=True): + opened_file = mock_open.return_value + # pylint: disable=no-member + config_util.process_ha_config_upgrade(hass) + assert opened_file.write.call_count == 1 + assert opened_file.write.call_args == mock.call(__version__) + + +@mock.patch('homeassistant.config.shutil') +@mock.patch('homeassistant.config.os') +@mock.patch('homeassistant.config.find_config_file', mock.Mock()) +def test_migrate_file_on_upgrade(mock_os, mock_shutil, hass): + """Test migrate of config files on upgrade.""" + ha_version = '0.7.0' + + mock_os.path.isdir = mock.Mock(return_value=True) + + mock_open = mock.mock_open() + + def _mock_isfile(filename): + return True + + with mock.patch('homeassistant.config.open', mock_open, create=True), \ + mock.patch('homeassistant.config.os.path.isfile', _mock_isfile): + opened_file = mock_open.return_value + # pylint: disable=no-member + opened_file.readline.return_value = ha_version + + hass.config.path = mock.Mock() + + config_util.process_ha_config_upgrade(hass) + + assert mock_os.rename.call_count == 1 + + +@mock.patch('homeassistant.config.shutil') +@mock.patch('homeassistant.config.os') +@mock.patch('homeassistant.config.find_config_file', mock.Mock()) +def test_migrate_no_file_on_upgrade(mock_os, mock_shutil, hass): + """Test not migrating config files on upgrade.""" + ha_version = '0.7.0' + + mock_os.path.isdir = mock.Mock(return_value=True) + + mock_open = mock.mock_open() + + def _mock_isfile(filename): + return False + + with mock.patch('homeassistant.config.open', mock_open, create=True), \ + mock.patch('homeassistant.config.os.path.isfile', _mock_isfile): + opened_file = mock_open.return_value + # pylint: disable=no-member + opened_file.readline.return_value = ha_version + + hass.config.path = mock.Mock() + + config_util.process_ha_config_upgrade(hass) + + assert mock_os.rename.call_count == 0 + + +async def test_loading_configuration(hass): + """Test loading core config onto hass object.""" + hass.config = mock.Mock() + + await config_util.async_process_ha_core_config(hass, { + 'latitude': 60, + 'longitude': 50, + 'elevation': 25, + 'name': 'Huis', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, + 'time_zone': 'America/New_York', + 'whitelist_external_dirs': '/tmp', + }) + + assert hass.config.latitude == 60 + assert hass.config.longitude == 50 + assert hass.config.elevation == 25 + assert hass.config.location_name == 'Huis' + assert hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL + assert hass.config.time_zone.zone == 'America/New_York' + assert len(hass.config.whitelist_external_dirs) == 2 + assert '/tmp' in hass.config.whitelist_external_dirs + + +async def test_loading_configuration_temperature_unit(hass): + """Test backward compatibility when loading core config.""" + hass.config = mock.Mock() + + await config_util.async_process_ha_core_config(hass, { + 'latitude': 60, + 'longitude': 50, + 'elevation': 25, + 'name': 'Huis', + CONF_TEMPERATURE_UNIT: 'C', + 'time_zone': 'America/New_York', + }) + + assert hass.config.latitude == 60 + assert hass.config.longitude == 50 + assert hass.config.elevation == 25 + assert hass.config.location_name == 'Huis' + assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC + assert hass.config.time_zone.zone == 'America/New_York' + + +async def test_loading_configuration_from_packages(hass): + """Test loading packages config onto hass object config.""" + hass.config = mock.Mock() + + await config_util.async_process_ha_core_config(hass, { + 'latitude': 39, + 'longitude': -1, + 'elevation': 500, + 'name': 'Huis', + CONF_TEMPERATURE_UNIT: 'C', + 'time_zone': 'Europe/Madrid', + 'packages': { + 'package_1': {'wake_on_lan': None}, + 'package_2': {'light': {'platform': 'hue'}, + 'media_extractor': None, + 'sun': None}}, + }) + + # Empty packages not allowed + with pytest.raises(MultipleInvalid): + await config_util.async_process_ha_core_config(hass, { + 'latitude': 39, + 'longitude': -1, + 'elevation': 500, + 'name': 'Huis', + CONF_TEMPERATURE_UNIT: 'C', + 'time_zone': 'Europe/Madrid', + 'packages': {'empty_package': None}, }) - def test_customize_dict_schema(self): - """Test basic customize config validation.""" - values = ( - {ATTR_FRIENDLY_NAME: None}, - {ATTR_HIDDEN: '2'}, - {ATTR_ASSUMED_STATE: '2'}, - ) - for val in values: - print(val) - with pytest.raises(MultipleInvalid): - config_util.CUSTOMIZE_DICT_SCHEMA(val) +@asynctest.mock.patch('homeassistant.util.location.detect_location_info', + autospec=True, return_value=location_util.LocationInfo( + '0.0.0.0', 'US', 'United States', 'CA', 'California', + 'San Diego', '92122', 'America/Los_Angeles', 32.8594, + -117.2073, True)) +@asynctest.mock.patch('homeassistant.util.location.elevation', + autospec=True, return_value=101) +async def test_discovering_configuration(mock_detect, mock_elevation, hass): + """Test auto discovery for missing core configs.""" + hass.config.latitude = None + hass.config.longitude = None + hass.config.elevation = None + hass.config.location_name = None + hass.config.time_zone = None - assert config_util.CUSTOMIZE_DICT_SCHEMA({ - ATTR_FRIENDLY_NAME: 2, - ATTR_HIDDEN: '1', - ATTR_ASSUMED_STATE: '0', - }) == { - ATTR_FRIENDLY_NAME: '2', - ATTR_HIDDEN: True, - ATTR_ASSUMED_STATE: False - } + await config_util.async_process_ha_core_config(hass, {}) - def test_customize_glob_is_ordered(self): - """Test that customize_glob preserves order.""" - conf = config_util.CORE_CONFIG_SCHEMA( - {'customize_glob': OrderedDict()}) - assert isinstance(conf['customize_glob'], OrderedDict) + assert hass.config.latitude == 32.8594 + assert hass.config.longitude == -117.2073 + assert hass.config.elevation == 101 + assert hass.config.location_name == 'San Diego' + assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC + assert hass.config.units.is_metric + assert hass.config.time_zone.zone == 'America/Los_Angeles' - def _compute_state(self, config): - run_coroutine_threadsafe( - config_util.async_process_ha_core_config(self.hass, config), - self.hass.loop).result() - entity = Entity() - entity.entity_id = 'test.test' - entity.hass = self.hass - entity.schedule_update_ha_state() +@asynctest.mock.patch('homeassistant.util.location.detect_location_info', + autospec=True, return_value=None) +@asynctest.mock.patch('homeassistant.util.location.elevation', return_value=0) +async def test_discovering_configuration_auto_detect_fails(mock_detect, + mock_elevation, + hass): + """Test config remains unchanged if discovery fails.""" + hass.config = Config() + hass.config.config_dir = "/test/config" - self.hass.block_till_done() + await config_util.async_process_ha_core_config(hass, {}) - return self.hass.states.get('test.test') + blankConfig = Config() + assert hass.config.latitude == blankConfig.latitude + assert hass.config.longitude == blankConfig.longitude + assert hass.config.elevation == blankConfig.elevation + assert hass.config.location_name == blankConfig.location_name + assert hass.config.units == blankConfig.units + assert hass.config.time_zone == blankConfig.time_zone + assert len(hass.config.whitelist_external_dirs) == 1 + assert "/test/config/www" in hass.config.whitelist_external_dirs - def test_entity_customization(self): - """Test entity customization through configuration.""" - config = {CONF_LATITUDE: 50, - CONF_LONGITUDE: 50, - CONF_NAME: 'Test', - CONF_CUSTOMIZE: {'test.test': {'hidden': True}}} - state = self._compute_state(config) +@asynctest.mock.patch( + 'homeassistant.scripts.check_config.check_ha_config_file') +async def test_check_ha_config_file_correct(mock_check, hass): + """Check that restart propagates to stop.""" + mock_check.return_value = check_config.HomeAssistantConfig() + assert await config_util.async_check_ha_config_file(hass) is None - assert state.attributes['hidden'] - @mock.patch('homeassistant.config.shutil') - @mock.patch('homeassistant.config.os') - def test_remove_lib_on_upgrade(self, mock_os, mock_shutil): - """Test removal of library on upgrade from before 0.50.""" - ha_version = '0.49.0' - mock_os.path.isdir = mock.Mock(return_value=True) - mock_open = mock.mock_open() - with mock.patch('homeassistant.config.open', mock_open, create=True): - opened_file = mock_open.return_value - # pylint: disable=no-member - opened_file.readline.return_value = ha_version - self.hass.config.path = mock.Mock() - config_util.process_ha_config_upgrade(self.hass) - hass_path = self.hass.config.path.return_value +@asynctest.mock.patch( + 'homeassistant.scripts.check_config.check_ha_config_file') +async def test_check_ha_config_file_wrong(mock_check, hass): + """Check that restart with a bad config doesn't propagate to stop.""" + mock_check.return_value = check_config.HomeAssistantConfig() + mock_check.return_value.add_error("bad") - assert mock_os.path.isdir.call_count == 1 - assert mock_os.path.isdir.call_args == mock.call(hass_path) - assert mock_shutil.rmtree.call_count == 1 - assert mock_shutil.rmtree.call_args == mock.call(hass_path) - - def test_process_config_upgrade(self): - """Test update of version on upgrade.""" - ha_version = '0.92.0' - - mock_open = mock.mock_open() - with mock.patch('homeassistant.config.open', mock_open, create=True), \ - mock.patch.object(config_util, '__version__', '0.91.0'): - opened_file = mock_open.return_value - # pylint: disable=no-member - opened_file.readline.return_value = ha_version - - config_util.process_ha_config_upgrade(self.hass) - - assert opened_file.write.call_count == 1 - assert opened_file.write.call_args == mock.call('0.91.0') - - def test_config_upgrade_same_version(self): - """Test no update of version on no upgrade.""" - ha_version = __version__ - - mock_open = mock.mock_open() - with mock.patch('homeassistant.config.open', mock_open, create=True): - opened_file = mock_open.return_value - # pylint: disable=no-member - opened_file.readline.return_value = ha_version - - config_util.process_ha_config_upgrade(self.hass) - - assert opened_file.write.call_count == 0 - - @mock.patch('homeassistant.config.find_config_file', mock.Mock()) - def test_config_upgrade_no_file(self): - """Test update of version on upgrade, with no version file.""" - mock_open = mock.mock_open() - mock_open.side_effect = [FileNotFoundError(), - mock.DEFAULT, - mock.DEFAULT] - with mock.patch('homeassistant.config.open', mock_open, create=True): - opened_file = mock_open.return_value - # pylint: disable=no-member - config_util.process_ha_config_upgrade(self.hass) - assert opened_file.write.call_count == 1 - assert opened_file.write.call_args == mock.call(__version__) - - @mock.patch('homeassistant.config.shutil') - @mock.patch('homeassistant.config.os') - @mock.patch('homeassistant.config.find_config_file', mock.Mock()) - def test_migrate_file_on_upgrade(self, mock_os, mock_shutil): - """Test migrate of config files on upgrade.""" - ha_version = '0.7.0' - - mock_os.path.isdir = mock.Mock(return_value=True) - - mock_open = mock.mock_open() - - def _mock_isfile(filename): - return True - - with mock.patch('homeassistant.config.open', mock_open, create=True), \ - mock.patch( - 'homeassistant.config.os.path.isfile', _mock_isfile): - opened_file = mock_open.return_value - # pylint: disable=no-member - opened_file.readline.return_value = ha_version - - self.hass.config.path = mock.Mock() - - config_util.process_ha_config_upgrade(self.hass) - - assert mock_os.rename.call_count == 1 - - @mock.patch('homeassistant.config.shutil') - @mock.patch('homeassistant.config.os') - @mock.patch('homeassistant.config.find_config_file', mock.Mock()) - def test_migrate_no_file_on_upgrade(self, mock_os, mock_shutil): - """Test not migrating config files on upgrade.""" - ha_version = '0.7.0' - - mock_os.path.isdir = mock.Mock(return_value=True) - - mock_open = mock.mock_open() - - def _mock_isfile(filename): - return False - - with mock.patch('homeassistant.config.open', mock_open, create=True), \ - mock.patch( - 'homeassistant.config.os.path.isfile', _mock_isfile): - opened_file = mock_open.return_value - # pylint: disable=no-member - opened_file.readline.return_value = ha_version - - self.hass.config.path = mock.Mock() - - config_util.process_ha_config_upgrade(self.hass) - - assert mock_os.rename.call_count == 0 - - def test_loading_configuration(self): - """Test loading core config onto hass object.""" - self.hass.config = mock.Mock() - - run_coroutine_threadsafe( - config_util.async_process_ha_core_config(self.hass, { - 'latitude': 60, - 'longitude': 50, - 'elevation': 25, - 'name': 'Huis', - CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, - 'time_zone': 'America/New_York', - 'whitelist_external_dirs': '/tmp', - }), self.hass.loop).result() - - assert self.hass.config.latitude == 60 - assert self.hass.config.longitude == 50 - assert self.hass.config.elevation == 25 - assert self.hass.config.location_name == 'Huis' - assert self.hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL - assert self.hass.config.time_zone.zone == 'America/New_York' - assert len(self.hass.config.whitelist_external_dirs) == 2 - assert '/tmp' in self.hass.config.whitelist_external_dirs - - def test_loading_configuration_temperature_unit(self): - """Test backward compatibility when loading core config.""" - self.hass.config = mock.Mock() - - run_coroutine_threadsafe( - config_util.async_process_ha_core_config(self.hass, { - 'latitude': 60, - 'longitude': 50, - 'elevation': 25, - 'name': 'Huis', - CONF_TEMPERATURE_UNIT: 'C', - 'time_zone': 'America/New_York', - }), self.hass.loop).result() - - assert self.hass.config.latitude == 60 - assert self.hass.config.longitude == 50 - assert self.hass.config.elevation == 25 - assert self.hass.config.location_name == 'Huis' - assert self.hass.config.units.name == CONF_UNIT_SYSTEM_METRIC - assert self.hass.config.time_zone.zone == 'America/New_York' - - def test_loading_configuration_from_packages(self): - """Test loading packages config onto hass object config.""" - self.hass.config = mock.Mock() - - run_coroutine_threadsafe( - config_util.async_process_ha_core_config(self.hass, { - 'latitude': 39, - 'longitude': -1, - 'elevation': 500, - 'name': 'Huis', - CONF_TEMPERATURE_UNIT: 'C', - 'time_zone': 'Europe/Madrid', - 'packages': { - 'package_1': {'wake_on_lan': None}, - 'package_2': {'light': {'platform': 'hue'}, - 'media_extractor': None, - 'sun': None}}, - }), self.hass.loop).result() - - # Empty packages not allowed - with pytest.raises(MultipleInvalid): - run_coroutine_threadsafe( - config_util.async_process_ha_core_config(self.hass, { - 'latitude': 39, - 'longitude': -1, - 'elevation': 500, - 'name': 'Huis', - CONF_TEMPERATURE_UNIT: 'C', - 'time_zone': 'Europe/Madrid', - 'packages': {'empty_package': None}, - }), self.hass.loop).result() - - @mock.patch('homeassistant.util.location.detect_location_info', - autospec=True, return_value=location_util.LocationInfo( - '0.0.0.0', 'US', 'United States', 'CA', 'California', - 'San Diego', '92122', 'America/Los_Angeles', 32.8594, - -117.2073, True)) - @mock.patch('homeassistant.util.location.elevation', - autospec=True, return_value=101) - def test_discovering_configuration(self, mock_detect, mock_elevation): - """Test auto discovery for missing core configs.""" - self.hass.config.latitude = None - self.hass.config.longitude = None - self.hass.config.elevation = None - self.hass.config.location_name = None - self.hass.config.time_zone = None - - run_coroutine_threadsafe( - config_util.async_process_ha_core_config( - self.hass, {}), self.hass.loop - ).result() - - assert self.hass.config.latitude == 32.8594 - assert self.hass.config.longitude == -117.2073 - assert self.hass.config.elevation == 101 - assert self.hass.config.location_name == 'San Diego' - assert self.hass.config.units.name == CONF_UNIT_SYSTEM_METRIC - assert self.hass.config.units.is_metric - assert self.hass.config.time_zone.zone == 'America/Los_Angeles' - - @mock.patch('homeassistant.util.location.detect_location_info', - autospec=True, return_value=None) - @mock.patch('homeassistant.util.location.elevation', return_value=0) - def test_discovering_configuration_auto_detect_fails(self, mock_detect, - mock_elevation): - """Test config remains unchanged if discovery fails.""" - self.hass.config = Config() - self.hass.config.config_dir = "/test/config" - - run_coroutine_threadsafe( - config_util.async_process_ha_core_config( - self.hass, {}), self.hass.loop - ).result() - - blankConfig = Config() - assert self.hass.config.latitude == blankConfig.latitude - assert self.hass.config.longitude == blankConfig.longitude - assert self.hass.config.elevation == blankConfig.elevation - assert self.hass.config.location_name == blankConfig.location_name - assert self.hass.config.units == blankConfig.units - assert self.hass.config.time_zone == blankConfig.time_zone - assert len(self.hass.config.whitelist_external_dirs) == 1 - assert "/test/config/www" in self.hass.config.whitelist_external_dirs - - @asynctest.mock.patch( - 'homeassistant.scripts.check_config.check_ha_config_file') - def test_check_ha_config_file_correct(self, mock_check): - """Check that restart propagates to stop.""" - mock_check.return_value = check_config.HomeAssistantConfig() - assert run_coroutine_threadsafe( - config_util.async_check_ha_config_file(self.hass), - self.hass.loop - ).result() is None - - @asynctest.mock.patch( - 'homeassistant.scripts.check_config.check_ha_config_file') - def test_check_ha_config_file_wrong(self, mock_check): - """Check that restart with a bad config doesn't propagate to stop.""" - mock_check.return_value = check_config.HomeAssistantConfig() - mock_check.return_value.add_error("bad") - - assert run_coroutine_threadsafe( - config_util.async_check_ha_config_file(self.hass), - self.hass.loop - ).result() == 'bad' + assert await config_util.async_check_ha_config_file(hass) == 'bad' @asynctest.mock.patch('homeassistant.config.os.path.isfile', diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 14195d43821..ff382ae5c0a 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -1,10 +1,9 @@ """Test Home Assistant color util methods.""" -import unittest -import homeassistant.util.color as color_util - import pytest import voluptuous as vol +import homeassistant.util.color as color_util + GAMUT = color_util.GamutType(color_util.XYPoint(0.704, 0.296), color_util.XYPoint(0.2151, 0.7106), color_util.XYPoint(0.138, 0.08)) @@ -22,408 +21,340 @@ GAMUT_INVALID_4 = color_util.GamutType(color_util.XYPoint(0.1, 0.1), color_util.XYPoint(0.7, 0.7)) -class TestColorUtil(unittest.TestCase): - """Test color util methods.""" +# pylint: disable=invalid-name +def test_color_RGB_to_xy_brightness(): + """Test color_RGB_to_xy_brightness.""" + assert color_util.color_RGB_to_xy_brightness(0, 0, 0) == (0, 0, 0) + assert color_util.color_RGB_to_xy_brightness(255, 255, 255) == \ + (0.323, 0.329, 255) - # pylint: disable=invalid-name - def test_color_RGB_to_xy_brightness(self): - """Test color_RGB_to_xy_brightness.""" - assert (0, 0, 0) == \ - color_util.color_RGB_to_xy_brightness(0, 0, 0) - assert (0.323, 0.329, 255) == \ - color_util.color_RGB_to_xy_brightness(255, 255, 255) + assert color_util.color_RGB_to_xy_brightness(0, 0, 255) == \ + (0.136, 0.04, 12) - assert (0.136, 0.04, 12) == \ - color_util.color_RGB_to_xy_brightness(0, 0, 255) + assert color_util.color_RGB_to_xy_brightness(0, 255, 0) == \ + (0.172, 0.747, 170) - assert (0.172, 0.747, 170) == \ - color_util.color_RGB_to_xy_brightness(0, 255, 0) + assert color_util.color_RGB_to_xy_brightness(255, 0, 0) == \ + (0.701, 0.299, 72) - assert (0.701, 0.299, 72) == \ - color_util.color_RGB_to_xy_brightness(255, 0, 0) + assert color_util.color_RGB_to_xy_brightness(128, 0, 0) == \ + (0.701, 0.299, 16) - assert (0.701, 0.299, 16) == \ - color_util.color_RGB_to_xy_brightness(128, 0, 0) + assert color_util.color_RGB_to_xy_brightness(255, 0, 0, GAMUT) == \ + (0.7, 0.299, 72) - assert (0.7, 0.299, 72) == \ - color_util.color_RGB_to_xy_brightness(255, 0, 0, GAMUT) + assert color_util.color_RGB_to_xy_brightness(0, 255, 0, GAMUT) == \ + (0.215, 0.711, 170) - assert (0.215, 0.711, 170) == \ - color_util.color_RGB_to_xy_brightness(0, 255, 0, GAMUT) + assert color_util.color_RGB_to_xy_brightness(0, 0, 255, GAMUT) == \ + (0.138, 0.08, 12) - assert (0.138, 0.08, 12) == \ - color_util.color_RGB_to_xy_brightness(0, 0, 255, GAMUT) - def test_color_RGB_to_xy(self): - """Test color_RGB_to_xy.""" - assert (0, 0) == \ - color_util.color_RGB_to_xy(0, 0, 0) - assert (0.323, 0.329) == \ - color_util.color_RGB_to_xy(255, 255, 255) +def test_color_RGB_to_xy(): + """Test color_RGB_to_xy.""" + assert color_util.color_RGB_to_xy(0, 0, 0) == (0, 0) + assert color_util.color_RGB_to_xy(255, 255, 255) == (0.323, 0.329) - assert (0.136, 0.04) == \ - color_util.color_RGB_to_xy(0, 0, 255) + assert color_util.color_RGB_to_xy(0, 0, 255) == (0.136, 0.04) - assert (0.172, 0.747) == \ - color_util.color_RGB_to_xy(0, 255, 0) + assert color_util.color_RGB_to_xy(0, 255, 0) == (0.172, 0.747) - assert (0.701, 0.299) == \ - color_util.color_RGB_to_xy(255, 0, 0) + assert color_util.color_RGB_to_xy(255, 0, 0) == (0.701, 0.299) - assert (0.701, 0.299) == \ - color_util.color_RGB_to_xy(128, 0, 0) + assert color_util.color_RGB_to_xy(128, 0, 0) == (0.701, 0.299) - assert (0.138, 0.08) == \ - color_util.color_RGB_to_xy(0, 0, 255, GAMUT) + assert color_util.color_RGB_to_xy(0, 0, 255, GAMUT) == (0.138, 0.08) - assert (0.215, 0.711) == \ - color_util.color_RGB_to_xy(0, 255, 0, GAMUT) + assert color_util.color_RGB_to_xy(0, 255, 0, GAMUT) == (0.215, 0.711) - assert (0.7, 0.299) == \ - color_util.color_RGB_to_xy(255, 0, 0, GAMUT) + assert color_util.color_RGB_to_xy(255, 0, 0, GAMUT) == (0.7, 0.299) - def test_color_xy_brightness_to_RGB(self): - """Test color_xy_brightness_to_RGB.""" - assert (0, 0, 0) == \ - color_util.color_xy_brightness_to_RGB(1, 1, 0) - assert (194, 186, 169) == \ - color_util.color_xy_brightness_to_RGB(.35, .35, 128) +def test_color_xy_brightness_to_RGB(): + """Test color_xy_brightness_to_RGB.""" + assert color_util.color_xy_brightness_to_RGB(1, 1, 0) == (0, 0, 0) - assert (255, 243, 222) == \ - color_util.color_xy_brightness_to_RGB(.35, .35, 255) + assert color_util.color_xy_brightness_to_RGB(.35, .35, 128) == \ + (194, 186, 169) - assert (255, 0, 60) == \ - color_util.color_xy_brightness_to_RGB(1, 0, 255) + assert color_util.color_xy_brightness_to_RGB(.35, .35, 255) == \ + (255, 243, 222) - assert (0, 255, 0) == \ - color_util.color_xy_brightness_to_RGB(0, 1, 255) + assert color_util.color_xy_brightness_to_RGB(1, 0, 255) == (255, 0, 60) - assert (0, 63, 255) == \ - color_util.color_xy_brightness_to_RGB(0, 0, 255) + assert color_util.color_xy_brightness_to_RGB(0, 1, 255) == (0, 255, 0) - assert (255, 0, 3) == \ - color_util.color_xy_brightness_to_RGB(1, 0, 255, GAMUT) + assert color_util.color_xy_brightness_to_RGB(0, 0, 255) == (0, 63, 255) - assert (82, 255, 0) == \ - color_util.color_xy_brightness_to_RGB(0, 1, 255, GAMUT) + assert color_util.color_xy_brightness_to_RGB(1, 0, 255, GAMUT) == \ + (255, 0, 3) - assert (9, 85, 255) == \ - color_util.color_xy_brightness_to_RGB(0, 0, 255, GAMUT) + assert color_util.color_xy_brightness_to_RGB(0, 1, 255, GAMUT) == \ + (82, 255, 0) - def test_color_xy_to_RGB(self): - """Test color_xy_to_RGB.""" - assert (255, 243, 222) == \ - color_util.color_xy_to_RGB(.35, .35) + assert color_util.color_xy_brightness_to_RGB(0, 0, 255, GAMUT) == \ + (9, 85, 255) - assert (255, 0, 60) == \ - color_util.color_xy_to_RGB(1, 0) - assert (0, 255, 0) == \ - color_util.color_xy_to_RGB(0, 1) +def test_color_xy_to_RGB(): + """Test color_xy_to_RGB.""" + assert color_util.color_xy_to_RGB(.35, .35) == (255, 243, 222) - assert (0, 63, 255) == \ - color_util.color_xy_to_RGB(0, 0) + assert color_util.color_xy_to_RGB(1, 0) == (255, 0, 60) - assert (255, 0, 3) == \ - color_util.color_xy_to_RGB(1, 0, GAMUT) + assert color_util.color_xy_to_RGB(0, 1) == (0, 255, 0) - assert (82, 255, 0) == \ - color_util.color_xy_to_RGB(0, 1, GAMUT) + assert color_util.color_xy_to_RGB(0, 0) == (0, 63, 255) - assert (9, 85, 255) == \ - color_util.color_xy_to_RGB(0, 0, GAMUT) + assert color_util.color_xy_to_RGB(1, 0, GAMUT) == (255, 0, 3) - def test_color_RGB_to_hsv(self): - """Test color_RGB_to_hsv.""" - assert (0, 0, 0) == \ - color_util.color_RGB_to_hsv(0, 0, 0) + assert color_util.color_xy_to_RGB(0, 1, GAMUT) == (82, 255, 0) - assert (0, 0, 100) == \ - color_util.color_RGB_to_hsv(255, 255, 255) + assert color_util.color_xy_to_RGB(0, 0, GAMUT) == (9, 85, 255) - assert (240, 100, 100) == \ - color_util.color_RGB_to_hsv(0, 0, 255) - assert (120, 100, 100) == \ - color_util.color_RGB_to_hsv(0, 255, 0) +def test_color_RGB_to_hsv(): + """Test color_RGB_to_hsv.""" + assert color_util.color_RGB_to_hsv(0, 0, 0) == (0, 0, 0) - assert (0, 100, 100) == \ - color_util.color_RGB_to_hsv(255, 0, 0) + assert color_util.color_RGB_to_hsv(255, 255, 255) == (0, 0, 100) - def test_color_hsv_to_RGB(self): - """Test color_hsv_to_RGB.""" - assert (0, 0, 0) == \ - color_util.color_hsv_to_RGB(0, 0, 0) + assert color_util.color_RGB_to_hsv(0, 0, 255) == (240, 100, 100) - assert (255, 255, 255) == \ - color_util.color_hsv_to_RGB(0, 0, 100) + assert color_util.color_RGB_to_hsv(0, 255, 0) == (120, 100, 100) - assert (0, 0, 255) == \ - color_util.color_hsv_to_RGB(240, 100, 100) + assert color_util.color_RGB_to_hsv(255, 0, 0) == (0, 100, 100) - assert (0, 255, 0) == \ - color_util.color_hsv_to_RGB(120, 100, 100) - assert (255, 0, 0) == \ - color_util.color_hsv_to_RGB(0, 100, 100) +def test_color_hsv_to_RGB(): + """Test color_hsv_to_RGB.""" + assert color_util.color_hsv_to_RGB(0, 0, 0) == (0, 0, 0) - def test_color_hsb_to_RGB(self): - """Test color_hsb_to_RGB.""" - assert (0, 0, 0) == \ - color_util.color_hsb_to_RGB(0, 0, 0) + assert color_util.color_hsv_to_RGB(0, 0, 100) == (255, 255, 255) - assert (255, 255, 255) == \ - color_util.color_hsb_to_RGB(0, 0, 1.0) + assert color_util.color_hsv_to_RGB(240, 100, 100) == (0, 0, 255) - assert (0, 0, 255) == \ - color_util.color_hsb_to_RGB(240, 1.0, 1.0) + assert color_util.color_hsv_to_RGB(120, 100, 100) == (0, 255, 0) - assert (0, 255, 0) == \ - color_util.color_hsb_to_RGB(120, 1.0, 1.0) + assert color_util.color_hsv_to_RGB(0, 100, 100) == (255, 0, 0) - assert (255, 0, 0) == \ - color_util.color_hsb_to_RGB(0, 1.0, 1.0) - def test_color_xy_to_hs(self): - """Test color_xy_to_hs.""" - assert (47.294, 100) == \ - color_util.color_xy_to_hs(1, 1) +def test_color_hsb_to_RGB(): + """Test color_hsb_to_RGB.""" + assert color_util.color_hsb_to_RGB(0, 0, 0) == (0, 0, 0) - assert (38.182, 12.941) == \ - color_util.color_xy_to_hs(.35, .35) + assert color_util.color_hsb_to_RGB(0, 0, 1.0) == (255, 255, 255) - assert (345.882, 100) == \ - color_util.color_xy_to_hs(1, 0) + assert color_util.color_hsb_to_RGB(240, 1.0, 1.0) == (0, 0, 255) - assert (120, 100) == \ - color_util.color_xy_to_hs(0, 1) + assert color_util.color_hsb_to_RGB(120, 1.0, 1.0) == (0, 255, 0) - assert (225.176, 100) == \ - color_util.color_xy_to_hs(0, 0) + assert color_util.color_hsb_to_RGB(0, 1.0, 1.0) == (255, 0, 0) - assert (359.294, 100) == \ - color_util.color_xy_to_hs(1, 0, GAMUT) - assert (100.706, 100) == \ - color_util.color_xy_to_hs(0, 1, GAMUT) +def test_color_xy_to_hs(): + """Test color_xy_to_hs.""" + assert color_util.color_xy_to_hs(1, 1) == (47.294, 100) - assert (221.463, 96.471) == \ - color_util.color_xy_to_hs(0, 0, GAMUT) + assert color_util.color_xy_to_hs(.35, .35) == (38.182, 12.941) - def test_color_hs_to_xy(self): - """Test color_hs_to_xy.""" - assert (0.151, 0.343) == \ - color_util.color_hs_to_xy(180, 100) + assert color_util.color_xy_to_hs(1, 0) == (345.882, 100) - assert (0.356, 0.321) == \ - color_util.color_hs_to_xy(350, 12.5) + assert color_util.color_xy_to_hs(0, 1) == (120, 100) - assert (0.229, 0.474) == \ - color_util.color_hs_to_xy(140, 50) + assert color_util.color_xy_to_hs(0, 0) == (225.176, 100) - assert (0.474, 0.317) == \ - color_util.color_hs_to_xy(0, 40) + assert color_util.color_xy_to_hs(1, 0, GAMUT) == (359.294, 100) - assert (0.323, 0.329) == \ - color_util.color_hs_to_xy(360, 0) + assert color_util.color_xy_to_hs(0, 1, GAMUT) == (100.706, 100) - assert (0.7, 0.299) == \ - color_util.color_hs_to_xy(0, 100, GAMUT) + assert color_util.color_xy_to_hs(0, 0, GAMUT) == (221.463, 96.471) - assert (0.215, 0.711) == \ - color_util.color_hs_to_xy(120, 100, GAMUT) - assert (0.17, 0.34) == \ - color_util.color_hs_to_xy(180, 100, GAMUT) +def test_color_hs_to_xy(): + """Test color_hs_to_xy.""" + assert color_util.color_hs_to_xy(180, 100) == (0.151, 0.343) - assert (0.138, 0.08) == \ - color_util.color_hs_to_xy(240, 100, GAMUT) + assert color_util.color_hs_to_xy(350, 12.5) == (0.356, 0.321) - assert (0.7, 0.299) == \ - color_util.color_hs_to_xy(360, 100, GAMUT) + assert color_util.color_hs_to_xy(140, 50) == (0.229, 0.474) - def test_rgb_hex_to_rgb_list(self): - """Test rgb_hex_to_rgb_list.""" - assert [255, 255, 255] == \ - color_util.rgb_hex_to_rgb_list('ffffff') + assert color_util.color_hs_to_xy(0, 40) == (0.474, 0.317) - assert [0, 0, 0] == \ - color_util.rgb_hex_to_rgb_list('000000') + assert color_util.color_hs_to_xy(360, 0) == (0.323, 0.329) - assert [255, 255, 255, 255] == \ - color_util.rgb_hex_to_rgb_list('ffffffff') + assert color_util.color_hs_to_xy(0, 100, GAMUT) == (0.7, 0.299) - assert [0, 0, 0, 0] == \ - color_util.rgb_hex_to_rgb_list('00000000') + assert color_util.color_hs_to_xy(120, 100, GAMUT) == (0.215, 0.711) - assert [51, 153, 255] == \ - color_util.rgb_hex_to_rgb_list('3399ff') + assert color_util.color_hs_to_xy(180, 100, GAMUT) == (0.17, 0.34) - assert [51, 153, 255, 0] == \ - color_util.rgb_hex_to_rgb_list('3399ff00') + assert color_util.color_hs_to_xy(240, 100, GAMUT) == (0.138, 0.08) - def test_color_name_to_rgb_valid_name(self): - """Test color_name_to_rgb.""" - assert (255, 0, 0) == \ - color_util.color_name_to_rgb('red') + assert color_util.color_hs_to_xy(360, 100, GAMUT) == (0.7, 0.299) - assert (0, 0, 255) == \ - color_util.color_name_to_rgb('blue') - assert (0, 128, 0) == \ - color_util.color_name_to_rgb('green') +def test_rgb_hex_to_rgb_list(): + """Test rgb_hex_to_rgb_list.""" + assert [255, 255, 255] == \ + color_util.rgb_hex_to_rgb_list('ffffff') - # spaces in the name - assert (72, 61, 139) == \ - color_util.color_name_to_rgb('dark slate blue') + assert [0, 0, 0] == \ + color_util.rgb_hex_to_rgb_list('000000') - # spaces removed from name - assert (72, 61, 139) == \ - color_util.color_name_to_rgb('darkslateblue') - assert (72, 61, 139) == \ - color_util.color_name_to_rgb('dark slateblue') - assert (72, 61, 139) == \ - color_util.color_name_to_rgb('darkslate blue') + assert [255, 255, 255, 255] == \ + color_util.rgb_hex_to_rgb_list('ffffffff') - def test_color_name_to_rgb_unknown_name_raises_value_error(self): - """Test color_name_to_rgb.""" - with pytest.raises(ValueError): - color_util.color_name_to_rgb('not a color') + assert [0, 0, 0, 0] == \ + color_util.rgb_hex_to_rgb_list('00000000') - def test_color_rgb_to_rgbw(self): - """Test color_rgb_to_rgbw.""" - assert (0, 0, 0, 0) == \ - color_util.color_rgb_to_rgbw(0, 0, 0) + assert [51, 153, 255] == \ + color_util.rgb_hex_to_rgb_list('3399ff') - assert (0, 0, 0, 255) == \ - color_util.color_rgb_to_rgbw(255, 255, 255) + assert [51, 153, 255, 0] == \ + color_util.rgb_hex_to_rgb_list('3399ff00') - assert (255, 0, 0, 0) == \ - color_util.color_rgb_to_rgbw(255, 0, 0) - assert (0, 255, 0, 0) == \ - color_util.color_rgb_to_rgbw(0, 255, 0) +def test_color_name_to_rgb_valid_name(): + """Test color_name_to_rgb.""" + assert color_util.color_name_to_rgb('red') == (255, 0, 0) - assert (0, 0, 255, 0) == \ - color_util.color_rgb_to_rgbw(0, 0, 255) + assert color_util.color_name_to_rgb('blue') == (0, 0, 255) - assert (255, 127, 0, 0) == \ - color_util.color_rgb_to_rgbw(255, 127, 0) + assert color_util.color_name_to_rgb('green') == (0, 128, 0) - assert (255, 0, 0, 253) == \ - color_util.color_rgb_to_rgbw(255, 127, 127) + # spaces in the name + assert color_util.color_name_to_rgb('dark slate blue') == (72, 61, 139) - assert (0, 0, 0, 127) == \ - color_util.color_rgb_to_rgbw(127, 127, 127) + # spaces removed from name + assert color_util.color_name_to_rgb('darkslateblue') == (72, 61, 139) + assert color_util.color_name_to_rgb('dark slateblue') == (72, 61, 139) + assert color_util.color_name_to_rgb('darkslate blue') == (72, 61, 139) - def test_color_rgbw_to_rgb(self): - """Test color_rgbw_to_rgb.""" - assert (0, 0, 0) == \ - color_util.color_rgbw_to_rgb(0, 0, 0, 0) - assert (255, 255, 255) == \ - color_util.color_rgbw_to_rgb(0, 0, 0, 255) +def test_color_name_to_rgb_unknown_name_raises_value_error(): + """Test color_name_to_rgb.""" + with pytest.raises(ValueError): + color_util.color_name_to_rgb('not a color') - assert (255, 0, 0) == \ - color_util.color_rgbw_to_rgb(255, 0, 0, 0) - assert (0, 255, 0) == \ - color_util.color_rgbw_to_rgb(0, 255, 0, 0) +def test_color_rgb_to_rgbw(): + """Test color_rgb_to_rgbw.""" + assert color_util.color_rgb_to_rgbw(0, 0, 0) == (0, 0, 0, 0) - assert (0, 0, 255) == \ - color_util.color_rgbw_to_rgb(0, 0, 255, 0) + assert color_util.color_rgb_to_rgbw(255, 255, 255) == (0, 0, 0, 255) - assert (255, 127, 0) == \ - color_util.color_rgbw_to_rgb(255, 127, 0, 0) + assert color_util.color_rgb_to_rgbw(255, 0, 0) == (255, 0, 0, 0) - assert (255, 127, 127) == \ - color_util.color_rgbw_to_rgb(255, 0, 0, 253) + assert color_util.color_rgb_to_rgbw(0, 255, 0) == (0, 255, 0, 0) - assert (127, 127, 127) == \ - color_util.color_rgbw_to_rgb(0, 0, 0, 127) + assert color_util.color_rgb_to_rgbw(0, 0, 255) == (0, 0, 255, 0) - def test_color_rgb_to_hex(self): - """Test color_rgb_to_hex.""" - assert color_util.color_rgb_to_hex(255, 255, 255) == 'ffffff' - assert color_util.color_rgb_to_hex(0, 0, 0) == '000000' - assert color_util.color_rgb_to_hex(51, 153, 255) == '3399ff' - assert color_util.color_rgb_to_hex(255, 67.9204190, 0) == 'ff4400' + assert color_util.color_rgb_to_rgbw(255, 127, 0) == (255, 127, 0, 0) - def test_gamut(self): - """Test gamut functions.""" - assert color_util.check_valid_gamut(GAMUT) - assert not color_util.check_valid_gamut(GAMUT_INVALID_1) - assert not color_util.check_valid_gamut(GAMUT_INVALID_2) - assert not color_util.check_valid_gamut(GAMUT_INVALID_3) - assert not color_util.check_valid_gamut(GAMUT_INVALID_4) + assert color_util.color_rgb_to_rgbw(255, 127, 127) == (255, 0, 0, 253) + assert color_util.color_rgb_to_rgbw(127, 127, 127) == (0, 0, 0, 127) -class ColorTemperatureMiredToKelvinTests(unittest.TestCase): - """Test color_temperature_mired_to_kelvin.""" - def test_should_return_25000_kelvin_when_input_is_40_mired(self): - """Function should return 25000K if given 40 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(40) - assert 25000 == kelvin +def test_color_rgbw_to_rgb(): + """Test color_rgbw_to_rgb.""" + assert color_util.color_rgbw_to_rgb(0, 0, 0, 0) == (0, 0, 0) - def test_should_return_5000_kelvin_when_input_is_200_mired(self): - """Function should return 5000K if given 200 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(200) - assert 5000 == kelvin - - -class ColorTemperatureKelvinToMiredTests(unittest.TestCase): - """Test color_temperature_kelvin_to_mired.""" - - def test_should_return_40_mired_when_input_is_25000_kelvin(self): - """Function should return 40 mired when given 25000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(25000) - assert 40 == mired - - def test_should_return_200_mired_when_input_is_5000_kelvin(self): - """Function should return 200 mired when given 5000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(5000) - assert 200 == mired + assert color_util.color_rgbw_to_rgb(0, 0, 0, 255) == (255, 255, 255) + assert color_util.color_rgbw_to_rgb(255, 0, 0, 0) == (255, 0, 0) -class ColorTemperatureToRGB(unittest.TestCase): - """Test color_temperature_to_rgb.""" + assert color_util.color_rgbw_to_rgb(0, 255, 0, 0) == (0, 255, 0) - def test_returns_same_value_for_any_two_temperatures_below_1000(self): - """Function should return same value for 999 Kelvin and 0 Kelvin.""" - rgb_1 = color_util.color_temperature_to_rgb(999) - rgb_2 = color_util.color_temperature_to_rgb(0) - assert rgb_1 == rgb_2 + assert color_util.color_rgbw_to_rgb(0, 0, 255, 0) == (0, 0, 255) - def test_returns_same_value_for_any_two_temperatures_above_40000(self): - """Function should return same value for 40001K and 999999K.""" - rgb_1 = color_util.color_temperature_to_rgb(40001) - rgb_2 = color_util.color_temperature_to_rgb(999999) - assert rgb_1 == rgb_2 + assert color_util.color_rgbw_to_rgb(255, 127, 0, 0) == (255, 127, 0) - def test_should_return_pure_white_at_6600(self): - """ - Function should return red=255, blue=255, green=255 when given 6600K. + assert color_util.color_rgbw_to_rgb(255, 0, 0, 253) == (255, 127, 127) - 6600K is considered "pure white" light. - This is just a rough estimate because the formula itself is a "best - guess" approach. - """ - rgb = color_util.color_temperature_to_rgb(6600) - assert (255, 255, 255) == rgb - - def test_color_above_6600_should_have_more_blue_than_red_or_green(self): - """Function should return a higher blue value for blue-ish light.""" - rgb = color_util.color_temperature_to_rgb(6700) - assert rgb[2] > rgb[1] - assert rgb[2] > rgb[0] - - def test_color_below_6600_should_have_more_red_than_blue_or_green(self): - """Function should return a higher red value for red-ish light.""" - rgb = color_util.color_temperature_to_rgb(6500) - assert rgb[0] > rgb[1] - assert rgb[0] > rgb[2] + assert color_util.color_rgbw_to_rgb(0, 0, 0, 127) == (127, 127, 127) + + +def test_color_rgb_to_hex(): + """Test color_rgb_to_hex.""" + assert color_util.color_rgb_to_hex(255, 255, 255) == 'ffffff' + assert color_util.color_rgb_to_hex(0, 0, 0) == '000000' + assert color_util.color_rgb_to_hex(51, 153, 255) == '3399ff' + assert color_util.color_rgb_to_hex(255, 67.9204190, 0) == 'ff4400' + + +def test_gamut(): + """Test gamut functions.""" + assert color_util.check_valid_gamut(GAMUT) + assert not color_util.check_valid_gamut(GAMUT_INVALID_1) + assert not color_util.check_valid_gamut(GAMUT_INVALID_2) + assert not color_util.check_valid_gamut(GAMUT_INVALID_3) + assert not color_util.check_valid_gamut(GAMUT_INVALID_4) + + +def test_should_return_25000_kelvin_when_input_is_40_mired(): + """Function should return 25000K if given 40 mired.""" + kelvin = color_util.color_temperature_mired_to_kelvin(40) + assert kelvin == 25000 + + +def test_should_return_5000_kelvin_when_input_is_200_mired(): + """Function should return 5000K if given 200 mired.""" + kelvin = color_util.color_temperature_mired_to_kelvin(200) + assert kelvin == 5000 + + +def test_should_return_40_mired_when_input_is_25000_kelvin(): + """Function should return 40 mired when given 25000 Kelvin.""" + mired = color_util.color_temperature_kelvin_to_mired(25000) + assert mired == 40 + + +def test_should_return_200_mired_when_input_is_5000_kelvin(): + """Function should return 200 mired when given 5000 Kelvin.""" + mired = color_util.color_temperature_kelvin_to_mired(5000) + assert mired == 200 + + +def test_returns_same_value_for_any_two_temperatures_below_1000(): + """Function should return same value for 999 Kelvin and 0 Kelvin.""" + rgb_1 = color_util.color_temperature_to_rgb(999) + rgb_2 = color_util.color_temperature_to_rgb(0) + assert rgb_1 == rgb_2 + + +def test_returns_same_value_for_any_two_temperatures_above_40000(): + """Function should return same value for 40001K and 999999K.""" + rgb_1 = color_util.color_temperature_to_rgb(40001) + rgb_2 = color_util.color_temperature_to_rgb(999999) + assert rgb_1 == rgb_2 + + +def test_should_return_pure_white_at_6600(): + """ + Function should return red=255, blue=255, green=255 when given 6600K. + + 6600K is considered "pure white" light. + This is just a rough estimate because the formula itself is a "best + guess" approach. + """ + rgb = color_util.color_temperature_to_rgb(6600) + assert (255, 255, 255) == rgb + + +def test_color_above_6600_should_have_more_blue_than_red_or_green(): + """Function should return a higher blue value for blue-ish light.""" + rgb = color_util.color_temperature_to_rgb(6700) + assert rgb[2] > rgb[1] + assert rgb[2] > rgb[0] + + +def test_color_below_6600_should_have_more_red_than_blue_or_green(): + """Function should return a higher red value for red-ish light.""" + rgb = color_util.color_temperature_to_rgb(6500) + assert rgb[0] > rgb[1] + assert rgb[0] > rgb[2] def test_get_color_in_voluptuous(): diff --git a/tests/util/test_distance.py b/tests/util/test_distance.py index 162f1a2fa99..691a3e47bf7 100644 --- a/tests/util/test_distance.py +++ b/tests/util/test_distance.py @@ -1,79 +1,68 @@ """Test homeassistant distance utility functions.""" -import unittest +import pytest + import homeassistant.util.distance as distance_util from homeassistant.const import (LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_FEET, LENGTH_MILES) -import pytest - INVALID_SYMBOL = 'bob' VALID_SYMBOL = LENGTH_KILOMETERS -class TestDistanceUtil(unittest.TestCase): - """Test the distance utility functions.""" +def test_convert_same_unit(): + """Test conversion from any unit to same unit.""" + assert distance_util.convert(5, LENGTH_KILOMETERS, LENGTH_KILOMETERS) == 5 + assert distance_util.convert(2, LENGTH_METERS, LENGTH_METERS) == 2 + assert distance_util.convert(10, LENGTH_MILES, LENGTH_MILES) == 10 + assert distance_util.convert(9, LENGTH_FEET, LENGTH_FEET) == 9 - def test_convert_same_unit(self): - """Test conversion from any unit to same unit.""" - assert 5 == distance_util.convert(5, LENGTH_KILOMETERS, - LENGTH_KILOMETERS) - assert 2 == distance_util.convert(2, LENGTH_METERS, - LENGTH_METERS) - assert 10 == distance_util.convert(10, LENGTH_MILES, LENGTH_MILES) - assert 9 == distance_util.convert(9, LENGTH_FEET, LENGTH_FEET) - def test_convert_invalid_unit(self): - """Test exception is thrown for invalid units.""" - with pytest.raises(ValueError): - distance_util.convert(5, INVALID_SYMBOL, - VALID_SYMBOL) +def test_convert_invalid_unit(): + """Test exception is thrown for invalid units.""" + with pytest.raises(ValueError): + distance_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) - with pytest.raises(ValueError): - distance_util.convert(5, VALID_SYMBOL, - INVALID_SYMBOL) + with pytest.raises(ValueError): + distance_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) - def test_convert_nonnumeric_value(self): - """Test exception is thrown for nonnumeric type.""" - with pytest.raises(TypeError): - distance_util.convert('a', LENGTH_KILOMETERS, LENGTH_METERS) - def test_convert_from_miles(self): - """Test conversion from miles to other units.""" - miles = 5 - assert distance_util.convert( - miles, LENGTH_MILES, LENGTH_KILOMETERS - ) == 8.04672 - assert distance_util.convert(miles, LENGTH_MILES, LENGTH_METERS) == \ - 8046.72 - assert distance_util.convert(miles, LENGTH_MILES, LENGTH_FEET) == \ - 26400.0008448 +def test_convert_nonnumeric_value(): + """Test exception is thrown for nonnumeric type.""" + with pytest.raises(TypeError): + distance_util.convert('a', LENGTH_KILOMETERS, LENGTH_METERS) - def test_convert_from_feet(self): - """Test conversion from feet to other units.""" - feet = 5000 - assert distance_util.convert(feet, LENGTH_FEET, LENGTH_KILOMETERS) == \ - 1.524 - assert distance_util.convert(feet, LENGTH_FEET, LENGTH_METERS) == \ - 1524 - assert distance_util.convert(feet, LENGTH_FEET, LENGTH_MILES) == \ - 0.9469694040000001 - def test_convert_from_kilometers(self): - """Test conversion from kilometers to other units.""" - km = 5 - assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_FEET) == \ - 16404.2 - assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_METERS) == \ - 5000 - assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_MILES) == \ - 3.106855 +def test_convert_from_miles(): + """Test conversion from miles to other units.""" + miles = 5 + assert distance_util.convert(miles, LENGTH_MILES, LENGTH_KILOMETERS) == \ + 8.04672 + assert distance_util.convert(miles, LENGTH_MILES, LENGTH_METERS) == 8046.72 + assert distance_util.convert(miles, LENGTH_MILES, LENGTH_FEET) == \ + 26400.0008448 - def test_convert_from_meters(self): - """Test conversion from meters to other units.""" - m = 5000 - assert distance_util.convert(m, LENGTH_METERS, LENGTH_FEET) == \ - 16404.2 - assert distance_util.convert(m, LENGTH_METERS, LENGTH_KILOMETERS) == \ - 5 - assert distance_util.convert(m, LENGTH_METERS, LENGTH_MILES) == \ - 3.106855 + +def test_convert_from_feet(): + """Test conversion from feet to other units.""" + feet = 5000 + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_KILOMETERS) == 1.524 + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_METERS) == 1524 + assert distance_util.convert(feet, LENGTH_FEET, LENGTH_MILES) == \ + 0.9469694040000001 + + +def test_convert_from_kilometers(): + """Test conversion from kilometers to other units.""" + km = 5 + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_FEET) == 16404.2 + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_METERS) == 5000 + assert distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_MILES) == \ + 3.106855 + + +def test_convert_from_meters(): + """Test conversion from meters to other units.""" + m = 5000 + assert distance_util.convert(m, LENGTH_METERS, LENGTH_FEET) == 16404.2 + assert distance_util.convert(m, LENGTH_METERS, LENGTH_KILOMETERS) == 5 + assert distance_util.convert(m, LENGTH_METERS, LENGTH_MILES) == 3.106855 diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 52f55fff345..61f10ab1bf6 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -1,253 +1,266 @@ """Test Home Assistant date util methods.""" -import unittest from datetime import datetime, timedelta -import homeassistant.util.dt as dt_util import pytest +import homeassistant.util.dt as dt_util + +DEFAULT_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE TEST_TIME_ZONE = 'America/Los_Angeles' -class TestDateUtil(unittest.TestCase): - """Test util date methods.""" +def teardown(): + """Stop everything that was started.""" + dt_util.set_default_time_zone(DEFAULT_TIME_ZONE) - def setUp(self): - """Set up the tests.""" - self.orig_default_time_zone = dt_util.DEFAULT_TIME_ZONE - def tearDown(self): - """Stop everything that was started.""" - dt_util.set_default_time_zone(self.orig_default_time_zone) +def test_get_time_zone_retrieves_valid_time_zone(): + """Test getting a time zone.""" + time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) - def test_get_time_zone_retrieves_valid_time_zone(self): - """Test getting a time zone.""" - time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) + assert time_zone is not None + assert TEST_TIME_ZONE == time_zone.zone - assert time_zone is not None - assert TEST_TIME_ZONE == time_zone.zone - def test_get_time_zone_returns_none_for_garbage_time_zone(self): - """Test getting a non existing time zone.""" - time_zone = dt_util.get_time_zone("Non existing time zone") +def test_get_time_zone_returns_none_for_garbage_time_zone(): + """Test getting a non existing time zone.""" + time_zone = dt_util.get_time_zone("Non existing time zone") - assert time_zone is None + assert time_zone is None - def test_set_default_time_zone(self): - """Test setting default time zone.""" - time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) - dt_util.set_default_time_zone(time_zone) +def test_set_default_time_zone(): + """Test setting default time zone.""" + time_zone = dt_util.get_time_zone(TEST_TIME_ZONE) - # We cannot compare the timezones directly because of DST - assert time_zone.zone == dt_util.now().tzinfo.zone + dt_util.set_default_time_zone(time_zone) - def test_utcnow(self): - """Test the UTC now method.""" - assert abs(dt_util.utcnow().replace(tzinfo=None)-datetime.utcnow()) < \ - timedelta(seconds=1) + # We cannot compare the timezones directly because of DST + assert time_zone.zone == dt_util.now().tzinfo.zone - def test_now(self): - """Test the now method.""" - dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) - assert abs( - dt_util.as_utc(dt_util.now()).replace( - tzinfo=None - ) - datetime.utcnow() - ) < timedelta(seconds=1) +def test_utcnow(): + """Test the UTC now method.""" + assert abs(dt_util.utcnow().replace(tzinfo=None)-datetime.utcnow()) < \ + timedelta(seconds=1) - def test_as_utc_with_naive_object(self): - """Test the now method.""" - utcnow = datetime.utcnow() - assert utcnow == dt_util.as_utc(utcnow).replace(tzinfo=None) +def test_now(): + """Test the now method.""" + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) - def test_as_utc_with_utc_object(self): - """Test UTC time with UTC object.""" - utcnow = dt_util.utcnow() + assert abs( + dt_util.as_utc(dt_util.now()).replace( + tzinfo=None + ) - datetime.utcnow() + ) < timedelta(seconds=1) - assert utcnow == dt_util.as_utc(utcnow) - def test_as_utc_with_local_object(self): - """Test the UTC time with local object.""" - dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) - localnow = dt_util.now() - utcnow = dt_util.as_utc(localnow) +def test_as_utc_with_naive_object(): + """Test the now method.""" + utcnow = datetime.utcnow() - assert localnow == utcnow - assert localnow.tzinfo != utcnow.tzinfo + assert utcnow == dt_util.as_utc(utcnow).replace(tzinfo=None) - def test_as_local_with_naive_object(self): - """Test local time with native object.""" - now = dt_util.now() - assert abs(now-dt_util.as_local(datetime.utcnow())) < \ - timedelta(seconds=1) - def test_as_local_with_local_object(self): - """Test local with local object.""" - now = dt_util.now() - assert now == now +def test_as_utc_with_utc_object(): + """Test UTC time with UTC object.""" + utcnow = dt_util.utcnow() - def test_as_local_with_utc_object(self): - """Test local time with UTC object.""" - dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) + assert utcnow == dt_util.as_utc(utcnow) - utcnow = dt_util.utcnow() - localnow = dt_util.as_local(utcnow) - assert localnow == utcnow - assert localnow.tzinfo != utcnow.tzinfo +def test_as_utc_with_local_object(): + """Test the UTC time with local object.""" + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) + localnow = dt_util.now() + utcnow = dt_util.as_utc(localnow) - def test_utc_from_timestamp(self): - """Test utc_from_timestamp method.""" - assert datetime(1986, 7, 9, tzinfo=dt_util.UTC) == \ - dt_util.utc_from_timestamp(521251200) + assert localnow == utcnow + assert localnow.tzinfo != utcnow.tzinfo - def test_as_timestamp(self): - """Test as_timestamp method.""" - ts = 1462401234 - utc_dt = dt_util.utc_from_timestamp(ts) - assert ts == dt_util.as_timestamp(utc_dt) - utc_iso = utc_dt.isoformat() - assert ts == dt_util.as_timestamp(utc_iso) - # confirm the ability to handle a string passed in - delta = dt_util.as_timestamp("2016-01-01 12:12:12") - delta -= dt_util.as_timestamp("2016-01-01 12:12:11") - assert 1 == delta +def test_as_local_with_naive_object(): + """Test local time with native object.""" + now = dt_util.now() + assert abs(now-dt_util.as_local(datetime.utcnow())) < \ + timedelta(seconds=1) - def test_parse_datetime_converts_correctly(self): - """Test parse_datetime converts strings.""" - assert \ - datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) == \ - dt_util.parse_datetime("1986-07-09T12:00:00Z") - utcnow = dt_util.utcnow() +def test_as_local_with_local_object(): + """Test local with local object.""" + now = dt_util.now() + assert now == now - assert utcnow == dt_util.parse_datetime(utcnow.isoformat()) - def test_parse_datetime_returns_none_for_incorrect_format(self): - """Test parse_datetime returns None if incorrect format.""" - assert dt_util.parse_datetime("not a datetime string") is None +def test_as_local_with_utc_object(): + """Test local time with UTC object.""" + dt_util.set_default_time_zone(dt_util.get_time_zone(TEST_TIME_ZONE)) - def test_get_age(self): - """Test get_age.""" - diff = dt_util.now() - timedelta(seconds=0) - assert dt_util.get_age(diff) == "0 seconds" + utcnow = dt_util.utcnow() + localnow = dt_util.as_local(utcnow) - diff = dt_util.now() - timedelta(seconds=1) - assert dt_util.get_age(diff) == "1 second" + assert localnow == utcnow + assert localnow.tzinfo != utcnow.tzinfo - diff = dt_util.now() - timedelta(seconds=30) - assert dt_util.get_age(diff) == "30 seconds" - diff = dt_util.now() - timedelta(minutes=5) - assert dt_util.get_age(diff) == "5 minutes" +def test_utc_from_timestamp(): + """Test utc_from_timestamp method.""" + assert datetime(1986, 7, 9, tzinfo=dt_util.UTC) == \ + dt_util.utc_from_timestamp(521251200) - diff = dt_util.now() - timedelta(minutes=1) - assert dt_util.get_age(diff) == "1 minute" - diff = dt_util.now() - timedelta(minutes=300) - assert dt_util.get_age(diff) == "5 hours" +def test_as_timestamp(): + """Test as_timestamp method.""" + ts = 1462401234 + utc_dt = dt_util.utc_from_timestamp(ts) + assert ts == dt_util.as_timestamp(utc_dt) + utc_iso = utc_dt.isoformat() + assert ts == dt_util.as_timestamp(utc_iso) - diff = dt_util.now() - timedelta(minutes=320) - assert dt_util.get_age(diff) == "5 hours" + # confirm the ability to handle a string passed in + delta = dt_util.as_timestamp("2016-01-01 12:12:12") + delta -= dt_util.as_timestamp("2016-01-01 12:12:11") + assert delta == 1 - diff = dt_util.now() - timedelta(minutes=2*60*24) - assert dt_util.get_age(diff) == "2 days" - diff = dt_util.now() - timedelta(minutes=32*60*24) - assert dt_util.get_age(diff) == "1 month" +def test_parse_datetime_converts_correctly(): + """Test parse_datetime converts strings.""" + assert \ + datetime(1986, 7, 9, 12, 0, 0, tzinfo=dt_util.UTC) == \ + dt_util.parse_datetime("1986-07-09T12:00:00Z") - diff = dt_util.now() - timedelta(minutes=365*60*24) - assert dt_util.get_age(diff) == "1 year" + utcnow = dt_util.utcnow() - def test_parse_time_expression(self): - """Test parse_time_expression.""" - assert [x for x in range(60)] == \ - dt_util.parse_time_expression('*', 0, 59) - assert [x for x in range(60)] == \ - dt_util.parse_time_expression(None, 0, 59) + assert utcnow == dt_util.parse_datetime(utcnow.isoformat()) - assert [x for x in range(0, 60, 5)] == \ - dt_util.parse_time_expression('/5', 0, 59) - assert [1, 2, 3] == \ - dt_util.parse_time_expression([2, 1, 3], 0, 59) +def test_parse_datetime_returns_none_for_incorrect_format(): + """Test parse_datetime returns None if incorrect format.""" + assert dt_util.parse_datetime("not a datetime string") is None - assert [x for x in range(24)] == \ - dt_util.parse_time_expression('*', 0, 23) - assert [42] == \ - dt_util.parse_time_expression(42, 0, 59) +def test_get_age(): + """Test get_age.""" + diff = dt_util.now() - timedelta(seconds=0) + assert dt_util.get_age(diff) == "0 seconds" - with pytest.raises(ValueError): - dt_util.parse_time_expression(61, 0, 60) + diff = dt_util.now() - timedelta(seconds=1) + assert dt_util.get_age(diff) == "1 second" - def test_find_next_time_expression_time_basic(self): - """Test basic stuff for find_next_time_expression_time.""" - def find(dt, hour, minute, second): - """Call test_find_next_time_expression_time.""" - seconds = dt_util.parse_time_expression(second, 0, 59) - minutes = dt_util.parse_time_expression(minute, 0, 59) - hours = dt_util.parse_time_expression(hour, 0, 23) + diff = dt_util.now() - timedelta(seconds=30) + assert dt_util.get_age(diff) == "30 seconds" - return dt_util.find_next_time_expression_time( - dt, seconds, minutes, hours) + diff = dt_util.now() - timedelta(minutes=5) + assert dt_util.get_age(diff) == "5 minutes" - assert datetime(2018, 10, 7, 10, 30, 0) == \ - find(datetime(2018, 10, 7, 10, 20, 0), '*', '/30', 0) + diff = dt_util.now() - timedelta(minutes=1) + assert dt_util.get_age(diff) == "1 minute" - assert datetime(2018, 10, 7, 10, 30, 0) == \ - find(datetime(2018, 10, 7, 10, 30, 0), '*', '/30', 0) + diff = dt_util.now() - timedelta(minutes=300) + assert dt_util.get_age(diff) == "5 hours" - assert datetime(2018, 10, 7, 12, 30, 30) == \ - find(datetime(2018, 10, 7, 10, 30, 0), '/3', '/30', [30, 45]) + diff = dt_util.now() - timedelta(minutes=320) + assert dt_util.get_age(diff) == "5 hours" - assert datetime(2018, 10, 8, 5, 0, 0) == \ - find(datetime(2018, 10, 7, 10, 30, 0), 5, 0, 0) + diff = dt_util.now() - timedelta(minutes=2*60*24) + assert dt_util.get_age(diff) == "2 days" - def test_find_next_time_expression_time_dst(self): - """Test daylight saving time for find_next_time_expression_time.""" - tz = dt_util.get_time_zone('Europe/Vienna') - dt_util.set_default_time_zone(tz) + diff = dt_util.now() - timedelta(minutes=32*60*24) + assert dt_util.get_age(diff) == "1 month" - def find(dt, hour, minute, second): - """Call test_find_next_time_expression_time.""" - seconds = dt_util.parse_time_expression(second, 0, 59) - minutes = dt_util.parse_time_expression(minute, 0, 59) - hours = dt_util.parse_time_expression(hour, 0, 23) + diff = dt_util.now() - timedelta(minutes=365*60*24) + assert dt_util.get_age(diff) == "1 year" - return dt_util.find_next_time_expression_time( - dt, seconds, minutes, hours) - # Entering DST, clocks are rolled forward - assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ - find(tz.localize(datetime(2018, 3, 25, 1, 50, 0)), 2, 30, 0) +def test_parse_time_expression(): + """Test parse_time_expression.""" + assert [x for x in range(60)] == \ + dt_util.parse_time_expression('*', 0, 59) + assert [x for x in range(60)] == \ + dt_util.parse_time_expression(None, 0, 59) - assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ - find(tz.localize(datetime(2018, 3, 25, 3, 50, 0)), 2, 30, 0) + assert [x for x in range(0, 60, 5)] == \ + dt_util.parse_time_expression('/5', 0, 59) - assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ - find(tz.localize(datetime(2018, 3, 26, 1, 50, 0)), 2, 30, 0) + assert [1, 2, 3] == \ + dt_util.parse_time_expression([2, 1, 3], 0, 59) - # Leaving DST, clocks are rolled back - assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ - find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False), - 2, 30, 0) + assert [x for x in range(24)] == \ + dt_util.parse_time_expression('*', 0, 23) - assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ - find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), - 2, 30, 0) + assert [42] == \ + dt_util.parse_time_expression(42, 0, 59) - assert tz.localize(datetime(2018, 10, 28, 4, 30, 0), is_dst=False) == \ - find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), - 4, 30, 0) + with pytest.raises(ValueError): + dt_util.parse_time_expression(61, 0, 60) - assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=True) == \ - find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True), - 2, 30, 0) - assert tz.localize(datetime(2018, 10, 29, 2, 30, 0)) == \ - find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False), - 2, 30, 0) +def test_find_next_time_expression_time_basic(): + """Test basic stuff for find_next_time_expression_time.""" + def find(dt, hour, minute, second): + """Call test_find_next_time_expression_time.""" + seconds = dt_util.parse_time_expression(second, 0, 59) + minutes = dt_util.parse_time_expression(minute, 0, 59) + hours = dt_util.parse_time_expression(hour, 0, 23) + + return dt_util.find_next_time_expression_time( + dt, seconds, minutes, hours) + + assert datetime(2018, 10, 7, 10, 30, 0) == \ + find(datetime(2018, 10, 7, 10, 20, 0), '*', '/30', 0) + + assert datetime(2018, 10, 7, 10, 30, 0) == \ + find(datetime(2018, 10, 7, 10, 30, 0), '*', '/30', 0) + + assert datetime(2018, 10, 7, 12, 30, 30) == \ + find(datetime(2018, 10, 7, 10, 30, 0), '/3', '/30', [30, 45]) + + assert datetime(2018, 10, 8, 5, 0, 0) == \ + find(datetime(2018, 10, 7, 10, 30, 0), 5, 0, 0) + + +def test_find_next_time_expression_time_dst(): + """Test daylight saving time for find_next_time_expression_time.""" + tz = dt_util.get_time_zone('Europe/Vienna') + dt_util.set_default_time_zone(tz) + + def find(dt, hour, minute, second): + """Call test_find_next_time_expression_time.""" + seconds = dt_util.parse_time_expression(second, 0, 59) + minutes = dt_util.parse_time_expression(minute, 0, 59) + hours = dt_util.parse_time_expression(hour, 0, 23) + + return dt_util.find_next_time_expression_time( + dt, seconds, minutes, hours) + + # Entering DST, clocks are rolled forward + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ + find(tz.localize(datetime(2018, 3, 25, 1, 50, 0)), 2, 30, 0) + + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ + find(tz.localize(datetime(2018, 3, 25, 3, 50, 0)), 2, 30, 0) + + assert tz.localize(datetime(2018, 3, 26, 2, 30, 0)) == \ + find(tz.localize(datetime(2018, 3, 26, 1, 50, 0)), 2, 30, 0) + + # Leaving DST, clocks are rolled back + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ + find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False), + 2, 30, 0) + + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=False) == \ + find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), + 2, 30, 0) + + assert tz.localize(datetime(2018, 10, 28, 4, 30, 0), is_dst=False) == \ + find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True), + 4, 30, 0) + + assert tz.localize(datetime(2018, 10, 28, 2, 30, 0), is_dst=True) == \ + find(tz.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True), + 2, 30, 0) + + assert tz.localize(datetime(2018, 10, 29, 2, 30, 0)) == \ + find(tz.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False), + 2, 30, 0) diff --git a/tests/util/test_init.py b/tests/util/test_init.py index af957582ec0..42b53cea2d6 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -1,223 +1,228 @@ """Test Home Assistant util methods.""" -import unittest from unittest.mock import patch, MagicMock from datetime import datetime, timedelta -from homeassistant import util -import homeassistant.util.dt as dt_util import pytest +from homeassistant import util +import homeassistant.util.dt as dt_util -class TestUtil(unittest.TestCase): - """Test util methods.""" - def test_sanitize_filename(self): - """Test sanitize_filename.""" - assert "test" == util.sanitize_filename("test") - assert "test" == util.sanitize_filename("/test") - assert "test" == util.sanitize_filename("..test") - assert "test" == util.sanitize_filename("\\test") - assert "test" == util.sanitize_filename("\\../test") +def test_sanitize_filename(): + """Test sanitize_filename.""" + assert util.sanitize_filename("test") == 'test' + assert util.sanitize_filename("/test") == 'test' + assert util.sanitize_filename("..test") == 'test' + assert util.sanitize_filename("\\test") == 'test' + assert util.sanitize_filename("\\../test") == 'test' - def test_sanitize_path(self): - """Test sanitize_path.""" - assert "test/path" == util.sanitize_path("test/path") - assert "test/path" == util.sanitize_path("~test/path") - assert "//test/path" == util.sanitize_path("~/../test/path") - def test_slugify(self): - """Test slugify.""" - assert "t_est" == util.slugify("T-!@#$!#@$!$est") - assert "test_more" == util.slugify("Test More") - assert "test_more" == util.slugify("Test_(More)") - assert "test_more" == util.slugify("Tèst_Mörê") - assert "b8_27_eb_00_00_00" == util.slugify("B8:27:EB:00:00:00") - assert "test_com" == util.slugify("test.com") - assert "greg_phone_exp_wayp1" == \ - util.slugify("greg_phone - exp_wayp1") - assert "we_are_we_are_a_test_calendar" == \ - util.slugify("We are, we are, a... Test Calendar") - assert "test_aouss_aou" == util.slugify("Tèst_äöüß_ÄÖÜ") - assert "ying_shi_ma" == util.slugify("影師嗎") - assert "keihuonto" == util.slugify("けいふぉんと") +def test_sanitize_path(): + """Test sanitize_path.""" + assert util.sanitize_path("test/path") == 'test/path' + assert util.sanitize_path("~test/path") == 'test/path' + assert util.sanitize_path("~/../test/path") == '//test/path' - def test_repr_helper(self): - """Test repr_helper.""" - assert "A" == util.repr_helper("A") - assert "5" == util.repr_helper(5) - assert "True" == util.repr_helper(True) - assert "test=1" == util.repr_helper({"test": 1}) - assert "1986-07-09T12:00:00+00:00" == \ - util.repr_helper(datetime(1986, 7, 9, 12, 0, 0)) - def test_convert(self): - """Test convert.""" - assert 5 == util.convert("5", int) - assert 5.0 == util.convert("5", float) - assert util.convert("True", bool) is True - assert 1 == util.convert("NOT A NUMBER", int, 1) - assert 1 == util.convert(None, int, 1) - assert 1 == util.convert(object, int, 1) +def test_slugify(): + """Test slugify.""" + assert util.slugify("T-!@#$!#@$!$est") == 't_est' + assert util.slugify("Test More") == 'test_more' + assert util.slugify("Test_(More)") == 'test_more' + assert util.slugify("Tèst_Mörê") == 'test_more' + assert util.slugify("B8:27:EB:00:00:00") == 'b8_27_eb_00_00_00' + assert util.slugify("test.com") == 'test_com' + assert util.slugify("greg_phone - exp_wayp1") == 'greg_phone_exp_wayp1' + assert util.slugify("We are, we are, a... Test Calendar") == \ + 'we_are_we_are_a_test_calendar' + assert util.slugify("Tèst_äöüß_ÄÖÜ") == 'test_aouss_aou' + assert util.slugify("影師嗎") == 'ying_shi_ma' + assert util.slugify("けいふぉんと") == 'keihuonto' - def test_ensure_unique_string(self): - """Test ensure_unique_string.""" - assert "Beer_3" == \ - util.ensure_unique_string("Beer", ["Beer", "Beer_2"]) - assert "Beer" == \ - util.ensure_unique_string("Beer", ["Wine", "Soda"]) - def test_ordered_enum(self): - """Test the ordered enum class.""" - class TestEnum(util.OrderedEnum): - """Test enum that can be ordered.""" +def test_repr_helper(): + """Test repr_helper.""" + assert util.repr_helper("A") == 'A' + assert util.repr_helper(5) == '5' + assert util.repr_helper(True) == 'True' + assert util.repr_helper({"test": 1}) == 'test=1' + assert util.repr_helper(datetime(1986, 7, 9, 12, 0, 0)) == \ + '1986-07-09T12:00:00+00:00' - FIRST = 1 - SECOND = 2 - THIRD = 3 - assert TestEnum.SECOND >= TestEnum.FIRST - assert TestEnum.SECOND >= TestEnum.SECOND - assert not (TestEnum.SECOND >= TestEnum.THIRD) +def test_convert(): + """Test convert.""" + assert util.convert("5", int) == 5 + assert util.convert("5", float) == 5.0 + assert util.convert("True", bool) is True + assert util.convert("NOT A NUMBER", int, 1) == 1 + assert util.convert(None, int, 1) == 1 + assert util.convert(object, int, 1) == 1 - assert TestEnum.SECOND > TestEnum.FIRST - assert not (TestEnum.SECOND > TestEnum.SECOND) - assert not (TestEnum.SECOND > TestEnum.THIRD) - assert not (TestEnum.SECOND <= TestEnum.FIRST) - assert TestEnum.SECOND <= TestEnum.SECOND - assert TestEnum.SECOND <= TestEnum.THIRD +def test_ensure_unique_string(): + """Test ensure_unique_string.""" + assert util.ensure_unique_string("Beer", ["Beer", "Beer_2"]) == 'Beer_3' + assert util.ensure_unique_string("Beer", ["Wine", "Soda"]) == 'Beer' - assert not (TestEnum.SECOND < TestEnum.FIRST) - assert not (TestEnum.SECOND < TestEnum.SECOND) - assert TestEnum.SECOND < TestEnum.THIRD - # Python will raise a TypeError if the <, <=, >, >= methods - # raise a NotImplemented error. - with pytest.raises(TypeError): - TestEnum.FIRST < 1 +def test_ordered_enum(): + """Test the ordered enum class.""" + class TestEnum(util.OrderedEnum): + """Test enum that can be ordered.""" - with pytest.raises(TypeError): - TestEnum.FIRST <= 1 + FIRST = 1 + SECOND = 2 + THIRD = 3 - with pytest.raises(TypeError): - TestEnum.FIRST > 1 + assert TestEnum.SECOND >= TestEnum.FIRST + assert TestEnum.SECOND >= TestEnum.SECOND + assert TestEnum.SECOND < TestEnum.THIRD - with pytest.raises(TypeError): - TestEnum.FIRST >= 1 + assert TestEnum.SECOND > TestEnum.FIRST + assert TestEnum.SECOND <= TestEnum.SECOND + assert TestEnum.SECOND <= TestEnum.THIRD - def test_throttle(self): - """Test the add cooldown decorator.""" - calls1 = [] - calls2 = [] + assert TestEnum.SECOND > TestEnum.FIRST + assert TestEnum.SECOND <= TestEnum.SECOND + assert TestEnum.SECOND <= TestEnum.THIRD - @util.Throttle(timedelta(seconds=4)) - def test_throttle1(): - calls1.append(1) + assert TestEnum.SECOND >= TestEnum.FIRST + assert TestEnum.SECOND >= TestEnum.SECOND + assert TestEnum.SECOND < TestEnum.THIRD - @util.Throttle(timedelta(seconds=4), timedelta(seconds=2)) - def test_throttle2(): - calls2.append(1) + # Python will raise a TypeError if the <, <=, >, >= methods + # raise a NotImplemented error. + with pytest.raises(TypeError): + TestEnum.FIRST < 1 - now = dt_util.utcnow() - plus3 = now + timedelta(seconds=3) - plus5 = plus3 + timedelta(seconds=2) + with pytest.raises(TypeError): + TestEnum.FIRST <= 1 - # Call first time and ensure methods got called + with pytest.raises(TypeError): + TestEnum.FIRST > 1 + + with pytest.raises(TypeError): + TestEnum.FIRST >= 1 + + +def test_throttle(): + """Test the add cooldown decorator.""" + calls1 = [] + calls2 = [] + + @util.Throttle(timedelta(seconds=4)) + def test_throttle1(): + calls1.append(1) + + @util.Throttle(timedelta(seconds=4), timedelta(seconds=2)) + def test_throttle2(): + calls2.append(1) + + now = dt_util.utcnow() + plus3 = now + timedelta(seconds=3) + plus5 = plus3 + timedelta(seconds=2) + + # Call first time and ensure methods got called + test_throttle1() + test_throttle2() + + assert len(calls1) == 1 + assert len(calls2) == 1 + + # Call second time. Methods should not get called + test_throttle1() + test_throttle2() + + assert len(calls1) == 1 + assert len(calls2) == 1 + + # Call again, overriding throttle, only first one should fire + test_throttle1(no_throttle=True) + test_throttle2(no_throttle=True) + + assert len(calls1) == 2 + assert len(calls2) == 1 + + with patch('homeassistant.util.utcnow', return_value=plus3): test_throttle1() test_throttle2() - assert 1 == len(calls1) - assert 1 == len(calls2) + assert len(calls1) == 2 + assert len(calls2) == 1 - # Call second time. Methods should not get called + with patch('homeassistant.util.utcnow', return_value=plus5): test_throttle1() test_throttle2() - assert 1 == len(calls1) - assert 1 == len(calls2) + assert len(calls1) == 3 + assert len(calls2) == 2 - # Call again, overriding throttle, only first one should fire - test_throttle1(no_throttle=True) - test_throttle2(no_throttle=True) - assert 2 == len(calls1) - assert 1 == len(calls2) +def test_throttle_per_instance(): + """Test that the throttle method is done per instance of a class.""" + class Tester: + """A tester class for the throttle.""" - with patch('homeassistant.util.utcnow', return_value=plus3): - test_throttle1() - test_throttle2() + @util.Throttle(timedelta(seconds=1)) + def hello(self): + """Test the throttle.""" + return True - assert 2 == len(calls1) - assert 1 == len(calls2) + assert Tester().hello() + assert Tester().hello() - with patch('homeassistant.util.utcnow', return_value=plus5): - test_throttle1() - test_throttle2() - assert 3 == len(calls1) - assert 2 == len(calls2) +def test_throttle_on_method(): + """Test that throttle works when wrapping a method.""" + class Tester: + """A tester class for the throttle.""" - def test_throttle_per_instance(self): - """Test that the throttle method is done per instance of a class.""" - class Tester: - """A tester class for the throttle.""" + def hello(self): + """Test the throttle.""" + return True - @util.Throttle(timedelta(seconds=1)) - def hello(self): - """Test the throttle.""" - return True + tester = Tester() + throttled = util.Throttle(timedelta(seconds=1))(tester.hello) - assert Tester().hello() - assert Tester().hello() + assert throttled() + assert throttled() is None - def test_throttle_on_method(self): - """Test that throttle works when wrapping a method.""" - class Tester: - """A tester class for the throttle.""" - def hello(self): - """Test the throttle.""" - return True +def test_throttle_on_two_method(): + """Test that throttle works when wrapping two methods.""" + class Tester: + """A test class for the throttle.""" - tester = Tester() - throttled = util.Throttle(timedelta(seconds=1))(tester.hello) + @util.Throttle(timedelta(seconds=1)) + def hello(self): + """Test the throttle.""" + return True - assert throttled() - assert throttled() is None + @util.Throttle(timedelta(seconds=1)) + def goodbye(self): + """Test the throttle.""" + return True - def test_throttle_on_two_method(self): - """Test that throttle works when wrapping two methods.""" - class Tester: - """A test class for the throttle.""" + tester = Tester() - @util.Throttle(timedelta(seconds=1)) - def hello(self): - """Test the throttle.""" - return True + assert tester.hello() + assert tester.goodbye() - @util.Throttle(timedelta(seconds=1)) - def goodbye(self): - """Test the throttle.""" - return True - tester = Tester() +@patch.object(util, 'random') +def test_get_random_string(mock_random): + """Test get random string.""" + results = ['A', 'B', 'C'] - assert tester.hello() - assert tester.goodbye() + def mock_choice(choices): + return results.pop(0) - @patch.object(util, 'random') - def test_get_random_string(self, mock_random): - """Test get random string.""" - results = ['A', 'B', 'C'] + generator = MagicMock() + generator.choice.side_effect = mock_choice + mock_random.SystemRandom.return_value = generator - def mock_choice(choices): - return results.pop(0) - - generator = MagicMock() - generator.choice.side_effect = mock_choice - mock_random.SystemRandom.return_value = generator - - assert util.get_random_string(length=3) == 'ABC' + assert util.get_random_string(length=3) == 'ABC' async def test_throttle_async(): diff --git a/tests/util/test_json.py b/tests/util/test_json.py index a7df74d9225..79e4613a2b4 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -2,15 +2,16 @@ from json import JSONEncoder import os import unittest +from unittest.mock import Mock import sys from tempfile import mkdtemp +import pytest + from homeassistant.util.json import ( SerializationError, load_json, save_json) from homeassistant.exceptions import HomeAssistantError -import pytest -from unittest.mock import Mock # Test data that can be saved as JSON TEST_JSON_A = {"a": 1, "B": "two"} @@ -19,75 +20,82 @@ TEST_JSON_B = {"a": "one", "B": 2} TEST_BAD_OBJECT = {("A",): 1} # Test data that can not be loaded as JSON TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" +TMP_DIR = None -class TestJSON(unittest.TestCase): - """Test util.json save and load.""" +def setup(): + """Set up for tests.""" + global TMP_DIR + TMP_DIR = mkdtemp() - def setUp(self): - """Set up for tests.""" - self.tmp_dir = mkdtemp() - def tearDown(self): - """Clean up after tests.""" - for fname in os.listdir(self.tmp_dir): - os.remove(os.path.join(self.tmp_dir, fname)) - os.rmdir(self.tmp_dir) +def teardown(): + """Clean up after tests.""" + for fname in os.listdir(TMP_DIR): + os.remove(os.path.join(TMP_DIR, fname)) + os.rmdir(TMP_DIR) - def _path_for(self, leaf_name): - return os.path.join(self.tmp_dir, leaf_name+".json") - def test_save_and_load(self): - """Test saving and loading back.""" - fname = self._path_for("test1") - save_json(fname, TEST_JSON_A) - data = load_json(fname) - assert data == TEST_JSON_A +def _path_for(leaf_name): + return os.path.join(TMP_DIR, leaf_name+".json") - # Skipped on Windows - @unittest.skipIf(sys.platform.startswith('win'), - "private permissions not supported on Windows") - def test_save_and_load_private(self): - """Test we can load private files and that they are protected.""" - fname = self._path_for("test2") - save_json(fname, TEST_JSON_A, private=True) - data = load_json(fname) - assert data == TEST_JSON_A - stats = os.stat(fname) - assert stats.st_mode & 0o77 == 0 - def test_overwrite_and_reload(self): - """Test that we can overwrite an existing file and read back.""" - fname = self._path_for("test3") - save_json(fname, TEST_JSON_A) - save_json(fname, TEST_JSON_B) - data = load_json(fname) - assert data == TEST_JSON_B +def test_save_and_load(): + """Test saving and loading back.""" + fname = _path_for("test1") + save_json(fname, TEST_JSON_A) + data = load_json(fname) + assert data == TEST_JSON_A - def test_save_bad_data(self): - """Test error from trying to save unserialisable data.""" - fname = self._path_for("test4") - with pytest.raises(SerializationError): - save_json(fname, TEST_BAD_OBJECT) - def test_load_bad_data(self): - """Test error from trying to load unserialisable data.""" - fname = self._path_for("test5") - with open(fname, "w") as fh: - fh.write(TEST_BAD_SERIALIED) - with pytest.raises(HomeAssistantError): - load_json(fname) +# Skipped on Windows +@unittest.skipIf(sys.platform.startswith('win'), + "private permissions not supported on Windows") +def test_save_and_load_private(): + """Test we can load private files and that they are protected.""" + fname = _path_for("test2") + save_json(fname, TEST_JSON_A, private=True) + data = load_json(fname) + assert data == TEST_JSON_A + stats = os.stat(fname) + assert stats.st_mode & 0o77 == 0 - def test_custom_encoder(self): - """Test serializing with a custom encoder.""" - class MockJSONEncoder(JSONEncoder): - """Mock JSON encoder.""" - def default(self, o): - """Mock JSON encode method.""" - return "9" +def test_overwrite_and_reload(): + """Test that we can overwrite an existing file and read back.""" + fname = _path_for("test3") + save_json(fname, TEST_JSON_A) + save_json(fname, TEST_JSON_B) + data = load_json(fname) + assert data == TEST_JSON_B - fname = self._path_for("test6") - save_json(fname, Mock(), encoder=MockJSONEncoder) - data = load_json(fname) - self.assertEqual(data, "9") + +def test_save_bad_data(): + """Test error from trying to save unserialisable data.""" + fname = _path_for("test4") + with pytest.raises(SerializationError): + save_json(fname, TEST_BAD_OBJECT) + + +def test_load_bad_data(): + """Test error from trying to load unserialisable data.""" + fname = _path_for("test5") + with open(fname, "w") as fh: + fh.write(TEST_BAD_SERIALIED) + with pytest.raises(HomeAssistantError): + load_json(fname) + + +def test_custom_encoder(): + """Test serializing with a custom encoder.""" + class MockJSONEncoder(JSONEncoder): + """Mock JSON encoder.""" + + def default(self, o): + """Mock JSON encode method.""" + return "9" + + fname = _path_for("test6") + save_json(fname, Mock(), encoder=MockJSONEncoder) + data = load_json(fname) + assert data == "9" diff --git a/tests/util/test_pressure.py b/tests/util/test_pressure.py index a3e6efb3754..245000761ad 100644 --- a/tests/util/test_pressure.py +++ b/tests/util/test_pressure.py @@ -1,5 +1,4 @@ """Test homeassistant pressure utility functions.""" -import unittest import pytest from homeassistant.const import (PRESSURE_PA, PRESSURE_HPA, PRESSURE_MBAR, @@ -10,57 +9,50 @@ INVALID_SYMBOL = 'bob' VALID_SYMBOL = PRESSURE_PA -class TestPressureUtil(unittest.TestCase): - """Test the pressure utility functions.""" +def test_convert_same_unit(): + """Test conversion from any unit to same unit.""" + assert pressure_util.convert(2, PRESSURE_PA, PRESSURE_PA) == 2 + assert pressure_util.convert(3, PRESSURE_HPA, PRESSURE_HPA) == 3 + assert pressure_util.convert(4, PRESSURE_MBAR, PRESSURE_MBAR) == 4 + assert pressure_util.convert(5, PRESSURE_INHG, PRESSURE_INHG) == 5 - def test_convert_same_unit(self): - """Test conversion from any unit to same unit.""" - assert pressure_util.convert(2, PRESSURE_PA, PRESSURE_PA) == 2 - assert pressure_util.convert(3, PRESSURE_HPA, PRESSURE_HPA) == 3 - assert pressure_util.convert(4, PRESSURE_MBAR, PRESSURE_MBAR) == 4 - assert pressure_util.convert(5, PRESSURE_INHG, PRESSURE_INHG) == 5 - def test_convert_invalid_unit(self): - """Test exception is thrown for invalid units.""" - with pytest.raises(ValueError): - pressure_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) +def test_convert_invalid_unit(): + """Test exception is thrown for invalid units.""" + with pytest.raises(ValueError): + pressure_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) - with pytest.raises(ValueError): - pressure_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) + with pytest.raises(ValueError): + pressure_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) - def test_convert_nonnumeric_value(self): - """Test exception is thrown for nonnumeric type.""" - with pytest.raises(TypeError): - pressure_util.convert('a', PRESSURE_HPA, PRESSURE_INHG) - def test_convert_from_hpascals(self): - """Test conversion from hPA to other units.""" - hpascals = 1000 - self.assertAlmostEqual( - pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PSI), - 14.5037743897) - self.assertAlmostEqual( - pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_INHG), - 29.5299801647) - self.assertAlmostEqual( - pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PA), - 100000) - self.assertAlmostEqual( - pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_MBAR), - 1000) +def test_convert_nonnumeric_value(): + """Test exception is thrown for nonnumeric type.""" + with pytest.raises(TypeError): + pressure_util.convert('a', PRESSURE_HPA, PRESSURE_INHG) - def test_convert_from_inhg(self): - """Test conversion from inHg to other units.""" - inhg = 30 - self.assertAlmostEqual( - pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PSI), - 14.7346266155) - self.assertAlmostEqual( - pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_HPA), - 1015.9167) - self.assertAlmostEqual( - pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PA), - 101591.67) - self.assertAlmostEqual( - pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_MBAR), - 1015.9167) + +def test_convert_from_hpascals(): + """Test conversion from hPA to other units.""" + hpascals = 1000 + assert pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PSI) == \ + pytest.approx(14.5037743897) + assert pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_INHG) == \ + pytest.approx(29.5299801647) + assert pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_PA) == \ + pytest.approx(100000) + assert pressure_util.convert(hpascals, PRESSURE_HPA, PRESSURE_MBAR) == \ + pytest.approx(1000) + + +def test_convert_from_inhg(): + """Test conversion from inHg to other units.""" + inhg = 30 + assert pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PSI) == \ + pytest.approx(14.7346266155) + assert pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_HPA) == \ + pytest.approx(1015.9167) + assert pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_PA) == \ + pytest.approx(101591.67) + assert pressure_util.convert(inhg, PRESSURE_INHG, PRESSURE_MBAR) == \ + pytest.approx(1015.9167) diff --git a/tests/util/test_ruamel_yaml.py b/tests/util/test_ruamel_yaml.py index 4ac8b85ae98..907cebc7f14 100644 --- a/tests/util/test_ruamel_yaml.py +++ b/tests/util/test_ruamel_yaml.py @@ -1,6 +1,5 @@ """Test Home Assistant ruamel.yaml loader.""" import os -import unittest from tempfile import mkdtemp import pytest @@ -114,45 +113,51 @@ views: cards: !include cards.yaml """ +TMP_DIR = None -class TestYAML(unittest.TestCase): - """Test lovelace.yaml save and load.""" - def setUp(self): - """Set up for tests.""" - self.tmp_dir = mkdtemp() - self.yaml = YAML(typ='rt') +def setup(): + """Set up for tests.""" + global TMP_DIR + TMP_DIR = mkdtemp() - def tearDown(self): - """Clean up after tests.""" - for fname in os.listdir(self.tmp_dir): - os.remove(os.path.join(self.tmp_dir, fname)) - os.rmdir(self.tmp_dir) - def _path_for(self, leaf_name): - return os.path.join(self.tmp_dir, leaf_name+".yaml") +def teardown(): + """Clean up after tests.""" + for fname in os.listdir(TMP_DIR): + os.remove(os.path.join(TMP_DIR, fname)) + os.rmdir(TMP_DIR) - def test_save_and_load(self): - """Test saving and loading back.""" - fname = self._path_for("test1") - open(fname, "w+").close() - util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_A)) - data = util_yaml.load_yaml(fname, True) - assert data == self.yaml.load(TEST_YAML_A) - def test_overwrite_and_reload(self): - """Test that we can overwrite an existing file and read back.""" - fname = self._path_for("test2") - open(fname, "w+").close() - util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_A)) - util_yaml.save_yaml(fname, self.yaml.load(TEST_YAML_B)) - data = util_yaml.load_yaml(fname, True) - assert data == self.yaml.load(TEST_YAML_B) +def _path_for(leaf_name): + return os.path.join(TMP_DIR, leaf_name+".yaml") - def test_load_bad_data(self): - """Test error from trying to load unserialisable data.""" - fname = self._path_for("test3") - with open(fname, "w") as fh: - fh.write(TEST_BAD_YAML) - with pytest.raises(HomeAssistantError): - util_yaml.load_yaml(fname, True) + +def test_save_and_load(): + """Test saving and loading back.""" + yaml = YAML(typ='rt') + fname = _path_for("test1") + open(fname, "w+").close() + util_yaml.save_yaml(fname, yaml.load(TEST_YAML_A)) + data = util_yaml.load_yaml(fname, True) + assert data == yaml.load(TEST_YAML_A) + + +def test_overwrite_and_reload(): + """Test that we can overwrite an existing file and read back.""" + yaml = YAML(typ='rt') + fname = _path_for("test2") + open(fname, "w+").close() + util_yaml.save_yaml(fname, yaml.load(TEST_YAML_A)) + util_yaml.save_yaml(fname, yaml.load(TEST_YAML_B)) + data = util_yaml.load_yaml(fname, True) + assert data == yaml.load(TEST_YAML_B) + + +def test_load_bad_data(): + """Test error from trying to load unserialisable data.""" + fname = _path_for("test3") + with open(fname, "w") as fh: + fh.write(TEST_BAD_YAML) + with pytest.raises(HomeAssistantError): + util_yaml.load_yaml(fname, True) diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 533ce3c0a15..39d5db1ff83 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -1,5 +1,5 @@ """Test the unit system helper.""" -import unittest +import pytest from homeassistant.util.unit_system import ( UnitSystem, @@ -19,141 +19,138 @@ from homeassistant.const import ( TEMPERATURE, VOLUME ) -import pytest - SYSTEM_NAME = 'TEST' INVALID_UNIT = 'INVALID' -class TestUnitSystem(unittest.TestCase): - """Test the unit system helper.""" +def test_invalid_units(): + """Test errors are raised when invalid units are passed in.""" + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS, PRESSURE_PA) - def test_invalid_units(self): - """Test errors are raised when invalid units are passed in.""" - with pytest.raises(ValueError): - UnitSystem(SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, VOLUME_LITERS, - MASS_GRAMS, PRESSURE_PA) + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, VOLUME_LITERS, + MASS_GRAMS, PRESSURE_PA) - with pytest.raises(ValueError): - UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, VOLUME_LITERS, - MASS_GRAMS, PRESSURE_PA) + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, + MASS_GRAMS, PRESSURE_PA) - with pytest.raises(ValueError): - UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, - MASS_GRAMS, PRESSURE_PA) + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, + INVALID_UNIT, PRESSURE_PA) - with pytest.raises(ValueError): - UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, - INVALID_UNIT, PRESSURE_PA) + with pytest.raises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS, INVALID_UNIT) - with pytest.raises(ValueError): - UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, - MASS_GRAMS, INVALID_UNIT) - def test_invalid_value(self): - """Test no conversion happens if value is non-numeric.""" - with pytest.raises(TypeError): - METRIC_SYSTEM.length('25a', LENGTH_KILOMETERS) - with pytest.raises(TypeError): - METRIC_SYSTEM.temperature('50K', TEMP_CELSIUS) - with pytest.raises(TypeError): - METRIC_SYSTEM.volume('50L', VOLUME_LITERS) - with pytest.raises(TypeError): - METRIC_SYSTEM.pressure('50Pa', PRESSURE_PA) +def test_invalid_value(): + """Test no conversion happens if value is non-numeric.""" + with pytest.raises(TypeError): + METRIC_SYSTEM.length('25a', LENGTH_KILOMETERS) + with pytest.raises(TypeError): + METRIC_SYSTEM.temperature('50K', TEMP_CELSIUS) + with pytest.raises(TypeError): + METRIC_SYSTEM.volume('50L', VOLUME_LITERS) + with pytest.raises(TypeError): + METRIC_SYSTEM.pressure('50Pa', PRESSURE_PA) - def test_as_dict(self): - """Test that the as_dict() method returns the expected dictionary.""" - expected = { - LENGTH: LENGTH_KILOMETERS, - TEMPERATURE: TEMP_CELSIUS, - VOLUME: VOLUME_LITERS, - MASS: MASS_GRAMS, - PRESSURE: PRESSURE_PA - } - assert expected == METRIC_SYSTEM.as_dict() +def test_as_dict(): + """Test that the as_dict() method returns the expected dictionary.""" + expected = { + LENGTH: LENGTH_KILOMETERS, + TEMPERATURE: TEMP_CELSIUS, + VOLUME: VOLUME_LITERS, + MASS: MASS_GRAMS, + PRESSURE: PRESSURE_PA + } - def test_temperature_same_unit(self): - """Test no conversion happens if to unit is same as from unit.""" - assert 5 == \ - METRIC_SYSTEM.temperature(5, - METRIC_SYSTEM.temperature_unit) + assert expected == METRIC_SYSTEM.as_dict() - def test_temperature_unknown_unit(self): - """Test no conversion happens if unknown unit.""" - with pytest.raises(ValueError): - METRIC_SYSTEM.temperature(5, 'K') - def test_temperature_to_metric(self): - """Test temperature conversion to metric system.""" - assert 25 == \ - METRIC_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit) - assert 26.7 == \ - round(METRIC_SYSTEM.temperature( - 80, IMPERIAL_SYSTEM.temperature_unit), 1) +def test_temperature_same_unit(): + """Test no conversion happens if to unit is same as from unit.""" + assert METRIC_SYSTEM.temperature(5, METRIC_SYSTEM.temperature_unit) == 5 - def test_temperature_to_imperial(self): - """Test temperature conversion to imperial system.""" - assert 77 == \ - IMPERIAL_SYSTEM.temperature(77, IMPERIAL_SYSTEM.temperature_unit) - assert 77 == \ - IMPERIAL_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit) - def test_length_unknown_unit(self): - """Test length conversion with unknown from unit.""" - with pytest.raises(ValueError): - METRIC_SYSTEM.length(5, 'fr') +def test_temperature_unknown_unit(): + """Test no conversion happens if unknown unit.""" + with pytest.raises(ValueError): + METRIC_SYSTEM.temperature(5, 'K') - def test_length_to_metric(self): - """Test length conversion to metric system.""" - assert 100 == \ - METRIC_SYSTEM.length(100, METRIC_SYSTEM.length_unit) - assert 8.04672 == \ - METRIC_SYSTEM.length(5, IMPERIAL_SYSTEM.length_unit) - def test_length_to_imperial(self): - """Test length conversion to imperial system.""" - assert 100 == \ - IMPERIAL_SYSTEM.length(100, - IMPERIAL_SYSTEM.length_unit) - assert 3.106855 == \ - IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) +def test_temperature_to_metric(): + """Test temperature conversion to metric system.""" + assert METRIC_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit) == 25 + assert round(METRIC_SYSTEM.temperature( + 80, IMPERIAL_SYSTEM.temperature_unit), 1) == 26.7 - def test_pressure_same_unit(self): - """Test no conversion happens if to unit is same as from unit.""" - assert 5 == \ - METRIC_SYSTEM.pressure(5, METRIC_SYSTEM.pressure_unit) - def test_pressure_unknown_unit(self): - """Test no conversion happens if unknown unit.""" - with pytest.raises(ValueError): - METRIC_SYSTEM.pressure(5, 'K') +def test_temperature_to_imperial(): + """Test temperature conversion to imperial system.""" + assert IMPERIAL_SYSTEM.temperature( + 77, IMPERIAL_SYSTEM.temperature_unit) == 77 + assert IMPERIAL_SYSTEM.temperature( + 25, METRIC_SYSTEM.temperature_unit) == 77 - def test_pressure_to_metric(self): - """Test pressure conversion to metric system.""" - assert 25 == \ - METRIC_SYSTEM.pressure(25, METRIC_SYSTEM.pressure_unit) - self.assertAlmostEqual( - METRIC_SYSTEM.pressure(14.7, IMPERIAL_SYSTEM.pressure_unit), - 101352.932, places=1) - def test_pressure_to_imperial(self): - """Test pressure conversion to imperial system.""" - assert 77 == \ - IMPERIAL_SYSTEM.pressure(77, IMPERIAL_SYSTEM.pressure_unit) - self.assertAlmostEqual( - IMPERIAL_SYSTEM.pressure(101352.932, METRIC_SYSTEM.pressure_unit), - 14.7, places=4) +def test_length_unknown_unit(): + """Test length conversion with unknown from unit.""" + with pytest.raises(ValueError): + METRIC_SYSTEM.length(5, 'fr') - def test_properties(self): - """Test the unit properties are returned as expected.""" - assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit - assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit - assert MASS_GRAMS == METRIC_SYSTEM.mass_unit - assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit - assert PRESSURE_PA == METRIC_SYSTEM.pressure_unit - def test_is_metric(self): - """Test the is metric flag.""" - assert METRIC_SYSTEM.is_metric - assert not IMPERIAL_SYSTEM.is_metric +def test_length_to_metric(): + """Test length conversion to metric system.""" + assert METRIC_SYSTEM.length(100, METRIC_SYSTEM.length_unit) == 100 + assert METRIC_SYSTEM.length(5, IMPERIAL_SYSTEM.length_unit) == 8.04672 + + +def test_length_to_imperial(): + """Test length conversion to imperial system.""" + assert IMPERIAL_SYSTEM.length(100, IMPERIAL_SYSTEM.length_unit) == 100 + assert IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) == 3.106855 + + +def test_pressure_same_unit(): + """Test no conversion happens if to unit is same as from unit.""" + assert METRIC_SYSTEM.pressure(5, METRIC_SYSTEM.pressure_unit) == 5 + + +def test_pressure_unknown_unit(): + """Test no conversion happens if unknown unit.""" + with pytest.raises(ValueError): + METRIC_SYSTEM.pressure(5, 'K') + + +def test_pressure_to_metric(): + """Test pressure conversion to metric system.""" + assert METRIC_SYSTEM.pressure(25, METRIC_SYSTEM.pressure_unit) == 25 + assert METRIC_SYSTEM.pressure(14.7, IMPERIAL_SYSTEM.pressure_unit) == \ + pytest.approx(101352.932, abs=1e-1) + + +def test_pressure_to_imperial(): + """Test pressure conversion to imperial system.""" + assert IMPERIAL_SYSTEM.pressure(77, IMPERIAL_SYSTEM.pressure_unit) == 77 + assert IMPERIAL_SYSTEM.pressure( + 101352.932, METRIC_SYSTEM.pressure_unit) == \ + pytest.approx(14.7, abs=1e-4) + + +def test_properties(): + """Test the unit properties are returned as expected.""" + assert LENGTH_KILOMETERS == METRIC_SYSTEM.length_unit + assert TEMP_CELSIUS == METRIC_SYSTEM.temperature_unit + assert MASS_GRAMS == METRIC_SYSTEM.mass_unit + assert VOLUME_LITERS == METRIC_SYSTEM.volume_unit + assert PRESSURE_PA == METRIC_SYSTEM.pressure_unit + + +def test_is_metric(): + """Test the is metric flag.""" + assert METRIC_SYSTEM.is_metric + assert not IMPERIAL_SYSTEM.is_metric diff --git a/tests/util/test_volume.py b/tests/util/test_volume.py index 26208d37b68..7f8da3910cf 100644 --- a/tests/util/test_volume.py +++ b/tests/util/test_volume.py @@ -1,49 +1,45 @@ """Test homeassistant volume utility functions.""" -import unittest +import pytest + import homeassistant.util.volume as volume_util from homeassistant.const import (VOLUME_LITERS, VOLUME_MILLILITERS, VOLUME_GALLONS, VOLUME_FLUID_OUNCE) -import pytest - INVALID_SYMBOL = 'bob' VALID_SYMBOL = VOLUME_LITERS -class TestVolumeUtil(unittest.TestCase): - """Test the volume utility functions.""" +def test_convert_same_unit(): + """Test conversion from any unit to same unit.""" + assert volume_util.convert(2, VOLUME_LITERS, VOLUME_LITERS) == 2 + assert volume_util.convert(3, VOLUME_MILLILITERS, VOLUME_MILLILITERS) == 3 + assert volume_util.convert(4, VOLUME_GALLONS, VOLUME_GALLONS) == 4 + assert volume_util.convert(5, VOLUME_FLUID_OUNCE, VOLUME_FLUID_OUNCE) == 5 - def test_convert_same_unit(self): - """Test conversion from any unit to same unit.""" - assert 2 == volume_util.convert(2, VOLUME_LITERS, VOLUME_LITERS) - assert 3 == volume_util.convert(3, VOLUME_MILLILITERS, - VOLUME_MILLILITERS) - assert 4 == volume_util.convert(4, VOLUME_GALLONS, - VOLUME_GALLONS) - assert 5 == volume_util.convert(5, VOLUME_FLUID_OUNCE, - VOLUME_FLUID_OUNCE) - def test_convert_invalid_unit(self): - """Test exception is thrown for invalid units.""" - with pytest.raises(ValueError): - volume_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) +def test_convert_invalid_unit(): + """Test exception is thrown for invalid units.""" + with pytest.raises(ValueError): + volume_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) - with pytest.raises(ValueError): - volume_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) + with pytest.raises(ValueError): + volume_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) - def test_convert_nonnumeric_value(self): - """Test exception is thrown for nonnumeric type.""" - with pytest.raises(TypeError): - volume_util.convert('a', VOLUME_GALLONS, VOLUME_LITERS) - def test_convert_from_liters(self): - """Test conversion from liters to other units.""" - liters = 5 - assert volume_util.convert(liters, VOLUME_LITERS, - VOLUME_GALLONS) == 1.321 +def test_convert_nonnumeric_value(): + """Test exception is thrown for nonnumeric type.""" + with pytest.raises(TypeError): + volume_util.convert('a', VOLUME_GALLONS, VOLUME_LITERS) - def test_convert_from_gallons(self): - """Test conversion from gallons to other units.""" - gallons = 5 - assert volume_util.convert(gallons, VOLUME_GALLONS, - VOLUME_LITERS) == 18.925 + +def test_convert_from_liters(): + """Test conversion from liters to other units.""" + liters = 5 + assert volume_util.convert(liters, VOLUME_LITERS, VOLUME_GALLONS) == 1.321 + + +def test_convert_from_gallons(): + """Test conversion from gallons to other units.""" + gallons = 5 + assert volume_util.convert(gallons, VOLUME_GALLONS, + VOLUME_LITERS) == 18.925 diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 99eee30027c..c7d1be3d58c 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -21,256 +21,269 @@ def mock_credstash(): yield mock_credstash -class TestYaml(unittest.TestCase): - """Test util.yaml loader.""" +def test_simple_list(): + """Test simple list.""" + conf = "config:\n - simple\n - list" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc['config'] == ["simple", "list"] - # pylint: disable=no-self-use, invalid-name - def test_simple_list(self): - """Test simple list.""" - conf = "config:\n - simple\n - list" +def test_simple_dict(): + """Test simple dict.""" + conf = "key: value" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc['key'] == 'value' + + +def test_unhashable_key(): + """Test an unhasable key.""" + files = {YAML_CONFIG_FILE: 'message:\n {{ states.state }}'} + with pytest.raises(HomeAssistantError), \ + patch_yaml_files(files): + load_yaml_config_file(YAML_CONFIG_FILE) + + +def test_no_key(): + """Test item without a key.""" + files = {YAML_CONFIG_FILE: 'a: a\nnokeyhere'} + with pytest.raises(HomeAssistantError), \ + patch_yaml_files(files): + yaml.load_yaml(YAML_CONFIG_FILE) + + +def test_environment_variable(): + """Test config file with environment variable.""" + os.environ["PASSWORD"] = "secret_password" + conf = "password: !env_var PASSWORD" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc['password'] == "secret_password" + del os.environ["PASSWORD"] + + +def test_environment_variable_default(): + """Test config file with default value for environment variable.""" + conf = "password: !env_var PASSWORD secret_password" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc['password'] == "secret_password" + + +def test_invalid_environment_variable(): + """Test config file with no environment variable sat.""" + conf = "password: !env_var PASSWORD" + with pytest.raises(HomeAssistantError): + with io.StringIO(conf) as file: + yaml.yaml.safe_load(file) + + +def test_include_yaml(): + """Test include yaml.""" + with patch_yaml_files({'test.yaml': 'value'}): + conf = 'key: !include test.yaml' with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) - assert doc['config'] == ["simple", "list"] + assert doc["key"] == "value" - def test_simple_dict(self): - """Test simple dict.""" - conf = "key: value" + with patch_yaml_files({'test.yaml': None}): + conf = 'key: !include test.yaml' with io.StringIO(conf) as file: doc = yaml.yaml.safe_load(file) - assert doc['key'] == 'value' + assert doc["key"] == {} - def test_unhashable_key(self): - """Test an unhasable key.""" - files = {YAML_CONFIG_FILE: 'message:\n {{ states.state }}'} - with pytest.raises(HomeAssistantError), \ - patch_yaml_files(files): - load_yaml_config_file(YAML_CONFIG_FILE) - def test_no_key(self): - """Test item without a key.""" - files = {YAML_CONFIG_FILE: 'a: a\nnokeyhere'} - with pytest.raises(HomeAssistantError), \ - patch_yaml_files(files): - yaml.load_yaml(YAML_CONFIG_FILE) +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_list(mock_walk): + """Test include dir list yaml.""" + mock_walk.return_value = [ + ['/tmp', [], ['two.yaml', 'one.yaml']], + ] - def test_environment_variable(self): - """Test config file with environment variable.""" - os.environ["PASSWORD"] = "secret_password" - conf = "password: !env_var PASSWORD" - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc['password'] == "secret_password" - del os.environ["PASSWORD"] - - def test_environment_variable_default(self): - """Test config file with default value for environment variable.""" - conf = "password: !env_var PASSWORD secret_password" - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc['password'] == "secret_password" - - def test_invalid_environment_variable(self): - """Test config file with no environment variable sat.""" - conf = "password: !env_var PASSWORD" - with pytest.raises(HomeAssistantError): - with io.StringIO(conf) as file: - yaml.yaml.safe_load(file) - - def test_include_yaml(self): - """Test include yaml.""" - with patch_yaml_files({'test.yaml': 'value'}): - conf = 'key: !include test.yaml' - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc["key"] == "value" - - with patch_yaml_files({'test.yaml': None}): - conf = 'key: !include test.yaml' - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc["key"] == {} - - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_list(self, mock_walk): - """Test include dir list yaml.""" - mock_walk.return_value = [ - ['/tmp', [], ['two.yaml', 'one.yaml']], - ] - - with patch_yaml_files({ + with patch_yaml_files({ '/tmp/one.yaml': 'one', '/tmp/two.yaml': 'two', - }): - conf = "key: !include_dir_list /tmp" - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc["key"] == sorted(["one", "two"]) + }): + conf = "key: !include_dir_list /tmp" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc["key"] == sorted(["one", "two"]) - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_list_recursive(self, mock_walk): - """Test include dir recursive list yaml.""" - mock_walk.return_value = [ - ['/tmp', ['tmp2', '.ignore', 'ignore'], ['zero.yaml']], - ['/tmp/tmp2', [], ['one.yaml', 'two.yaml']], - ['/tmp/ignore', [], ['.ignore.yaml']] - ] - with patch_yaml_files({ +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_list_recursive(mock_walk): + """Test include dir recursive list yaml.""" + mock_walk.return_value = [ + ['/tmp', ['tmp2', '.ignore', 'ignore'], ['zero.yaml']], + ['/tmp/tmp2', [], ['one.yaml', 'two.yaml']], + ['/tmp/ignore', [], ['.ignore.yaml']] + ] + + with patch_yaml_files({ '/tmp/zero.yaml': 'zero', '/tmp/tmp2/one.yaml': 'one', '/tmp/tmp2/two.yaml': 'two' - }): - conf = "key: !include_dir_list /tmp" - with io.StringIO(conf) as file: - assert '.ignore' in mock_walk.return_value[0][1], \ - "Expecting .ignore in here" - doc = yaml.yaml.safe_load(file) - assert 'tmp2' in mock_walk.return_value[0][1] - assert '.ignore' not in mock_walk.return_value[0][1] - assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) + }): + conf = "key: !include_dir_list /tmp" + with io.StringIO(conf) as file: + assert '.ignore' in mock_walk.return_value[0][1], \ + "Expecting .ignore in here" + doc = yaml.yaml.safe_load(file) + assert 'tmp2' in mock_walk.return_value[0][1] + assert '.ignore' not in mock_walk.return_value[0][1] + assert sorted(doc["key"]) == sorted(["zero", "one", "two"]) - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_named(self, mock_walk): - """Test include dir named yaml.""" - mock_walk.return_value = [ - ['/tmp', [], ['first.yaml', 'second.yaml', 'secrets.yaml']] - ] - with patch_yaml_files({ +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_named(mock_walk): + """Test include dir named yaml.""" + mock_walk.return_value = [ + ['/tmp', [], ['first.yaml', 'second.yaml', 'secrets.yaml']] + ] + + with patch_yaml_files({ '/tmp/first.yaml': 'one', '/tmp/second.yaml': 'two' - }): - conf = "key: !include_dir_named /tmp" - correct = {'first': 'one', 'second': 'two'} - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc["key"] == correct + }): + conf = "key: !include_dir_named /tmp" + correct = {'first': 'one', 'second': 'two'} + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc["key"] == correct - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_named_recursive(self, mock_walk): - """Test include dir named yaml.""" - mock_walk.return_value = [ - ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], - ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], - ['/tmp/ignore', [], ['.ignore.yaml']] - ] - with patch_yaml_files({ +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_named_recursive(mock_walk): + """Test include dir named yaml.""" + mock_walk.return_value = [ + ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ['/tmp/ignore', [], ['.ignore.yaml']] + ] + + with patch_yaml_files({ '/tmp/first.yaml': 'one', '/tmp/tmp2/second.yaml': 'two', '/tmp/tmp2/third.yaml': 'three' - }): - conf = "key: !include_dir_named /tmp" - correct = {'first': 'one', 'second': 'two', 'third': 'three'} - with io.StringIO(conf) as file: - assert '.ignore' in mock_walk.return_value[0][1], \ - "Expecting .ignore in here" - doc = yaml.yaml.safe_load(file) - assert 'tmp2' in mock_walk.return_value[0][1] - assert '.ignore' not in mock_walk.return_value[0][1] - assert doc["key"] == correct + }): + conf = "key: !include_dir_named /tmp" + correct = {'first': 'one', 'second': 'two', 'third': 'three'} + with io.StringIO(conf) as file: + assert '.ignore' in mock_walk.return_value[0][1], \ + "Expecting .ignore in here" + doc = yaml.yaml.safe_load(file) + assert 'tmp2' in mock_walk.return_value[0][1] + assert '.ignore' not in mock_walk.return_value[0][1] + assert doc["key"] == correct - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_merge_list(self, mock_walk): - """Test include dir merge list yaml.""" - mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] - with patch_yaml_files({ +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_merge_list(mock_walk): + """Test include dir merge list yaml.""" + mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] + + with patch_yaml_files({ '/tmp/first.yaml': '- one', '/tmp/second.yaml': '- two\n- three' - }): - conf = "key: !include_dir_merge_list /tmp" - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert sorted(doc["key"]) == sorted(["one", "two", "three"]) + }): + conf = "key: !include_dir_merge_list /tmp" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert sorted(doc["key"]) == sorted(["one", "two", "three"]) - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_merge_list_recursive(self, mock_walk): - """Test include dir merge list yaml.""" - mock_walk.return_value = [ - ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], - ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], - ['/tmp/ignore', [], ['.ignore.yaml']] - ] - with patch_yaml_files({ +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_merge_list_recursive(mock_walk): + """Test include dir merge list yaml.""" + mock_walk.return_value = [ + ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ['/tmp/ignore', [], ['.ignore.yaml']] + ] + + with patch_yaml_files({ '/tmp/first.yaml': '- one', '/tmp/tmp2/second.yaml': '- two', '/tmp/tmp2/third.yaml': '- three\n- four' - }): - conf = "key: !include_dir_merge_list /tmp" - with io.StringIO(conf) as file: - assert '.ignore' in mock_walk.return_value[0][1], \ - "Expecting .ignore in here" - doc = yaml.yaml.safe_load(file) - assert 'tmp2' in mock_walk.return_value[0][1] - assert '.ignore' not in mock_walk.return_value[0][1] - assert sorted(doc["key"]) == sorted(["one", "two", - "three", "four"]) + }): + conf = "key: !include_dir_merge_list /tmp" + with io.StringIO(conf) as file: + assert '.ignore' in mock_walk.return_value[0][1], \ + "Expecting .ignore in here" + doc = yaml.yaml.safe_load(file) + assert 'tmp2' in mock_walk.return_value[0][1] + assert '.ignore' not in mock_walk.return_value[0][1] + assert sorted(doc["key"]) == sorted(["one", "two", + "three", "four"]) - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_merge_named(self, mock_walk): - """Test include dir merge named yaml.""" - mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] - files = { - '/tmp/first.yaml': 'key1: one', - '/tmp/second.yaml': 'key2: two\nkey3: three', - } +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_merge_named(mock_walk): + """Test include dir merge named yaml.""" + mock_walk.return_value = [['/tmp', [], ['first.yaml', 'second.yaml']]] - with patch_yaml_files(files): - conf = "key: !include_dir_merge_named /tmp" - with io.StringIO(conf) as file: - doc = yaml.yaml.safe_load(file) - assert doc["key"] == { - "key1": "one", - "key2": "two", - "key3": "three" - } + files = { + '/tmp/first.yaml': 'key1: one', + '/tmp/second.yaml': 'key2: two\nkey3: three', + } - @patch('homeassistant.util.yaml.os.walk') - def test_include_dir_merge_named_recursive(self, mock_walk): - """Test include dir merge named yaml.""" - mock_walk.return_value = [ - ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], - ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], - ['/tmp/ignore', [], ['.ignore.yaml']] - ] + with patch_yaml_files(files): + conf = "key: !include_dir_merge_named /tmp" + with io.StringIO(conf) as file: + doc = yaml.yaml.safe_load(file) + assert doc["key"] == { + "key1": "one", + "key2": "two", + "key3": "three" + } - with patch_yaml_files({ + +@patch('homeassistant.util.yaml.os.walk') +def test_include_dir_merge_named_recursive(mock_walk): + """Test include dir merge named yaml.""" + mock_walk.return_value = [ + ['/tmp', ['tmp2', '.ignore', 'ignore'], ['first.yaml']], + ['/tmp/tmp2', [], ['second.yaml', 'third.yaml']], + ['/tmp/ignore', [], ['.ignore.yaml']] + ] + + with patch_yaml_files({ '/tmp/first.yaml': 'key1: one', '/tmp/tmp2/second.yaml': 'key2: two', '/tmp/tmp2/third.yaml': 'key3: three\nkey4: four' - }): - conf = "key: !include_dir_merge_named /tmp" - with io.StringIO(conf) as file: - assert '.ignore' in mock_walk.return_value[0][1], \ - "Expecting .ignore in here" - doc = yaml.yaml.safe_load(file) - assert 'tmp2' in mock_walk.return_value[0][1] - assert '.ignore' not in mock_walk.return_value[0][1] - assert doc["key"] == { - "key1": "one", - "key2": "two", - "key3": "three", - "key4": "four" - } + }): + conf = "key: !include_dir_merge_named /tmp" + with io.StringIO(conf) as file: + assert '.ignore' in mock_walk.return_value[0][1], \ + "Expecting .ignore in here" + doc = yaml.yaml.safe_load(file) + assert 'tmp2' in mock_walk.return_value[0][1] + assert '.ignore' not in mock_walk.return_value[0][1] + assert doc["key"] == { + "key1": "one", + "key2": "two", + "key3": "three", + "key4": "four" + } - @patch('homeassistant.util.yaml.open', create=True) - def test_load_yaml_encoding_error(self, mock_open): - """Test raising a UnicodeDecodeError.""" - mock_open.side_effect = UnicodeDecodeError('', b'', 1, 0, '') - with pytest.raises(HomeAssistantError): - yaml.load_yaml('test') - def test_dump(self): - """The that the dump method returns empty None values.""" - assert yaml.dump({'a': None, 'b': 'b'}) == 'a:\nb: b\n' +@patch('homeassistant.util.yaml.open', create=True) +def test_load_yaml_encoding_error(mock_open): + """Test raising a UnicodeDecodeError.""" + mock_open.side_effect = UnicodeDecodeError('', b'', 1, 0, '') + with pytest.raises(HomeAssistantError): + yaml.load_yaml('test') - def test_dump_unicode(self): - """The that the dump method returns empty None values.""" - assert yaml.dump({'a': None, 'b': 'привет'}) == 'a:\nb: привет\n' + +def test_dump(): + """The that the dump method returns empty None values.""" + assert yaml.dump({'a': None, 'b': 'b'}) == 'a:\nb: b\n' + + +def test_dump_unicode(): + """The that the dump method returns empty None values.""" + assert yaml.dump({'a': None, 'b': 'привет'}) == 'a:\nb: привет\n' FILES = {} @@ -415,9 +428,9 @@ class TestSecrets(unittest.TestCase): def test_secrets_are_not_dict(self): """Did secrets handle non-dict file.""" FILES[self._secret_path] = ( - '- http_pw: pwhttp\n' - ' comp1_un: un1\n' - ' comp1_pw: pw1\n') + '- http_pw: pwhttp\n' + ' comp1_un: un1\n' + ' comp1_pw: pw1\n') yaml.clear_secret_cache() with pytest.raises(HomeAssistantError): load_yaml(self._yaml_path,