From bbd7041a73572547be49ead53b183aa1e55a6d75 Mon Sep 17 00:00:00 2001 From: Alex Henry Date: Fri, 29 Jul 2022 13:20:05 +1200 Subject: [PATCH] Refactor and improve anthemav (#75852) --- homeassistant/components/anthemav/__init__.py | 14 ++++- .../components/anthemav/media_player.py | 21 ++++--- tests/components/anthemav/conftest.py | 22 +++++++ tests/components/anthemav/test_config_flow.py | 36 +++++++++-- tests/components/anthemav/test_init.py | 61 +++++++++++-------- 5 files changed, 112 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/anthemav/__init__.py b/homeassistant/components/anthemav/__init__.py index 5ad845b52d6..eb1d9b0b560 100644 --- a/homeassistant/components/anthemav/__init__.py +++ b/homeassistant/components/anthemav/__init__.py @@ -6,8 +6,8 @@ import logging import anthemav from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -25,7 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def async_anthemav_update_callback(message): """Receive notification from transport that new data exists.""" _LOGGER.debug("Received update callback from AVR: %s", message) - async_dispatcher_send(hass, f"{ANTHEMAV_UDATE_SIGNAL}_{entry.data[CONF_NAME]}") + async_dispatcher_send(hass, f"{ANTHEMAV_UDATE_SIGNAL}_{entry.entry_id}") try: avr = await anthemav.Connection.create( @@ -41,6 +41,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_setup_platforms(entry, PLATFORMS) + @callback + def close_avr(event: Event) -> None: + avr.close() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_avr) + ) + return True diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index 3c3a363a6db..bf8172083e6 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -85,17 +85,17 @@ async def async_setup_entry( ) -> None: """Set up entry.""" name = config_entry.data[CONF_NAME] - macaddress = config_entry.data[CONF_MAC] + mac_address = config_entry.data[CONF_MAC] model = config_entry.data[CONF_MODEL] avr = hass.data[DOMAIN][config_entry.entry_id] - device = AnthemAVR(avr, name, macaddress, model) + entity = AnthemAVR(avr, name, mac_address, model, config_entry.entry_id) - _LOGGER.debug("dump_devicedata: %s", device.dump_avrdata) - _LOGGER.debug("dump_conndata: %s", avr.dump_conndata) + _LOGGER.debug("Device data dump: %s", entity.dump_avrdata) + _LOGGER.debug("Connection data dump: %s", avr.dump_conndata) - async_add_entities([device]) + async_add_entities([entity]) class AnthemAVR(MediaPlayerEntity): @@ -110,14 +110,17 @@ class AnthemAVR(MediaPlayerEntity): | MediaPlayerEntityFeature.SELECT_SOURCE ) - def __init__(self, avr: Connection, name: str, macaddress: str, model: str) -> None: + def __init__( + self, avr: Connection, name: str, mac_address: str, model: str, entry_id: str + ) -> None: """Initialize entity with transport.""" super().__init__() self.avr = avr + self._entry_id = entry_id self._attr_name = name - self._attr_unique_id = macaddress + self._attr_unique_id = mac_address self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, macaddress)}, + identifiers={(DOMAIN, mac_address)}, name=name, manufacturer=MANUFACTURER, model=model, @@ -131,7 +134,7 @@ class AnthemAVR(MediaPlayerEntity): self.async_on_remove( async_dispatcher_connect( self.hass, - f"{ANTHEMAV_UDATE_SIGNAL}_{self._attr_name}", + f"{ANTHEMAV_UDATE_SIGNAL}_{self._entry_id}", self.async_write_ha_state, ) ) diff --git a/tests/components/anthemav/conftest.py b/tests/components/anthemav/conftest.py index 8fbdf3145c3..f96696fe308 100644 --- a/tests/components/anthemav/conftest.py +++ b/tests/components/anthemav/conftest.py @@ -3,6 +3,11 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest +from homeassistant.components.anthemav.const import CONF_MODEL, DOMAIN +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT + +from tests.common import MockConfigEntry + @pytest.fixture def mock_anthemav() -> AsyncMock: @@ -14,6 +19,7 @@ def mock_anthemav() -> AsyncMock: avr.close = MagicMock() avr.protocol.input_list = [] avr.protocol.audio_listening_mode_list = [] + avr.protocol.power = False return avr @@ -26,3 +32,19 @@ def mock_connection_create(mock_anthemav: AsyncMock) -> AsyncMock: return_value=mock_anthemav, ) as mock: yield mock + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "1.1.1.1", + CONF_PORT: 14999, + CONF_NAME: "Anthem AV", + CONF_MAC: "00:00:00:00:00:01", + CONF_MODEL: "MRX 520", + }, + unique_id="00:00:00:00:00:01", + ) diff --git a/tests/components/anthemav/test_config_flow.py b/tests/components/anthemav/test_config_flow.py index 1f3dec8d5e1..f8bec435dc6 100644 --- a/tests/components/anthemav/test_config_flow.py +++ b/tests/components/anthemav/test_config_flow.py @@ -2,19 +2,22 @@ from unittest.mock import AsyncMock, patch from anthemav.device_error import DeviceError +import pytest -from homeassistant import config_entries from homeassistant.components.anthemav.const import DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from tests.common import MockConfigEntry + async def test_form_with_valid_connection( hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock ) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] == FlowResultType.FORM assert result["errors"] is None @@ -47,7 +50,7 @@ async def test_form_with_valid_connection( async def test_form_device_info_error(hass: HomeAssistant) -> None: """Test we handle DeviceError from library.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) with patch( @@ -71,7 +74,7 @@ async def test_form_device_info_error(hass: HomeAssistant) -> None: async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) with patch( @@ -102,7 +105,7 @@ async def test_import_configuration( "name": "Anthem Av Import", } result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + DOMAIN, context={"source": SOURCE_IMPORT}, data=config ) assert result["type"] == FlowResultType.CREATE_ENTRY @@ -113,3 +116,26 @@ async def test_import_configuration( "mac": "00:00:00:00:00:01", "model": "MRX 520", } + + +@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT]) +async def test_device_already_configured( + hass: HomeAssistant, + mock_connection_create: AsyncMock, + mock_anthemav: AsyncMock, + mock_config_entry: MockConfigEntry, + source: str, +) -> None: + """Test we import existing configuration.""" + config = { + "host": "1.1.1.1", + "port": 14999, + } + + mock_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source}, data=config + ) + + assert result.get("type") == FlowResultType.ABORT + assert result.get("reason") == "already_configured" diff --git a/tests/components/anthemav/test_init.py b/tests/components/anthemav/test_init.py index 866925f4e46..97dc5be95b0 100644 --- a/tests/components/anthemav/test_init.py +++ b/tests/components/anthemav/test_init.py @@ -2,28 +2,19 @@ from unittest.mock import ANY, AsyncMock, patch from homeassistant import config_entries -from homeassistant.components.anthemav.const import CONF_MODEL, DOMAIN -from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT +from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry async def test_load_unload_config_entry( - hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock + hass: HomeAssistant, + mock_connection_create: AsyncMock, + mock_anthemav: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test load and unload AnthemAv component.""" - mock_config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_HOST: "1.1.1.1", - CONF_PORT: 14999, - CONF_NAME: "Anthem AV", - CONF_MAC: "aabbccddeeff", - CONF_MODEL: "MRX 520", - }, - ) - mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -42,18 +33,10 @@ async def test_load_unload_config_entry( mock_anthemav.close.assert_called_once() -async def test_config_entry_not_ready(hass: HomeAssistant) -> None: +async def test_config_entry_not_ready( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: """Test AnthemAV configuration entry not ready.""" - mock_config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_HOST: "1.1.1.1", - CONF_PORT: 14999, - CONF_NAME: "Anthem AV", - CONF_MAC: "aabbccddeeff", - CONF_MODEL: "MRX 520", - }, - ) with patch( "anthemav.Connection.create", @@ -63,3 +46,31 @@ async def test_config_entry_not_ready(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() assert mock_config_entry.state is config_entries.ConfigEntryState.SETUP_RETRY + + +async def test_anthemav_dispatcher_signal( + hass: HomeAssistant, + mock_connection_create: AsyncMock, + mock_anthemav: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test send update signal to dispatcher.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + states = hass.states.get("media_player.anthem_av") + assert states + assert states.state == STATE_OFF + + # change state of the AVR + mock_anthemav.protocol.power = True + + # get the callback function that trigger the signal to update the state + avr_update_callback = mock_connection_create.call_args[1]["update_callback"] + avr_update_callback("power") + + await hass.async_block_till_done() + + states = hass.states.get("media_player.anthem_av") + assert states.state == STATE_ON