Refactor and improve anthemav (#75852)

This commit is contained in:
Alex Henry 2022-07-29 13:20:05 +12:00 committed by GitHub
parent a1d96175a8
commit bbd7041a73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 42 deletions

View File

@ -6,8 +6,8 @@ import logging
import anthemav import anthemav
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.dispatcher import async_dispatcher_send 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): def async_anthemav_update_callback(message):
"""Receive notification from transport that new data exists.""" """Receive notification from transport that new data exists."""
_LOGGER.debug("Received update callback from AVR: %s", message) _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: try:
avr = await anthemav.Connection.create( 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) 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 return True

View File

@ -85,17 +85,17 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up entry.""" """Set up entry."""
name = config_entry.data[CONF_NAME] 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] model = config_entry.data[CONF_MODEL]
avr = hass.data[DOMAIN][config_entry.entry_id] 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("Device data dump: %s", entity.dump_avrdata)
_LOGGER.debug("dump_conndata: %s", avr.dump_conndata) _LOGGER.debug("Connection data dump: %s", avr.dump_conndata)
async_add_entities([device]) async_add_entities([entity])
class AnthemAVR(MediaPlayerEntity): class AnthemAVR(MediaPlayerEntity):
@ -110,14 +110,17 @@ class AnthemAVR(MediaPlayerEntity):
| MediaPlayerEntityFeature.SELECT_SOURCE | 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.""" """Initialize entity with transport."""
super().__init__() super().__init__()
self.avr = avr self.avr = avr
self._entry_id = entry_id
self._attr_name = name self._attr_name = name
self._attr_unique_id = macaddress self._attr_unique_id = mac_address
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, macaddress)}, identifiers={(DOMAIN, mac_address)},
name=name, name=name,
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model=model, model=model,
@ -131,7 +134,7 @@ class AnthemAVR(MediaPlayerEntity):
self.async_on_remove( self.async_on_remove(
async_dispatcher_connect( async_dispatcher_connect(
self.hass, self.hass,
f"{ANTHEMAV_UDATE_SIGNAL}_{self._attr_name}", f"{ANTHEMAV_UDATE_SIGNAL}_{self._entry_id}",
self.async_write_ha_state, self.async_write_ha_state,
) )
) )

View File

@ -3,6 +3,11 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest 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 @pytest.fixture
def mock_anthemav() -> AsyncMock: def mock_anthemav() -> AsyncMock:
@ -14,6 +19,7 @@ def mock_anthemav() -> AsyncMock:
avr.close = MagicMock() avr.close = MagicMock()
avr.protocol.input_list = [] avr.protocol.input_list = []
avr.protocol.audio_listening_mode_list = [] avr.protocol.audio_listening_mode_list = []
avr.protocol.power = False
return avr return avr
@ -26,3 +32,19 @@ def mock_connection_create(mock_anthemav: AsyncMock) -> AsyncMock:
return_value=mock_anthemav, return_value=mock_anthemav,
) as mock: ) as mock:
yield 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",
)

View File

@ -2,19 +2,22 @@
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from anthemav.device_error import DeviceError from anthemav.device_error import DeviceError
import pytest
from homeassistant import config_entries
from homeassistant.components.anthemav.const import DOMAIN from homeassistant.components.anthemav.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_form_with_valid_connection( async def test_form_with_valid_connection(
hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock
) -> None: ) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( 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["type"] == FlowResultType.FORM
assert result["errors"] is None 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: async def test_form_device_info_error(hass: HomeAssistant) -> None:
"""Test we handle DeviceError from library.""" """Test we handle DeviceError from library."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
with patch( 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: async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
with patch( with patch(
@ -102,7 +105,7 @@ async def test_import_configuration(
"name": "Anthem Av Import", "name": "Anthem Av Import",
} }
result = await hass.config_entries.flow.async_init( 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 assert result["type"] == FlowResultType.CREATE_ENTRY
@ -113,3 +116,26 @@ async def test_import_configuration(
"mac": "00:00:00:00:00:01", "mac": "00:00:00:00:00:01",
"model": "MRX 520", "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"

View File

@ -2,28 +2,19 @@
from unittest.mock import ANY, AsyncMock, patch from unittest.mock import ANY, AsyncMock, patch
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.anthemav.const import CONF_MODEL, DOMAIN from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_load_unload_config_entry( 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: ) -> None:
"""Test load and unload AnthemAv component.""" """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) mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -42,18 +33,10 @@ async def test_load_unload_config_entry(
mock_anthemav.close.assert_called_once() 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.""" """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( with patch(
"anthemav.Connection.create", "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.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_config_entry.state is config_entries.ConfigEntryState.SETUP_RETRY 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