diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index e92b32d73b4..a0d654133de 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -19,6 +19,8 @@ from homeassistant.const import ( SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK) +_LOGGER = logging.getLogger(__name__) + DOMAIN = 'media_player' SCAN_INTERVAL = 10 @@ -230,11 +232,9 @@ def setup(hass, config): def media_player_service_handler(service): """Map services to methods on MediaPlayerDevice.""" - target_players = component.extract_from_service(service) - method = SERVICE_TO_METHOD[service.service] - for player in target_players: + for player in component.extract_from_service(service): getattr(player, method)() if player.should_poll: @@ -246,14 +246,15 @@ def setup(hass, config): def volume_set_service(service): """Set specified volume on the media player.""" - target_players = component.extract_from_service(service) + volume = service.data.get(ATTR_MEDIA_VOLUME_LEVEL) - if ATTR_MEDIA_VOLUME_LEVEL not in service.data: + if volume is None: + _LOGGER.error( + 'Received call to %s without attribute %s', + service.service, ATTR_MEDIA_VOLUME_LEVEL) return - volume = service.data[ATTR_MEDIA_VOLUME_LEVEL] - - for player in target_players: + for player in component.extract_from_service(service): player.set_volume_level(volume) if player.should_poll: @@ -264,14 +265,15 @@ def setup(hass, config): def volume_mute_service(service): """Mute (true) or unmute (false) the media player.""" - target_players = component.extract_from_service(service) + mute = service.data.get(ATTR_MEDIA_VOLUME_MUTED) - if ATTR_MEDIA_VOLUME_MUTED not in service.data: + if mute is None: + _LOGGER.error( + 'Received call to %s without attribute %s', + service.service, ATTR_MEDIA_VOLUME_MUTED) return - mute = service.data[ATTR_MEDIA_VOLUME_MUTED] - - for player in target_players: + for player in component.extract_from_service(service): player.mute_volume(mute) if player.should_poll: @@ -282,14 +284,15 @@ def setup(hass, config): def media_seek_service(service): """Seek to a position.""" - target_players = component.extract_from_service(service) + position = service.data.get(ATTR_MEDIA_SEEK_POSITION) - if ATTR_MEDIA_SEEK_POSITION not in service.data: + if position is None: + _LOGGER.error( + 'Received call to %s without attribute %s', + service.service, ATTR_MEDIA_SEEK_POSITION) return - position = service.data[ATTR_MEDIA_SEEK_POSITION] - - for player in target_players: + for player in component.extract_from_service(service): player.media_seek(position) if player.should_poll: @@ -303,10 +306,12 @@ def setup(hass, config): media_type = service.data.get(ATTR_MEDIA_CONTENT_TYPE) media_id = service.data.get(ATTR_MEDIA_CONTENT_ID) - if media_type is None: - return - - if media_id is None: + if media_type is None or media_id is None: + missing_attr = (ATTR_MEDIA_CONTENT_TYPE if media_type is None + else ATTR_MEDIA_CONTENT_ID) + _LOGGER.error( + 'Received call to %s without attribute %s', + service.service, missing_attr) return for player in component.extract_from_service(service): diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index 913d41c27ba..a433fe4d7ab 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -239,7 +239,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): if self._cur_track > 0: support |= SUPPORT_PREVIOUS_TRACK - if self._cur_track < len(self.tracks)-1: + if self._cur_track < len(self.tracks) - 1: support |= SUPPORT_NEXT_TRACK return support @@ -252,7 +252,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): def media_next_track(self): """Send next track command.""" - if self._cur_track < len(self.tracks)-1: + if self._cur_track < len(self.tracks) - 1: self._cur_track += 1 self.update_ha_state() diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 85086aa15cc..d142c5a29d1 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -71,6 +71,9 @@ def setup(hass, config): message = call.data.get(ATTR_MESSAGE) if message is None: + _LOGGER.error( + 'Received call to %s without attribute %s', + call.service, ATTR_MESSAGE) return title = template.render( diff --git a/homeassistant/components/notify/file.py b/homeassistant/components/notify/file.py index f9b186e59ad..b474f12be63 100644 --- a/homeassistant/components/notify/file.py +++ b/homeassistant/components/notify/file.py @@ -45,7 +45,7 @@ class FileNotificationService(BaseNotificationService): title = '{} notifications (Log started: {})\n{}\n'.format( kwargs.get(ATTR_TITLE), dt_util.strip_microseconds(dt_util.utcnow()), - '-'*80) + '-' * 80) file.write(title) if self.add_timestamp == 1: diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index fa1cd702c8f..9e128f95630 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -123,6 +123,9 @@ def setup(hass, config): service.data.get(ATTR_TEMPERATURE), float) if temperature is None: + _LOGGER.error( + "Received call to %s without attribute %s", + SERVICE_SET_TEMPERATURE, ATTR_TEMPERATURE) return for thermostat in target_thermostats: diff --git a/homeassistant/components/thermostat/demo.py b/homeassistant/components/thermostat/demo.py index bceb7048a34..ce5a3130d94 100644 --- a/homeassistant/components/thermostat/demo.py +++ b/homeassistant/components/thermostat/demo.py @@ -11,8 +11,8 @@ from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Demo thermostats.""" add_devices([ - DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19), - DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77), + DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19, False), + DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77, True), ]) @@ -21,13 +21,14 @@ class DemoThermostat(ThermostatDevice): """Representation of a demo thermostat.""" def __init__(self, name, target_temperature, unit_of_measurement, - away, current_temperature): + away, current_temperature, is_fan_on): """Initialize the thermostat.""" self._name = name self._target_temperature = target_temperature self._unit_of_measurement = unit_of_measurement self._away = away self._current_temperature = current_temperature + self._is_fan_on = is_fan_on @property def should_poll(self): @@ -59,6 +60,11 @@ class DemoThermostat(ThermostatDevice): """Return if away mode is on.""" return self._away + @property + def is_fan_on(self): + """Return true if the fan is on.""" + return self._is_fan_on + def set_temperature(self, temperature): """Set new target temperature.""" self._target_temperature = temperature @@ -70,3 +76,11 @@ class DemoThermostat(ThermostatDevice): def turn_away_mode_off(self): """Turn away mode off.""" self._away = False + + def turn_fan_on(self): + """Turn fan on.""" + self._is_fan_on = True + + def turn_fan_off(self): + """Turn fan off.""" + self._is_fan_on = False diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 499a5cd8c18..7f4dc4e37c4 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -25,6 +25,11 @@ class TestDemoMediaPlayer(unittest.TestCase): state = self.hass.states.get(entity_id) assert 1.0 == state.attributes.get('volume_level') + mp.set_volume_level(self.hass, None, entity_id) + self.hass.pool.block_till_done() + state = self.hass.states.get(entity_id) + assert 1.0 == state.attributes.get('volume_level') + mp.set_volume_level(self.hass, 0.5, entity_id) self.hass.pool.block_till_done() state = self.hass.states.get(entity_id) @@ -41,6 +46,12 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 0.5 == state.attributes.get('volume_level') assert False is state.attributes.get('is_volume_muted') + + mp.mute_volume(self.hass, None, entity_id) + self.hass.pool.block_till_done() + state = self.hass.states.get(entity_id) + assert False is state.attributes.get('is_volume_muted') + mp.mute_volume(self.hass, True, entity_id) self.hass.pool.block_till_done() state = self.hass.states.get(entity_id) @@ -87,7 +98,7 @@ class TestDemoMediaPlayer(unittest.TestCase): assert self.hass.states.is_state(entity_id, 'playing') def test_prev_next_track(self): - """Test media_next_track and media_prevoius_track .""" + """Test media_next_track and media_previous_track .""" assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) state = self.hass.states.get(entity_id) assert 1 == state.attributes.get('media_track') @@ -115,6 +126,27 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) + assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + ent_id = 'media_player.lounge_room' + state = self.hass.states.get(ent_id) + assert 1 == state.attributes.get('media_episode') + assert 0 == (mp.SUPPORT_PREVIOUS_TRACK & + state.attributes.get('supported_media_commands')) + + mp.media_next_track(self.hass, ent_id) + self.hass.pool.block_till_done() + state = self.hass.states.get(ent_id) + assert 2 == state.attributes.get('media_episode') + assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & + state.attributes.get('supported_media_commands')) + + mp.media_previous_track(self.hass, ent_id) + self.hass.pool.block_till_done() + state = self.hass.states.get(ent_id) + assert 1 == state.attributes.get('media_episode') + assert 0 == (mp.SUPPORT_PREVIOUS_TRACK & + state.attributes.get('supported_media_commands')) + @patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.' 'media_seek') def test_play_media(self, mock_seek): @@ -126,6 +158,13 @@ class TestDemoMediaPlayer(unittest.TestCase): state.attributes.get('supported_media_commands')) assert state.attributes.get('media_content_id') is not None + mp.play_media(self.hass, None, 'some_id', ent_id) + self.hass.pool.block_till_done() + state = self.hass.states.get(ent_id) + assert 0 < (mp.SUPPORT_PLAY_MEDIA & + state.attributes.get('supported_media_commands')) + assert not 'some_id' == state.attributes.get('media_content_id') + mp.play_media(self.hass, 'youtube', 'some_id', ent_id) self.hass.pool.block_till_done() state = self.hass.states.get(ent_id) @@ -133,6 +172,9 @@ class TestDemoMediaPlayer(unittest.TestCase): state.attributes.get('supported_media_commands')) assert 'some_id' == state.attributes.get('media_content_id') + assert not mock_seek.called + mp.media_seek(self.hass, None, ent_id) + self.hass.pool.block_till_done() assert not mock_seek.called mp.media_seek(self.hass, 100, ent_id) self.hass.pool.block_till_done() diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index 6777b2f5c9b..f7b86d42e5f 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -25,8 +25,37 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): self._media_title = None self._supported_media_commands = 0 - self.turn_off_service_calls = mock_service( - hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF) + self.service_calls = { + 'turn_on': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_TURN_ON), + 'turn_off': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_TURN_OFF), + 'mute_volume': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_MUTE), + 'set_volume_level': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET), + 'media_play': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY), + 'media_pause': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE), + 'media_previous_track': mock_service( + hass, media_player.DOMAIN, + media_player.SERVICE_MEDIA_PREVIOUS_TRACK), + 'media_next_track': mock_service( + hass, media_player.DOMAIN, + media_player.SERVICE_MEDIA_NEXT_TRACK), + 'media_seek': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK), + 'play_media': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_PLAY_MEDIA), + 'volume_up': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_UP), + 'volume_down': mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_DOWN), + 'media_play_pause': mock_service( + hass, media_player.DOMAIN, + media_player.SERVICE_MEDIA_PLAY_PAUSE), + } @property def name(self): @@ -97,23 +126,26 @@ class TestMediaPlayer(unittest.TestCase): self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state') self.hass.states.set(self.mock_state_switch_id, STATE_OFF) - self.config_children_only = \ - {'name': 'test', 'platform': 'universal', - 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), - media_player.ENTITY_ID_FORMAT.format('mock2')]} - self.config_children_and_attr = \ - {'name': 'test', 'platform': 'universal', - 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), - media_player.ENTITY_ID_FORMAT.format('mock2')], - 'attributes': { - 'is_volume_muted': self.mock_mute_switch_id, - 'state': self.mock_state_switch_id}} + self.config_children_only = { + 'name': 'test', 'platform': 'universal', + 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), + media_player.ENTITY_ID_FORMAT.format('mock2')] + } + self.config_children_and_attr = { + 'name': 'test', 'platform': 'universal', + 'children': [media_player.ENTITY_ID_FORMAT.format('mock1'), + media_player.ENTITY_ID_FORMAT.format('mock2')], + 'attributes': { + 'is_volume_muted': self.mock_mute_switch_id, + 'state': self.mock_state_switch_id + } + } def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() - def test_check_config_children_only(self): + def test_config_children_only(self): """Check config with only children.""" config_start = copy(self.config_children_only) del config_start['platform'] @@ -125,7 +157,7 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual(config_start, self.config_children_only) - def test_check_config_children_and_attr(self): + def test_config_children_and_attr(self): """Check config with children and attributes.""" config_start = copy(self.config_children_and_attr) del config_start['platform'] @@ -136,13 +168,13 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual(config_start, self.config_children_and_attr) - def test_check_config_no_name(self): + def test_config_no_name(self): """Check config with no Name entry.""" response = universal.validate_config({'platform': 'universal'}) self.assertFalse(response) - def test_check_config_bad_children(self): + def test_config_bad_children(self): """Check config with bad children entry.""" config_no_children = {'name': 'test', 'platform': 'universal'} config_bad_children = {'name': 'test', 'children': {}, @@ -156,7 +188,7 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual([], config_bad_children['children']) - def test_check_config_bad_commands(self): + def test_config_bad_commands(self): """Check config with bad commands entry.""" config = {'name': 'test', 'commands': [], 'platform': 'universal'} @@ -164,7 +196,7 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual({}, config['commands']) - def test_check_config_bad_attributes(self): + def test_config_bad_attributes(self): """Check config with bad attributes.""" config = {'name': 'test', 'attributes': [], 'platform': 'universal'} @@ -172,7 +204,7 @@ class TestMediaPlayer(unittest.TestCase): self.assertTrue(response) self.assertEqual({}, config['attributes']) - def test_check_config_bad_key(self): + def test_config_bad_key(self): """Check config with bad key.""" config = {'name': 'test', 'asdf': 5, 'platform': 'universal'} @@ -183,6 +215,7 @@ class TestMediaPlayer(unittest.TestCase): def test_platform_setup(self): """Test platform setup.""" config = {'name': 'test', 'platform': 'universal'} + bad_config = {'platform': 'universal'} entities = [] def add_devices(new_entities): @@ -190,8 +223,10 @@ class TestMediaPlayer(unittest.TestCase): for dev in new_entities: entities.append(dev) - universal.setup_platform(self.hass, config, add_devices) + universal.setup_platform(self.hass, bad_config, add_devices) + self.assertEqual(0, len(entities)) + universal.setup_platform(self.hass, config, add_devices) self.assertEqual(1, len(entities)) self.assertEqual('test', entities[0].name) @@ -263,6 +298,15 @@ class TestMediaPlayer(unittest.TestCase): self.assertEqual(config['name'], ump.name) + def test_polling(self): + """Test should_poll property.""" + config = self.config_children_only + universal.validate_config(config) + + ump = universal.UniversalMediaPlayer(self.hass, **config) + + self.assertEqual(False, ump.should_poll) + def test_state_children_only(self): """Test media player state with only children.""" config = self.config_children_only @@ -388,8 +432,7 @@ class TestMediaPlayer(unittest.TestCase): ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name']) ump.update() - self.mock_mp_1._supported_media_commands = \ - universal.SUPPORT_VOLUME_SET + self.mock_mp_1._supported_media_commands = universal.SUPPORT_VOLUME_SET self.mock_mp_1._state = STATE_PLAYING self.mock_mp_1.update_ha_state() ump.update() @@ -400,7 +443,7 @@ class TestMediaPlayer(unittest.TestCase): self.assertEqual(check_flags, ump.supported_media_commands) def test_service_call_to_child(self): - """Test a service call that should be routed to a child.""" + """Test service calls that should be routed to a child.""" config = self.config_children_only universal.validate_config(config) @@ -413,13 +456,53 @@ class TestMediaPlayer(unittest.TestCase): ump.update() ump.turn_off() - self.assertEqual(1, len(self.mock_mp_2.turn_off_service_calls)) + self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_off'])) + + ump.turn_on() + self.assertEqual(1, len(self.mock_mp_2.service_calls['turn_on'])) + + ump.mute_volume(True) + self.assertEqual(1, len(self.mock_mp_2.service_calls['mute_volume'])) + + ump.set_volume_level(0.5) + self.assertEqual( + 1, len(self.mock_mp_2.service_calls['set_volume_level'])) + + ump.media_play() + self.assertEqual(1, len(self.mock_mp_2.service_calls['media_play'])) + + ump.media_pause() + self.assertEqual(1, len(self.mock_mp_2.service_calls['media_pause'])) + + ump.media_previous_track() + self.assertEqual( + 1, len(self.mock_mp_2.service_calls['media_previous_track'])) + + ump.media_next_track() + self.assertEqual( + 1, len(self.mock_mp_2.service_calls['media_next_track'])) + + ump.media_seek(100) + self.assertEqual(1, len(self.mock_mp_2.service_calls['media_seek'])) + + ump.play_media('movie', 'batman') + self.assertEqual(1, len(self.mock_mp_2.service_calls['play_media'])) + + ump.volume_up() + self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_up'])) + + ump.volume_down() + self.assertEqual(1, len(self.mock_mp_2.service_calls['volume_down'])) + + ump.media_play_pause() + self.assertEqual( + 1, len(self.mock_mp_2.service_calls['media_play_pause'])) def test_service_call_to_command(self): """Test service call to command.""" config = self.config_children_only - config['commands'] = \ - {'turn_off': {'service': 'test.turn_off', 'data': {}}} + config['commands'] = {'turn_off': { + 'service': 'test.turn_off', 'data': {}}} universal.validate_config(config) service = mock_service(self.hass, 'test', 'turn_off') diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py index 8f1c41bbb92..ffe156deb9d 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/notify/test_command_line.py @@ -3,8 +3,10 @@ import os import tempfile import unittest -from homeassistant import core import homeassistant.components.notify as notify + +from tests.common import get_test_home_assistant + from unittest.mock import patch @@ -13,12 +15,27 @@ class TestCommandLine(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.hass = core.HomeAssistant() + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" self.hass.stop() + def test_bad_config(self): + """Test set up the platform with bad/missing config.""" + self.assertFalse(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'bad_platform', + } + })) + self.assertFalse(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + } + })) + def test_command_line_output(self): """Test the command line output.""" with tempfile.TemporaryDirectory() as tempdirname: @@ -41,7 +58,7 @@ class TestCommandLine(unittest.TestCase): @patch('homeassistant.components.notify.command_line._LOGGER.error') def test_error_for_none_zero_exit_code(self, mock_error): - """Test if an error if logged for non zero exit codes.""" + """Test if an error is logged for non zero exit codes.""" self.assertTrue(notify.setup(self.hass, { 'notify': { 'name': 'test', diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 92f4723323b..0d4f2115ca7 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -30,6 +30,12 @@ class TestNotifyDemo(unittest.TestCase): """"Stop down everything that was started.""" self.hass.stop() + def test_sending_none_message(self): + """Test send with None as message.""" + notify.send_message(self.hass, None) + self.hass.pool.block_till_done() + self.assertTrue(len(self.events) == 0) + def test_sending_templated_message(self): """Send a templated message.""" self.hass.states.set('sensor.temperature', 10) diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py new file mode 100644 index 00000000000..00dc80f84f4 --- /dev/null +++ b/tests/components/notify/test_file.py @@ -0,0 +1,56 @@ +"""The tests for the notify file platform.""" +import os +import unittest +import tempfile + +import homeassistant.components.notify as notify +from homeassistant.components.notify import ( + ATTR_TITLE_DEFAULT) +import homeassistant.util.dt as dt_util + +from tests.common import get_test_home_assistant + + +class TestNotifyFile(unittest.TestCase): + """Test the file notify.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """"Stop down everything that was started.""" + self.hass.stop() + + def test_bad_config(self): + """Test set up the platform with bad/missing config.""" + self.assertFalse(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'file', + } + })) + + def test_notify_file(self): + """Test the notify file output.""" + with tempfile.TemporaryDirectory() as tempdirname: + filename = os.path.join(tempdirname, 'notify.txt') + message = 'one, two, testing, testing' + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'file', + 'filename': filename, + 'timestamp': 0 + } + })) + title = '{} notifications (Log started: {})\n{}\n'.format( + ATTR_TITLE_DEFAULT, + dt_util.strip_microseconds(dt_util.utcnow()), + '-' * 80) + + self.hass.services.call('notify', 'test', {'message': message}, + blocking=True) + + result = open(filename).read() + self.assertEqual(result, "{}{}\n".format(title, message)) diff --git a/tests/components/thermostat/test_demo.py b/tests/components/thermostat/test_demo.py new file mode 100644 index 00000000000..2d202e8a4e1 --- /dev/null +++ b/tests/components/thermostat/test_demo.py @@ -0,0 +1,101 @@ +"""The tests for the demo thermostat.""" +import unittest + +from homeassistant.const import ( + TEMP_CELCIUS, +) +from homeassistant.components import thermostat + +from tests.common import get_test_home_assistant + + +ENTITY_NEST = 'thermostat.nest' + + +class TestDemoThermostat(unittest.TestCase): + """Test the Heat Control thermostat.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.hass.config.temperature_unit = TEMP_CELCIUS + self.assertTrue(thermostat.setup(self.hass, {'thermostat': { + 'platform': 'demo', + }})) + + def tearDown(self): # pylint: disable=invalid-name + """Stop down everything that was started.""" + self.hass.stop() + + def test_setup_params(self): + """Test the inititial parameters.""" + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual(21, state.attributes.get('temperature')) + self.assertEqual('off', state.attributes.get('away_mode')) + self.assertEqual(19, state.attributes.get('current_temperature')) + self.assertEqual('off', state.attributes.get('fan')) + + def test_default_setup_params(self): + """Test the setup with default parameters.""" + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual(7, state.attributes.get('min_temp')) + self.assertEqual(35, state.attributes.get('max_temp')) + + def test_set_target_temp_bad_attr(self): + """Test setting the target temperature without required attribute.""" + self.assertEqual('21', self.hass.states.get(ENTITY_NEST).state) + thermostat.set_temperature(self.hass, None, ENTITY_NEST) + self.hass.pool.block_till_done() + self.assertEqual('21', self.hass.states.get(ENTITY_NEST).state) + + def test_set_target_temp(self): + """Test the setting of the target temperature.""" + thermostat.set_temperature(self.hass, 30, ENTITY_NEST) + self.hass.pool.block_till_done() + self.assertEqual('30.0', self.hass.states.get(ENTITY_NEST).state) + + def test_set_away_mode_bad_attr(self): + """Test setting the away mode without required attribute.""" + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('away_mode')) + thermostat.set_away_mode(self.hass, None, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('away_mode')) + + def test_set_away_mode_on(self): + """Test setting the away mode on/true.""" + thermostat.set_away_mode(self.hass, True, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('on', state.attributes.get('away_mode')) + + def test_set_away_mode_off(self): + """Test setting the away mode off/false.""" + thermostat.set_away_mode(self.hass, False, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('away_mode')) + + def test_set_fan_mode_on_bad_attr(self): + """Test setting the fan mode on/true without required attribute.""" + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('fan')) + thermostat.set_fan_mode(self.hass, None, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('fan')) + + def test_set_fan_mode_on(self): + """Test setting the fan mode on/true.""" + thermostat.set_fan_mode(self.hass, True, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('on', state.attributes.get('fan')) + + def test_set_fan_mode_off(self): + """Test setting the fan mode off/false.""" + thermostat.set_fan_mode(self.hass, False, ENTITY_NEST) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY_NEST) + self.assertEqual('off', state.attributes.get('fan')) diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index cbd0c021bdb..cb75c72f8ca 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -1,5 +1,6 @@ """The tests for the heat control thermostat.""" import unittest +from unittest import mock from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, @@ -10,16 +11,55 @@ from homeassistant.const import ( TEMP_CELCIUS, ) from homeassistant.components import thermostat +import homeassistant.components.thermostat.heat_control as heat_control from tests.common import get_test_home_assistant -entity = 'thermostat.test' -ent_sensor = 'sensor.test' -ent_switch = 'switch.test' -min_temp = 3.0 -max_temp = 65.0 -target_temp = 42.0 +ENTITY = 'thermostat.test' +ENT_SENSOR = 'sensor.test' +ENT_SWITCH = 'switch.test' +MIN_TEMP = 3.0 +MAX_TEMP = 65.0 +TARGET_TEMP = 42.0 + + +class TestSetupThermostatHeatControl(unittest.TestCase): + """Test the Heat Control thermostat with custom config.""" + + def setUp(self): # pylint: disable=invalid-name + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): # pylint: disable=invalid-name + """Stop down everything that was started.""" + self.hass.stop() + + def test_setup_missing_conf(self): + """Test set up heat_control with missing config values.""" + config = { + 'name': 'test', + 'target_sensor': ENT_SENSOR + } + add_devices = mock.MagicMock() + result = heat_control.setup_platform(self.hass, config, add_devices) + self.assertEqual(False, result) + + def test_setup_with_sensor(self): + """Test set up heat_control with sensor to trigger update at init.""" + self.hass.states.set(ENT_SENSOR, 22.0, { + ATTR_UNIT_OF_MEASUREMENT: TEMP_CELCIUS + }) + thermostat.setup(self.hass, {'thermostat': { + 'platform': 'heat_control', + 'name': 'test', + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR + }}) + state = self.hass.states.get(ENTITY) + self.assertEqual( + TEMP_CELCIUS, state.attributes.get('unit_of_measurement')) + self.assertEqual(22.0, state.attributes.get('current_temperature')) class TestThermostatHeatControl(unittest.TestCase): @@ -32,8 +72,8 @@ class TestThermostatHeatControl(unittest.TestCase): thermostat.setup(self.hass, {'thermostat': { 'platform': 'heat_control', 'name': 'test', - 'heater': ent_switch, - 'target_sensor': ent_sensor + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR }}) def tearDown(self): # pylint: disable=invalid-name @@ -42,11 +82,11 @@ class TestThermostatHeatControl(unittest.TestCase): def test_setup_defaults_to_unknown(self): """Test the setting of defaults to unknown.""" - self.assertEqual('unknown', self.hass.states.get(entity).state) + self.assertEqual('unknown', self.hass.states.get(ENTITY).state) def test_default_setup_params(self): """Test the setup with default parameters.""" - state = self.hass.states.get(entity) + state = self.hass.states.get(ENTITY) self.assertEqual(7, state.attributes.get('min_temp')) self.assertEqual(35, state.attributes.get('max_temp')) self.assertEqual(None, state.attributes.get('temperature')) @@ -56,25 +96,41 @@ class TestThermostatHeatControl(unittest.TestCase): thermostat.setup(self.hass, {'thermostat': { 'platform': 'heat_control', 'name': 'test', - 'heater': ent_switch, - 'target_sensor': ent_sensor, - 'min_temp': min_temp, - 'max_temp': max_temp, - 'target_temp': target_temp + 'heater': ENT_SWITCH, + 'target_sensor': ENT_SENSOR, + 'min_temp': MIN_TEMP, + 'max_temp': MAX_TEMP, + 'target_temp': TARGET_TEMP }}) - state = self.hass.states.get(entity) - self.assertEqual(min_temp, state.attributes.get('min_temp')) - self.assertEqual(max_temp, state.attributes.get('max_temp')) - self.assertEqual(target_temp, state.attributes.get('temperature')) - self.assertEqual(str(target_temp), self.hass.states.get(entity).state) + state = self.hass.states.get(ENTITY) + self.assertEqual(MIN_TEMP, state.attributes.get('min_temp')) + self.assertEqual(MAX_TEMP, state.attributes.get('max_temp')) + self.assertEqual(TARGET_TEMP, state.attributes.get('temperature')) + self.assertEqual(str(TARGET_TEMP), self.hass.states.get(ENTITY).state) def test_set_target_temp(self): """Test the setting of the target temperature.""" thermostat.set_temperature(self.hass, 30) self.hass.pool.block_till_done() - self.assertEqual('30.0', self.hass.states.get(entity).state) + self.assertEqual('30.0', self.hass.states.get(ENTITY).state) - def test_set_target_temp_turns_on_heater(self): + def test_sensor_bad_unit(self): + """Test sensor that have bad unit.""" + self._setup_sensor(22.0, unit='bad_unit') + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY) + self.assertEqual(None, state.attributes.get('unit_of_measurement')) + self.assertEqual(None, state.attributes.get('current_temperature')) + + def test_sensor_bad_value(self): + """Test sensor that have None as state.""" + self._setup_sensor(None) + self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY) + self.assertEqual(None, state.attributes.get('unit_of_measurement')) + self.assertEqual(None, state.attributes.get('current_temperature')) + + def test_set_target_temp_heater_on(self): """Test if target temperature turn heater on.""" self._setup_switch(False) self._setup_sensor(25) @@ -85,9 +141,9 @@ class TestThermostatHeatControl(unittest.TestCase): call = self.calls[0] self.assertEqual('switch', call.domain) self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ent_switch, call.data['entity_id']) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) - def test_set_target_temp_turns_off_heater(self): + def test_set_target_temp_heater_off(self): """Test if target temperature turn heater off.""" self._setup_switch(True) self._setup_sensor(30) @@ -98,9 +154,9 @@ class TestThermostatHeatControl(unittest.TestCase): call = self.calls[0] self.assertEqual('switch', call.domain) self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ent_switch, call.data['entity_id']) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) - def test_set_temp_change_turns_on_heater(self): + def test_set_temp_change_heater_on(self): """Test if temperature change turn heater on.""" self._setup_switch(False) thermostat.set_temperature(self.hass, 30) @@ -111,9 +167,9 @@ class TestThermostatHeatControl(unittest.TestCase): call = self.calls[0] self.assertEqual('switch', call.domain) self.assertEqual(SERVICE_TURN_ON, call.service) - self.assertEqual(ent_switch, call.data['entity_id']) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) - def test_temp_change_turns_off_heater(self): + def test_temp_change_heater_off(self): """Test if temperature change turn heater off.""" self._setup_switch(True) thermostat.set_temperature(self.hass, 25) @@ -124,17 +180,17 @@ class TestThermostatHeatControl(unittest.TestCase): call = self.calls[0] self.assertEqual('switch', call.domain) self.assertEqual(SERVICE_TURN_OFF, call.service) - self.assertEqual(ent_switch, call.data['entity_id']) + self.assertEqual(ENT_SWITCH, call.data['entity_id']) def _setup_sensor(self, temp, unit=TEMP_CELCIUS): """Setup the test sensor.""" - self.hass.states.set(ent_sensor, temp, { + self.hass.states.set(ENT_SENSOR, temp, { ATTR_UNIT_OF_MEASUREMENT: unit }) def _setup_switch(self, is_on): """Setup the test switch.""" - self.hass.states.set(ent_switch, STATE_ON if is_on else STATE_OFF) + self.hass.states.set(ENT_SWITCH, STATE_ON if is_on else STATE_OFF) self.calls = [] def log_call(call): diff --git a/tests/components/thermostat/test_honeywell.py b/tests/components/thermostat/test_honeywell.py index ba3a7f5d8dc..159507948b8 100644 --- a/tests/components/thermostat/test_honeywell.py +++ b/tests/components/thermostat/test_honeywell.py @@ -23,6 +23,15 @@ class TestHoneywell(unittest.TestCase): CONF_PASSWORD: 'pass', 'region': 'us', } + bad_pass_config = { + CONF_USERNAME: 'user', + 'region': 'us', + } + bad_region_config = { + CONF_USERNAME: 'user', + CONF_PASSWORD: 'pass', + 'region': 'un', + } hass = mock.MagicMock() add_devices = mock.MagicMock() @@ -37,6 +46,10 @@ class TestHoneywell(unittest.TestCase): locations[0].devices_by_id.values.return_value = devices_1 locations[1].devices_by_id.values.return_value = devices_2 + result = honeywell.setup_platform(hass, bad_pass_config, add_devices) + self.assertFalse(result) + result = honeywell.setup_platform(hass, bad_region_config, add_devices) + self.assertFalse(result) result = honeywell.setup_platform(hass, config, add_devices) self.assertTrue(result) mock_sc.assert_called_once_with('user', 'pass')