diff --git a/CODEOWNERS b/CODEOWNERS index 1b6d88b54648d0a459c5596ddc3f8e00f0ecb325..ed4ab888541b0036b689af1f42e00948bd070409 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -74,6 +74,8 @@ build.json @home-assistant/supervisor /tests/components/analytics/ @home-assistant/core @ludeeus /homeassistant/components/androidtv/ @JeffLIrion @ollo69 /tests/components/androidtv/ @JeffLIrion @ollo69 +/homeassistant/components/anthemav/ @hyralex +/tests/components/anthemav/ @hyralex /homeassistant/components/apache_kafka/ @bachya /tests/components/apache_kafka/ @bachya /homeassistant/components/api/ @home-assistant/core diff --git a/homeassistant/components/anthemav/__init__.py b/homeassistant/components/anthemav/__init__.py index 56b06e865c290c7a62ad15e0f5b92f1c2c6b4d5b..5ad845b52d6d84d44acdb5d2afeb13f870c1bdc5 100644 --- a/homeassistant/components/anthemav/__init__.py +++ b/homeassistant/components/anthemav/__init__.py @@ -1 +1,59 @@ -"""The anthemav component.""" +"""The Anthem A/V Receivers integration.""" +from __future__ import annotations + +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.exceptions import ConfigEntryNotReady +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .const import ANTHEMAV_UDATE_SIGNAL, DOMAIN + +PLATFORMS = [Platform.MEDIA_PLAYER] + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Anthem A/V Receivers from a config entry.""" + + @callback + 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]}") + + try: + avr = await anthemav.Connection.create( + host=entry.data[CONF_HOST], + port=entry.data[CONF_PORT], + update_callback=async_anthemav_update_callback, + ) + + except OSError as err: + raise ConfigEntryNotReady from err + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = avr + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + avr = hass.data[DOMAIN][entry.entry_id] + + if avr is not None: + _LOGGER.debug("Close avr connection") + avr.close() + + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/anthemav/config_flow.py b/homeassistant/components/anthemav/config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..55e1fd42e07107182d9efa9bd20f10e840b4fa10 --- /dev/null +++ b/homeassistant/components/anthemav/config_flow.py @@ -0,0 +1,96 @@ +"""Config flow for Anthem A/V Receivers integration.""" +from __future__ import annotations + +import logging +from typing import Any + +import anthemav +from anthemav.connection import Connection +from anthemav.device_error import DeviceError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT +from homeassistant.data_entry_flow import FlowResult +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import format_mac + +from .const import CONF_MODEL, DEFAULT_NAME, DEFAULT_PORT, DOMAIN + +DEVICE_TIMEOUT_SECONDS = 4.0 + +_LOGGER = logging.getLogger(__name__) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } +) + + +async def connect_device(user_input: dict[str, Any]) -> Connection: + """Connect to the AVR device.""" + avr = await anthemav.Connection.create( + host=user_input[CONF_HOST], port=user_input[CONF_PORT], auto_reconnect=False + ) + await avr.reconnect() + await avr.protocol.wait_for_device_initialised(DEVICE_TIMEOUT_SECONDS) + return avr + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Anthem A/V Receivers.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + if CONF_NAME not in user_input: + user_input[CONF_NAME] = DEFAULT_NAME + + errors = {} + + avr: Connection | None = None + try: + avr = await connect_device(user_input) + except OSError: + _LOGGER.error( + "Couldn't establish connection to %s:%s", + user_input[CONF_HOST], + user_input[CONF_PORT], + ) + errors["base"] = "cannot_connect" + except DeviceError: + _LOGGER.error( + "Couldn't receive device information from %s:%s", + user_input[CONF_HOST], + user_input[CONF_PORT], + ) + errors["base"] = "cannot_receive_deviceinfo" + else: + user_input[CONF_MAC] = format_mac(avr.protocol.macaddress) + user_input[CONF_MODEL] = avr.protocol.model + await self.async_set_unique_id(user_input[CONF_MAC]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) + finally: + if avr is not None: + avr.close() + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + async def async_step_import( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Import a config entry from configuration.yaml.""" + return await self.async_step_user(user_input) diff --git a/homeassistant/components/anthemav/const.py b/homeassistant/components/anthemav/const.py new file mode 100644 index 0000000000000000000000000000000000000000..c63a477f7f9523b18ed8f9b4a96cfb2c79319408 --- /dev/null +++ b/homeassistant/components/anthemav/const.py @@ -0,0 +1,7 @@ +"""Constants for the Anthem A/V Receivers integration.""" +ANTHEMAV_UDATE_SIGNAL = "anthemav_update" +CONF_MODEL = "model" +DEFAULT_NAME = "Anthem AV" +DEFAULT_PORT = 14999 +DOMAIN = "anthemav" +MANUFACTURER = "Anthem" diff --git a/homeassistant/components/anthemav/manifest.json b/homeassistant/components/anthemav/manifest.json index c43b976416d91f2ed1661222a114609e7496db08..33fe565ad03d731c1833ec7f5ca0726fcef37318 100644 --- a/homeassistant/components/anthemav/manifest.json +++ b/homeassistant/components/anthemav/manifest.json @@ -2,8 +2,9 @@ "domain": "anthemav", "name": "Anthem A/V Receivers", "documentation": "https://www.home-assistant.io/integrations/anthemav", - "requirements": ["anthemav==1.2.0"], - "codeowners": [], + "requirements": ["anthemav==1.3.2"], + "codeowners": ["@hyralex"], + "config_flow": true, "iot_class": "local_push", "loggers": ["anthemav"] } diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index 05bfea7ef45efdecb93f29be9cf35af9cc5cb14c..93fdedef0545433b7fa79dc9fe33985637c3461d 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -2,8 +2,9 @@ from __future__ import annotations import logging +from typing import Any -import anthemav +from anthemav.connection import Connection import voluptuous as vol from homeassistant.components.media_player import ( @@ -11,33 +12,37 @@ from homeassistant.components.media_player import ( MediaPlayerEntity, MediaPlayerEntityFeature, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, + CONF_MAC, CONF_NAME, CONF_PORT, - EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "anthemav" +from .const import ( + ANTHEMAV_UDATE_SIGNAL, + CONF_MODEL, + DEFAULT_NAME, + DEFAULT_PORT, + DOMAIN, + MANUFACTURER, +) -DEFAULT_PORT = 14999 +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, } ) @@ -50,30 +55,33 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up our socket to the AVR.""" + _LOGGER.warning( + "AnthemAV configuration is deprecated and has been automatically imported. Please remove the integration from your configuration file" + ) + await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) - host = config[CONF_HOST] - port = config[CONF_PORT] - name = config.get(CONF_NAME) - device = None - - _LOGGER.info("Provisioning Anthem AVR device at %s:%d", host, port) - @callback - 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, DOMAIN) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up entry.""" + name = config_entry.data[CONF_NAME] + macaddress = config_entry.data[CONF_MAC] + model = config_entry.data[CONF_MODEL] - avr = await anthemav.Connection.create( - host=host, port=port, update_callback=async_anthemav_update_callback - ) + avr = hass.data[DOMAIN][config_entry.entry_id] - device = AnthemAVR(avr, name) + device = AnthemAVR(avr, name, macaddress, model) _LOGGER.debug("dump_devicedata: %s", device.dump_avrdata) _LOGGER.debug("dump_conndata: %s", avr.dump_conndata) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.avr.close) async_add_entities([device]) @@ -89,23 +97,34 @@ class AnthemAVR(MediaPlayerEntity): | MediaPlayerEntityFeature.SELECT_SOURCE ) - def __init__(self, avr, name): + def __init__(self, avr: Connection, name: str, macaddress: str, model: str) -> None: """Initialize entity with transport.""" super().__init__() self.avr = avr - self._attr_name = name or self._lookup("model") + self._attr_name = name + self._attr_unique_id = macaddress + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, macaddress)}, + name=name, + manufacturer=MANUFACTURER, + model=model, + ) - def _lookup(self, propname, dval=None): + def _lookup(self, propname: str, dval: Any | None = None) -> Any | None: return getattr(self.avr.protocol, propname, dval) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """When entity is added to hass.""" self.async_on_remove( - async_dispatcher_connect(self.hass, DOMAIN, self.async_write_ha_state) + async_dispatcher_connect( + self.hass, + f"{ANTHEMAV_UDATE_SIGNAL}_{self._attr_name}", + self.async_write_ha_state, + ) ) @property - def state(self): + def state(self) -> str | None: """Return state of power on/off.""" pwrstate = self._lookup("power") @@ -116,22 +135,22 @@ class AnthemAVR(MediaPlayerEntity): return None @property - def is_volume_muted(self): + def is_volume_muted(self) -> bool | None: """Return boolean reflecting mute state on device.""" return self._lookup("mute", False) @property - def volume_level(self): + def volume_level(self) -> float | None: """Return volume level from 0 to 1.""" return self._lookup("volume_as_percentage", 0.0) @property - def media_title(self): + def media_title(self) -> str | None: """Return current input name (closest we have to media title).""" return self._lookup("input_name", "No Source") @property - def app_name(self): + def app_name(self) -> str | None: """Return details about current video and audio stream.""" return ( f"{self._lookup('video_input_resolution_text', '')} " @@ -139,38 +158,38 @@ class AnthemAVR(MediaPlayerEntity): ) @property - def source(self): + def source(self) -> str | None: """Return currently selected input.""" return self._lookup("input_name", "Unknown") @property - def source_list(self): + def source_list(self) -> list[str] | None: """Return all active, configured inputs.""" return self._lookup("input_list", ["Unknown"]) - async def async_select_source(self, source): + async def async_select_source(self, source: str) -> None: """Change AVR to the designated source (by name).""" self._update_avr("input_name", source) - async def async_turn_off(self): + async def async_turn_off(self) -> None: """Turn AVR power off.""" self._update_avr("power", False) - async def async_turn_on(self): + async def async_turn_on(self) -> None: """Turn AVR power on.""" self._update_avr("power", True) - async def async_set_volume_level(self, volume): + async def async_set_volume_level(self, volume: float) -> None: """Set AVR volume (0 to 1).""" self._update_avr("volume_as_percentage", volume) - async def async_mute_volume(self, mute): + async def async_mute_volume(self, mute: bool) -> None: """Engage AVR mute.""" self._update_avr("mute", mute) - def _update_avr(self, propname, value): + def _update_avr(self, propname: str, value: Any | None) -> None: """Update a property in the AVR.""" - _LOGGER.info("Sending command to AVR: set %s to %s", propname, str(value)) + _LOGGER.debug("Sending command to AVR: set %s to %s", propname, str(value)) setattr(self.avr.protocol, propname, value) @property diff --git a/homeassistant/components/anthemav/strings.json b/homeassistant/components/anthemav/strings.json new file mode 100644 index 0000000000000000000000000000000000000000..1f1dd0ec75b49935b6e7f649c58b8ab73fcadf3a --- /dev/null +++ b/homeassistant/components/anthemav/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "cannot_receive_deviceinfo": "Failed to retreive MAC Address. Make sure the device is turned on" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/anthemav/translations/en.json b/homeassistant/components/anthemav/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..9177d5a6e7006c259463181bf1e589c72e05d559 --- /dev/null +++ b/homeassistant/components/anthemav/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "cannot_receive_deviceinfo": "Failed to retreive MAC Address. Make sure the device is turned on" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/anthemav/translations/fr.json b/homeassistant/components/anthemav/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..cf1e7c7aded40b99a2573b1dcbac2d3113f60ef2 --- /dev/null +++ b/homeassistant/components/anthemav/translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "cannot_receive_deviceinfo": "Erreur lors de la d\u00e9couverte de l'addresse MAC. V\u00e9rifiez que l'appareil est allum\u00e9." + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 3c6ad94a21fe48dd5c424ac2d64ec005c53d607d..f344e3acb5c17b29b723f5beea1647c7c5b38aa1 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -29,6 +29,7 @@ FLOWS = { "ambiclimate", "ambient_station", "androidtv", + "anthemav", "apple_tv", "arcam_fmj", "aseko_pool_live", diff --git a/requirements_all.txt b/requirements_all.txt index 98a6c6f5a95f212774141c6296dc0419ea984e3e..f992d73f894223a39a506343ef2c1b14a598d282 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -310,7 +310,7 @@ androidtv[async]==0.0.67 anel_pwrctrl-homeassistant==0.0.1.dev2 # homeassistant.components.anthemav -anthemav==1.2.0 +anthemav==1.3.2 # homeassistant.components.apcupsd apcaccess==0.0.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 64ad3f769b978656262ffd4b15a383edd3a0973a..12e59e5d4233910578d7aa54f2482d2107d69990 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -269,6 +269,9 @@ ambiclimate==0.2.1 # homeassistant.components.androidtv androidtv[async]==0.0.67 +# homeassistant.components.anthemav +anthemav==1.3.2 + # homeassistant.components.apprise apprise==0.9.9 diff --git a/tests/components/anthemav/__init__.py b/tests/components/anthemav/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..829f99b10b5d95937db616c61f716bf68d1fd5be --- /dev/null +++ b/tests/components/anthemav/__init__.py @@ -0,0 +1 @@ +"""Tests for the Anthem A/V Receivers integration.""" diff --git a/tests/components/anthemav/conftest.py b/tests/components/anthemav/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..8fbdf3145c3ba4d693f6d8c3bc3508c40bc1c1ef --- /dev/null +++ b/tests/components/anthemav/conftest.py @@ -0,0 +1,28 @@ +"""Fixtures for anthemav integration tests.""" +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +@pytest.fixture +def mock_anthemav() -> AsyncMock: + """Return the default mocked anthemav.""" + avr = AsyncMock() + avr.protocol.macaddress = "000000000001" + avr.protocol.model = "MRX 520" + avr.reconnect = AsyncMock() + avr.close = MagicMock() + avr.protocol.input_list = [] + avr.protocol.audio_listening_mode_list = [] + return avr + + +@pytest.fixture +def mock_connection_create(mock_anthemav: AsyncMock) -> AsyncMock: + """Return the default mocked connection.create.""" + + with patch( + "anthemav.Connection.create", + return_value=mock_anthemav, + ) as mock: + yield mock diff --git a/tests/components/anthemav/test_config_flow.py b/tests/components/anthemav/test_config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..98ed0f0abf043446f32bce17ff0e7c22e3b9fcc7 --- /dev/null +++ b/tests/components/anthemav/test_config_flow.py @@ -0,0 +1,115 @@ +"""Test the Anthem A/V Receivers config flow.""" +from unittest.mock import AsyncMock, patch + +from anthemav.device_error import DeviceError + +from homeassistant import config_entries +from homeassistant.components.anthemav.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM + + +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} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.anthemav.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 14999, + }, + ) + + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["data"] == { + "host": "1.1.1.1", + "port": 14999, + "name": "Anthem AV", + "mac": "00:00:00:00:00:01", + "model": "MRX 520", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +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} + ) + + with patch( + "anthemav.Connection.create", + side_effect=DeviceError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 14999, + }, + ) + + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_receive_deviceinfo"} + + +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} + ) + + with patch( + "anthemav.Connection.create", + side_effect=OSError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "port": 14999, + }, + ) + + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_FORM + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_import_configuration( + hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock +) -> None: + """Test we import existing configuration.""" + config = { + "host": "1.1.1.1", + "port": 14999, + "name": "Anthem Av Import", + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == { + "host": "1.1.1.1", + "port": 14999, + "name": "Anthem Av Import", + "mac": "00:00:00:00:00:01", + "model": "MRX 520", + } diff --git a/tests/components/anthemav/test_init.py b/tests/components/anthemav/test_init.py new file mode 100644 index 0000000000000000000000000000000000000000..866925f4e46faf98a7c2a2c34be0eb42b8c22df6 --- /dev/null +++ b/tests/components/anthemav/test_init.py @@ -0,0 +1,65 @@ +"""Test the Anthem A/V Receivers config flow.""" +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.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_load_unload_config_entry( + hass: HomeAssistant, mock_connection_create: AsyncMock, mock_anthemav: AsyncMock +) -> 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() + + # assert avr is created + mock_connection_create.assert_called_with( + host="1.1.1.1", port=14999, update_callback=ANY + ) + assert mock_config_entry.state == config_entries.ConfigEntryState.LOADED + + # unload + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + # assert unload and avr is closed + assert mock_config_entry.state == config_entries.ConfigEntryState.NOT_LOADED + mock_anthemav.close.assert_called_once() + + +async def test_config_entry_not_ready(hass: HomeAssistant) -> 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", + side_effect=OSError, + ): + mock_config_entry.add_to_hass(hass) + 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