From 4110bd0acffdf13808d3ec54bb54577301dca225 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Tue, 9 Apr 2019 11:21:00 -0500 Subject: [PATCH] Add support for when device is not logged in to HEOS (#22913) --- homeassistant/components/heos/__init__.py | 25 ++++++++----- homeassistant/components/heos/media_player.py | 2 +- tests/components/heos/conftest.py | 2 + tests/components/heos/test_init.py | 37 ++++++++++++++++--- tests/components/heos/test_media_player.py | 33 ++++++++++++++++- 5 files changed, 82 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index ffbd8ebffd4..084444be4ea 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -75,12 +75,16 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): await controller.disconnect() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect_controller) + # Get players and sources try: - players, favorites, inputs = await asyncio.gather( - controller.get_players(), - controller.get_favorites(), - controller.get_input_sources() - ) + players = await controller.get_players() + favorites = {} + if controller.is_signed_in: + favorites = await controller.get_favorites() + else: + _LOGGER.warning("%s is not logged in to your HEOS account and will" + " be unable to retrieve your favorites", host) + inputs = await controller.get_input_sources() except (asyncio.TimeoutError, ConnectionError, CommandError) as error: await controller.disconnect() _LOGGER.debug("Unable to retrieve players and sources: %s", error, @@ -175,9 +179,11 @@ class SourceManager: retry_attempts = 0 while True: try: - return await asyncio.gather( - controller.get_favorites(), - controller.get_input_sources()) + favorites = {} + if controller.is_signed_in: + favorites = await controller.get_favorites() + inputs = await controller.get_input_sources() + return favorites, inputs except (asyncio.TimeoutError, ConnectionError, CommandError) \ as error: if retry_attempts < self.max_retry_attempts: @@ -192,7 +198,8 @@ class SourceManager: return async def update_sources(event): - if event in const.EVENT_SOURCES_CHANGED: + if event in (const.EVENT_SOURCES_CHANGED, + const.EVENT_USER_CHANGED): sources = await get_sources() # If throttled, it will return None if sources: diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 466c9ae8faa..72d42f8f66f 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -141,7 +141,7 @@ class HeosMediaPlayer(MediaPlayerDevice): async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" - await self._player.set_volume(volume * 100) + await self._player.set_volume(int(volume * 100)) async def async_update(self): """Update supported features of the player.""" diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index db675a24ee0..211153b1cc7 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -28,6 +28,8 @@ def controller_fixture(players, favorites, input_sources, dispatcher): mock_heos.players = players mock_heos.get_favorites.return_value = favorites mock_heos.get_input_sources.return_value = input_sources + mock_heos.is_signed_in = True + mock_heos.signed_in_username = "user@user.com" yield mock_heos diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index b6bc3e24e1a..408b2f7d088 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -5,8 +5,7 @@ from asynctest import patch from pyheos import CommandError, const import pytest -from homeassistant.components.heos import ( - SourceManager, async_setup_entry, async_unload_entry) +from homeassistant.components.heos import async_setup_entry, async_unload_entry from homeassistant.components.heos.const import ( DATA_CONTROLLER, DATA_SOURCE_MANAGER, DOMAIN) from homeassistant.components.media_player.const import ( @@ -61,7 +60,7 @@ async def test_async_setup_no_config_returns_true(hass, config_entry): async def test_async_setup_entry_loads_platforms( - hass, config_entry, controller): + hass, config_entry, controller, input_sources, favorites): """Test load connects to heos, retrieves players, and loads platforms.""" config_entry.add_to_hass(hass) with patch.object( @@ -71,10 +70,39 @@ async def test_async_setup_entry_loads_platforms( await hass.async_block_till_done() assert forward_mock.call_count == 1 assert controller.connect.call_count == 1 + assert controller.get_players.call_count == 1 + assert controller.get_favorites.call_count == 1 + assert controller.get_input_sources.call_count == 1 controller.disconnect.assert_not_called() assert hass.data[DOMAIN][DATA_CONTROLLER] == controller assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players - assert isinstance(hass.data[DOMAIN][DATA_SOURCE_MANAGER], SourceManager) + assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == favorites + assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources + + +async def test_async_setup_entry_not_signed_in_loads_platforms( + hass, config_entry, controller, input_sources, caplog): + """Test setup does not retrieve favorites when not logged in.""" + config_entry.add_to_hass(hass) + controller.is_signed_in = False + controller.signed_in_username = None + with patch.object( + hass.config_entries, 'async_forward_entry_setup') as forward_mock: + assert await async_setup_entry(hass, config_entry) + # Assert platforms loaded + await hass.async_block_till_done() + assert forward_mock.call_count == 1 + assert controller.connect.call_count == 1 + assert controller.get_players.call_count == 1 + assert controller.get_favorites.call_count == 0 + assert controller.get_input_sources.call_count == 1 + controller.disconnect.assert_not_called() + assert hass.data[DOMAIN][DATA_CONTROLLER] == controller + assert hass.data[DOMAIN][MEDIA_PLAYER_DOMAIN] == controller.players + assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].favorites == {} + assert hass.data[DOMAIN][DATA_SOURCE_MANAGER].inputs == input_sources + assert "127.0.0.1 is not logged in to your HEOS account and will be " \ + "unable to retrieve your favorites" in caplog.text async def test_async_setup_entry_connect_failure( @@ -138,4 +166,3 @@ async def test_update_sources_retry(hass, config_entry, config, controller, while "Unable to update sources" not in caplog.text: await asyncio.sleep(0.1) assert controller.get_favorites.call_count == 2 - assert controller.get_input_sources.call_count == 2 diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 2f8d7dfc1e9..dd36c2c013d 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -116,7 +116,12 @@ async def test_updates_start_from_signals( state = hass.states.get('media_player.test_player') assert state.state == STATE_PLAYING - # Test sources event update + +async def test_updates_from_sources_updated( + hass, config_entry, config, controller, input_sources): + """Tests player updates from changes in sources list.""" + await setup_platform(hass, config_entry, config) + player = controller.players[1] event = asyncio.Event() async def set_signal(): @@ -124,11 +129,34 @@ async def test_updates_start_from_signals( hass.helpers.dispatcher.async_dispatcher_connect( SIGNAL_HEOS_SOURCES_UPDATED, set_signal) - favorites.clear() + input_sources.clear() player.heos.dispatcher.send( const.SIGNAL_CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED) await event.wait() source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list + assert len(source_list) == 2 + state = hass.states.get('media_player.test_player') + assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list + + +async def test_updates_from_user_changed( + hass, config_entry, config, controller): + """Tests player updates from changes in user.""" + await setup_platform(hass, config_entry, config) + player = controller.players[1] + event = asyncio.Event() + + async def set_signal(): + event.set() + hass.helpers.dispatcher.async_dispatcher_connect( + SIGNAL_HEOS_SOURCES_UPDATED, set_signal) + + controller.is_signed_in = False + controller.signed_in_username = None + player.heos.dispatcher.send( + const.SIGNAL_CONTROLLER_EVENT, const.EVENT_USER_CHANGED) + await event.wait() + source_list = hass.data[DOMAIN][DATA_SOURCE_MANAGER].source_list assert len(source_list) == 1 state = hass.states.get('media_player.test_player') assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list @@ -194,6 +222,7 @@ async def test_services(hass, config_entry, config, controller): {ATTR_ENTITY_ID: 'media_player.test_player', ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True) player.set_volume.assert_called_once_with(100) + assert isinstance(player.set_volume.call_args[0][0], int) async def test_select_favorite(