diff --git a/homeassistant/components/heos/quality_scale.yaml b/homeassistant/components/heos/quality_scale.yaml index 2d73f4d29b8..3dd6953778b 100644 --- a/homeassistant/components/heos/quality_scale.yaml +++ b/homeassistant/components/heos/quality_scale.yaml @@ -38,15 +38,8 @@ rules: comment: Needs to be set to 0. The underlying library handles parallel updates. reauthentication-flow: done test-coverage: - status: todo - comment: | - 1. Integration has >95% coverage, however tests need to be updated to not patch internals. - 2. test_async_setup_entry_connect_failure and test_async_setup_entry_player_failure -> Instead of - calling async_setup_entry directly, rather use hass.config_entries.async_setup and then assert - the config_entry.state is what we expect. - 3. test_unload_entry -> We should use hass.config_entries.async_unload and assert the entry state - 4. Recommend using snapshot in test_state_attributes. - 5. Find a way to avoid using internal dispatcher in test_updates_from_connection_event. + status: done + comment: 99% test coverage # Gold devices: done diagnostics: todo diff --git a/tests/components/heos/snapshots/test_media_player.ambr b/tests/components/heos/snapshots/test_media_player.ambr new file mode 100644 index 00000000000..7ade53c92ee --- /dev/null +++ b/tests/components/heos/snapshots/test_media_player.ambr @@ -0,0 +1,34 @@ +# serializer version: 1 +# name: test_state_attributes + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'entity_picture': 'http://', + 'friendly_name': 'Test Player', + 'group_members': list([ + 'media_player.test_player', + 'media_player.test_player_2', + ]), + 'is_volume_muted': False, + 'media_album_id': 1, + 'media_album_name': 'Album', + 'media_artist': 'Artist', + 'media_content_id': '1', + 'media_content_type': , + 'media_queue_id': 1, + 'media_source_id': 1, + 'media_station': 'Station Name', + 'media_title': 'Song', + 'media_type': 'Station', + 'shuffle': False, + 'source_list': list([ + "Today's Hits Radio", + 'Classical MPR (Classical Music)', + 'HEOS Drive - Line In 1', + ]), + 'supported_features': , + 'volume_level': 0.25, + }), + 'entity_id': 'media_player.test_player', + 'state': 'idle', + }) +# --- diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py index 21f3606f9bd..b03d75e5798 100644 --- a/tests/components/heos/test_config_flow.py +++ b/tests/components/heos/test_config_flow.py @@ -3,7 +3,6 @@ from pyheos import CommandAuthenticationError, CommandFailedError, Heos, HeosError import pytest -from homeassistant.components import heos from homeassistant.components.heos.const import DOMAIN from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME @@ -44,7 +43,7 @@ async def test_cannot_connect_shows_error_form( """Test form is shown with error when cannot connect.""" controller.connect.side_effect = HeosError() result = await hass.config_entries.flow.async_init( - heos.DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "127.0.0.1"} + DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "127.0.0.1"} ) assert result["type"] is FlowResultType.FORM assert result["step_id"] == "user" @@ -60,7 +59,7 @@ async def test_create_entry_when_host_valid( data = {CONF_HOST: "127.0.0.1"} result = await hass.config_entries.flow.async_init( - heos.DOMAIN, context={"source": SOURCE_USER}, data=data + DOMAIN, context={"source": SOURCE_USER}, data=data ) assert result["type"] is FlowResultType.CREATE_ENTRY assert result["result"].unique_id == DOMAIN @@ -78,7 +77,7 @@ async def test_create_entry_when_friendly_name_valid( data = {CONF_HOST: "Office (127.0.0.1)"} result = await hass.config_entries.flow.async_init( - heos.DOMAIN, context={"source": SOURCE_USER}, data=data + DOMAIN, context={"source": SOURCE_USER}, data=data ) assert result["type"] is FlowResultType.CREATE_ENTRY @@ -99,7 +98,7 @@ async def test_discovery_shows_create_form( # Single discovered host shows form for user to finish setup. result = await hass.config_entries.flow.async_init( - heos.DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data + DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data ) assert hass.data[DOMAIN] == {"Office (127.0.0.1)": "127.0.0.1"} assert result["type"] is FlowResultType.FORM @@ -107,7 +106,7 @@ async def test_discovery_shows_create_form( # Subsequent discovered hosts append to discovered hosts and abort. result = await hass.config_entries.flow.async_init( - heos.DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data_bedroom + DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data_bedroom ) assert hass.data[DOMAIN] == { "Office (127.0.0.1)": "127.0.0.1", diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index 1362722390a..f06c5709f6d 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -2,30 +2,22 @@ import asyncio from typing import cast -from unittest.mock import Mock, patch from pyheos import ( CommandFailedError, Heos, HeosError, + HeosOptions, SignalHeosEvent, SignalType, const, ) import pytest -from homeassistant.components.heos import ( - ControllerManager, - HeosOptions, - HeosRuntimeData, - async_setup_entry, - async_unload_entry, -) from homeassistant.components.heos.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -38,18 +30,14 @@ async def test_async_setup_entry_loads_platforms( ) -> None: """Test load connects to heos, retrieves players, and loads platforms.""" config_entry.add_to_hass(hass) - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) 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 == 1 - assert controller.get_input_sources.call_count == 1 - controller.disconnect.assert_not_called() + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert config_entry.state == ConfigEntryState.LOADED + assert hass.states.get("media_player.test_player") is not None + 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() async def test_async_setup_entry_with_options_loads_platforms( @@ -75,7 +63,7 @@ async def test_async_setup_entry_with_options_loads_platforms( async def test_async_setup_entry_auth_failure_starts_reauth( hass: HomeAssistant, config_entry_options: MockConfigEntry, - controller: Mock, + controller: Heos, ) -> None: """Test load with auth failure starts reauth, loads platforms.""" config_entry_options.add_to_hass(hass) @@ -110,18 +98,12 @@ async def test_async_setup_entry_not_signed_in_loads_platforms( """Test setup does not retrieve favorites when not logged in.""" config_entry.add_to_hass(hass) controller._signed_in_username = None - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) 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 await hass.config_entries.async_setup(config_entry.entry_id) + 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 ( "The HEOS System is not logged in: Enter credentials in the integration options to access favorites and streaming services" in caplog.text @@ -134,8 +116,8 @@ async def test_async_setup_entry_connect_failure( """Connection failure raises ConfigEntryNotReady.""" config_entry.add_to_hass(hass) controller.connect.side_effect = HeosError() - with pytest.raises(ConfigEntryNotReady): - await async_setup_entry(hass, config_entry) + assert not await hass.config_entries.async_setup(config_entry.entry_id) + assert config_entry.state == ConfigEntryState.SETUP_RETRY assert controller.connect.call_count == 1 assert controller.disconnect.call_count == 1 controller.connect.reset_mock() @@ -148,27 +130,21 @@ async def test_async_setup_entry_player_failure( """Failure to retrieve players/sources raises ConfigEntryNotReady.""" config_entry.add_to_hass(hass) controller.get_players.side_effect = HeosError() - with pytest.raises(ConfigEntryNotReady): - await async_setup_entry(hass, config_entry) + assert not await hass.config_entries.async_setup(config_entry.entry_id) assert controller.connect.call_count == 1 assert controller.disconnect.call_count == 1 controller.connect.reset_mock() controller.disconnect.reset_mock() -async def test_unload_entry(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: +async def test_unload_entry( + hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos +) -> None: """Test entries are unloaded correctly.""" - controller_manager = Mock(ControllerManager) - config_entry.runtime_data = HeosRuntimeData(controller_manager, None, None, {}) - - with patch.object( - hass.config_entries, "async_forward_entry_unload", return_value=True - ) as unload: - assert await async_unload_entry(hass, config_entry) - await hass.async_block_till_done() - assert controller_manager.disconnect.call_count == 1 - assert unload.call_count == 1 - assert DOMAIN not in hass.data + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert await hass.config_entries.async_unload(config_entry.entry_id) + assert controller.disconnect.call_count == 1 async def test_update_sources_retry( diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 3dd5312d899..98f701d423a 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -18,15 +18,14 @@ from pyheos import ( const, ) import pytest +from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props -from homeassistant.components.heos import media_player from homeassistant.components.heos.const import DOMAIN, SIGNAL_HEOS_UPDATED from homeassistant.components.media_player import ( ATTR_GROUP_MEMBERS, ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE_LIST, - ATTR_MEDIA_ALBUM_NAME, - ATTR_MEDIA_ARTIST, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION, @@ -34,7 +33,6 @@ from homeassistant.components.media_player import ( ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, ATTR_MEDIA_SHUFFLE, - ATTR_MEDIA_TITLE, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, DOMAIN as MEDIA_PLAYER_DOMAIN, @@ -43,13 +41,10 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SERVICE_UNJOIN, - MediaPlayerEntityFeature, MediaType, ) from homeassistant.const import ( ATTR_ENTITY_ID, - ATTR_FRIENDLY_NAME, - ATTR_SUPPORTED_FEATURES, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, @@ -72,42 +67,20 @@ from tests.common import MockConfigEntry @pytest.mark.usefixtures("controller") async def test_state_attributes( - hass: HomeAssistant, config_entry: MockConfigEntry + hass: HomeAssistant, config_entry: MockConfigEntry, snapshot: SnapshotAssertion ) -> None: """Tests the state attributes.""" config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) state = hass.states.get("media_player.test_player") - assert state.state == STATE_IDLE - assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == 0.25 - assert not state.attributes[ATTR_MEDIA_VOLUME_MUTED] - assert state.attributes[ATTR_MEDIA_CONTENT_ID] == "1" - assert state.attributes[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC - assert ATTR_MEDIA_DURATION not in state.attributes - assert ATTR_MEDIA_POSITION not in state.attributes - assert state.attributes[ATTR_MEDIA_TITLE] == "Song" - assert state.attributes[ATTR_MEDIA_ARTIST] == "Artist" - assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "Album" - assert not state.attributes[ATTR_MEDIA_SHUFFLE] - assert state.attributes["media_album_id"] == 1 - assert state.attributes["media_queue_id"] == 1 - assert state.attributes["media_source_id"] == 1 - assert state.attributes["media_station"] == "Station Name" - assert state.attributes["media_type"] == "Station" - assert state.attributes[ATTR_FRIENDLY_NAME] == "Test Player" - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == MediaPlayerEntityFeature.PLAY - | MediaPlayerEntityFeature.PAUSE - | MediaPlayerEntityFeature.STOP - | MediaPlayerEntityFeature.NEXT_TRACK - | MediaPlayerEntityFeature.PREVIOUS_TRACK - | media_player.BASE_SUPPORTED_FEATURES - ) - assert ATTR_INPUT_SOURCE not in state.attributes - assert ( - state.attributes[ATTR_INPUT_SOURCE_LIST] - == config_entry.runtime_data.source_manager.source_list + assert state == snapshot( + exclude=props( + "entity_picture_local", + "context", + "last_changed", + "last_reported", + "last_updated", + ) )