From be0926b7b8cb08415832d81e6559a43f062c85a7 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:21:54 +0200 Subject: [PATCH] Add config flow to enigma2 (#106348) * add config flow to enigma2 * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * fix suggested change * use parametrize for config flow tests * Restore PLATFORM_SCHEMA and add create_issue to async_setup_platform * fix docstring * remove name, refactor config flow * bump dependency * remove name, add verify_ssl, use async_create_clientsession * use translation key, change integration type to device * Bump openwebifpy to 4.2.1 * cleanup, remove CONF_NAME from entity, add async_set_unique_id * clear unneeded constants, fix tests * fix tests * move _attr_translation_key out of init * update test requirement * Address review comments * address review comments * clear strings.json * Review coments --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> --- CODEOWNERS | 1 + homeassistant/components/enigma2/__init__.py | 47 ++++++ .../components/enigma2/config_flow.py | 158 ++++++++++++++++++ homeassistant/components/enigma2/const.py | 1 + .../components/enigma2/manifest.json | 2 + .../components/enigma2/media_player.py | 80 ++++----- homeassistant/components/enigma2/strings.json | 30 ++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 4 +- requirements_test_all.txt | 3 + tests/components/enigma2/__init__.py | 1 + tests/components/enigma2/conftest.py | 90 ++++++++++ tests/components/enigma2/test_config_flow.py | 149 +++++++++++++++++ tests/components/enigma2/test_init.py | 38 +++++ 14 files changed, 565 insertions(+), 40 deletions(-) create mode 100644 homeassistant/components/enigma2/config_flow.py create mode 100644 homeassistant/components/enigma2/strings.json create mode 100644 tests/components/enigma2/__init__.py create mode 100644 tests/components/enigma2/conftest.py create mode 100644 tests/components/enigma2/test_config_flow.py create mode 100644 tests/components/enigma2/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index a4224025acc..b2de3031cf8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -389,6 +389,7 @@ build.json @home-assistant/supervisor /homeassistant/components/energyzero/ @klaasnicolaas /tests/components/energyzero/ @klaasnicolaas /homeassistant/components/enigma2/ @autinerd +/tests/components/enigma2/ @autinerd /homeassistant/components/enocean/ @bdurrer /tests/components/enocean/ @bdurrer /homeassistant/components/enphase_envoy/ @bdraco @cgarwood @dgomes @joostlek @catsmanac diff --git a/homeassistant/components/enigma2/__init__.py b/homeassistant/components/enigma2/__init__.py index 11cd4d9a804..241ca7444fb 100644 --- a/homeassistant/components/enigma2/__init__.py +++ b/homeassistant/components/enigma2/__init__.py @@ -1 +1,48 @@ """Support for Enigma2 devices.""" + +from openwebif.api import OpenWebIfDevice +from yarl import URL + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_create_clientsession + +from .const import DOMAIN + +PLATFORMS = [Platform.MEDIA_PLAYER] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Enigma2 from a config entry.""" + base_url = URL.build( + scheme="http" if not entry.data[CONF_SSL] else "https", + host=entry.data[CONF_HOST], + port=entry.data[CONF_PORT], + user=entry.data.get(CONF_USERNAME), + password=entry.data.get(CONF_PASSWORD), + ) + + session = async_create_clientsession( + hass, verify_ssl=entry.data[CONF_VERIFY_SSL], base_url=base_url + ) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = OpenWebIfDevice(session) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/enigma2/config_flow.py b/homeassistant/components/enigma2/config_flow.py new file mode 100644 index 00000000000..c144f2b7dae --- /dev/null +++ b/homeassistant/components/enigma2/config_flow.py @@ -0,0 +1,158 @@ +"""Config flow for Enigma2.""" + +from typing import Any + +from aiohttp.client_exceptions import ClientError +from openwebif.api import OpenWebIfDevice +from openwebif.error import InvalidAuthError +import voluptuous as vol +from yarl import URL + +from homeassistant.components.homeassistant import DOMAIN as HOMEASSISTANT_DOMAIN +from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) +from homeassistant.helpers import selector +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue + +from .const import ( + CONF_DEEP_STANDBY, + CONF_SOURCE_BOUQUET, + CONF_USE_CHANNEL_ICON, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, + DOMAIN, +) + +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): selector.TextSelector(), + vol.Required(CONF_PORT, default=DEFAULT_PORT): vol.All( + selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=65535, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Coerce(int), + ), + vol.Optional(CONF_USERNAME): selector.TextSelector(), + vol.Optional(CONF_PASSWORD): selector.TextSelector( + selector.TextSelectorConfig(type=selector.TextSelectorType.PASSWORD) + ), + vol.Required(CONF_SSL, default=DEFAULT_SSL): selector.BooleanSelector(), + vol.Required( + CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL + ): selector.BooleanSelector(), + } +) + + +class Enigma2ConfigFlowHandler(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Enigma2.""" + + DATA_KEYS = ( + CONF_HOST, + CONF_PORT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + CONF_VERIFY_SSL, + ) + OPTIONS_KEYS = (CONF_DEEP_STANDBY, CONF_SOURCE_BOUQUET, CONF_USE_CHANNEL_ICON) + + def __init__(self) -> None: + """Initialize the config flow.""" + super().__init__() + self.errors: dict[str, str] = {} + self._data: dict[str, Any] = {} + self._options: dict[str, Any] = {} + + async def validate_user_input(self, user_input: dict[str, Any]) -> dict[str, Any]: + """Validate user input.""" + + self.errors = {} + + self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) + + base_url = URL.build( + scheme="http" if not user_input[CONF_SSL] else "https", + host=user_input[CONF_HOST], + port=user_input[CONF_PORT], + user=user_input.get(CONF_USERNAME), + password=user_input.get(CONF_PASSWORD), + ) + + session = async_create_clientsession( + self.hass, verify_ssl=user_input[CONF_VERIFY_SSL], base_url=base_url + ) + + try: + about = await OpenWebIfDevice(session).get_about() + except InvalidAuthError: + self.errors["base"] = "invalid_auth" + except ClientError: + self.errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + self.errors["base"] = "unknown" + else: + await self.async_set_unique_id(about["info"]["ifaces"][0]["mac"]) + self._abort_if_unique_id_configured() + + return user_input + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the user step.""" + if user_input is None: + return self.async_show_form(step_id=SOURCE_USER, data_schema=CONFIG_SCHEMA) + + data = await self.validate_user_input(user_input) + if "base" in self.errors: + return self.async_show_form( + step_id=SOURCE_USER, data_schema=CONFIG_SCHEMA, errors=self.errors + ) + return self.async_create_entry( + data=data, title=data[CONF_HOST], options=self._options + ) + + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: + """Validate import.""" + if CONF_PORT not in user_input: + user_input[CONF_PORT] = DEFAULT_PORT + if CONF_SSL not in user_input: + user_input[CONF_SSL] = DEFAULT_SSL + user_input[CONF_VERIFY_SSL] = DEFAULT_VERIFY_SSL + + async_create_issue( + self.hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.11.0", + is_fixable=False, + is_persistent=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Enigma2", + }, + ) + + self._data = { + key: user_input[key] for key in user_input if key in self.DATA_KEYS + } + self._options = { + key: user_input[key] for key in user_input if key in self.OPTIONS_KEYS + } + + return await self.async_step_user(self._data) diff --git a/homeassistant/components/enigma2/const.py b/homeassistant/components/enigma2/const.py index 277efad50eb..d7508fee64e 100644 --- a/homeassistant/components/enigma2/const.py +++ b/homeassistant/components/enigma2/const.py @@ -16,3 +16,4 @@ DEFAULT_PASSWORD = "dreambox" DEFAULT_DEEP_STANDBY = False DEFAULT_SOURCE_BOUQUET = "" DEFAULT_MAC_ADDRESS = "" +DEFAULT_VERIFY_SSL = False diff --git a/homeassistant/components/enigma2/manifest.json b/homeassistant/components/enigma2/manifest.json index 0de4adc13b8..ef08314e541 100644 --- a/homeassistant/components/enigma2/manifest.json +++ b/homeassistant/components/enigma2/manifest.json @@ -2,7 +2,9 @@ "domain": "enigma2", "name": "Enigma2 (OpenWebif)", "codeowners": ["@autinerd"], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/enigma2", + "integration_type": "device", "iot_class": "local_polling", "loggers": ["openwebif"], "requirements": ["openwebifpy==4.2.4"] diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index afe8a426c72..037d82cd6c0 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -9,7 +9,6 @@ from aiohttp.client_exceptions import ClientConnectorError, ServerDisconnectedEr from openwebif.api import OpenWebIfDevice from openwebif.enums import PowerState, RemoteControlCodes, SetVolumeOption import voluptuous as vol -from yarl import URL from homeassistant.components.media_player import ( MediaPlayerEntity, @@ -17,6 +16,7 @@ from homeassistant.components.media_player import ( MediaPlayerState, MediaType, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,10 +26,9 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.aiohttp_client import async_create_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import PLATFORM_SCHEMA +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -47,6 +46,7 @@ from .const import ( DEFAULT_SSL, DEFAULT_USE_CHANNEL_ICON, DEFAULT_USERNAME, + DOMAIN, ) ATTR_MEDIA_CURRENTLY_RECORDING = "media_currently_recording" @@ -81,49 +81,44 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up of an enigma2 media player.""" - if discovery_info: - # Discovery gives us the streaming service port (8001) - # which is not useful as OpenWebif never runs on that port. - # So use the default port instead. - config[CONF_PORT] = DEFAULT_PORT - config[CONF_NAME] = discovery_info["hostname"] - config[CONF_HOST] = discovery_info["host"] - config[CONF_USERNAME] = DEFAULT_USERNAME - config[CONF_PASSWORD] = DEFAULT_PASSWORD - config[CONF_SSL] = DEFAULT_SSL - config[CONF_USE_CHANNEL_ICON] = DEFAULT_USE_CHANNEL_ICON - config[CONF_MAC_ADDRESS] = DEFAULT_MAC_ADDRESS - config[CONF_DEEP_STANDBY] = DEFAULT_DEEP_STANDBY - config[CONF_SOURCE_BOUQUET] = DEFAULT_SOURCE_BOUQUET - - base_url = URL.build( - scheme="https" if config[CONF_SSL] else "http", - host=config[CONF_HOST], - port=config.get(CONF_PORT), - user=config.get(CONF_USERNAME), - password=config.get(CONF_PASSWORD), - ) - session = async_create_clientsession(hass, verify_ssl=False, base_url=base_url) + entry_data = { + CONF_HOST: config[CONF_HOST], + CONF_PORT: config[CONF_PORT], + CONF_USERNAME: config[CONF_USERNAME], + CONF_PASSWORD: config[CONF_PASSWORD], + CONF_SSL: config[CONF_SSL], + CONF_USE_CHANNEL_ICON: config[CONF_USE_CHANNEL_ICON], + CONF_DEEP_STANDBY: config[CONF_DEEP_STANDBY], + CONF_SOURCE_BOUQUET: config[CONF_SOURCE_BOUQUET], + } - device = OpenWebIfDevice( - host=session, - turn_off_to_deep=config.get(CONF_DEEP_STANDBY, False), - source_bouquet=config.get(CONF_SOURCE_BOUQUET), + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=entry_data + ) ) - try: - about = await device.get_about() - except ClientConnectorError as err: - raise PlatformNotReady from err - async_add_entities([Enigma2Device(config[CONF_NAME], device, about)]) +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Enigma2 media player platform.""" + + device: OpenWebIfDevice = hass.data[DOMAIN][entry.entry_id] + about = await device.get_about() + device.mac_address = about["info"]["ifaces"][0]["mac"] + entity = Enigma2Device(entry, device, about) + async_add_entities([entity]) class Enigma2Device(MediaPlayerEntity): """Representation of an Enigma2 box.""" _attr_has_entity_name = True + _attr_name = None _attr_media_content_type = MediaType.TVSHOW _attr_supported_features = ( @@ -139,14 +134,23 @@ class Enigma2Device(MediaPlayerEntity): | MediaPlayerEntityFeature.SELECT_SOURCE ) - def __init__(self, name: str, device: OpenWebIfDevice, about: dict) -> None: + def __init__( + self, entry: ConfigEntry, device: OpenWebIfDevice, about: dict + ) -> None: """Initialize the Enigma2 device.""" self._device: OpenWebIfDevice = device - self._device.mac_address = about["info"]["ifaces"][0]["mac"] + self._entry = entry - self._attr_name = name self._attr_unique_id = device.mac_address + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, device.mac_address)}, + manufacturer=about["info"]["brand"], + model=about["info"]["model"], + configuration_url=device.base, + name=entry.data[CONF_HOST], + ) + async def async_turn_off(self) -> None: """Turn off media player.""" if self._device.turn_off_to_deep: diff --git a/homeassistant/components/enigma2/strings.json b/homeassistant/components/enigma2/strings.json new file mode 100644 index 00000000000..888c6d59387 --- /dev/null +++ b/homeassistant/components/enigma2/strings.json @@ -0,0 +1,30 @@ +{ + "config": { + "flow_title": "{name}", + "step": { + "user": { + "description": "Please enter the connection details of your device.", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]", + "ssl": "[%key:common::config_flow::data::ssl%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "name": "[%key:common::config_flow::data::name%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index fd87c965db5..c02d8a2987e 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -148,6 +148,7 @@ FLOWS = { "emulated_roku", "energenie_power_sockets", "energyzero", + "enigma2", "enocean", "enphase_envoy", "environment_canada", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index d10cb3fdb80..2b1e5b4fb91 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -1604,8 +1604,8 @@ }, "enigma2": { "name": "Enigma2 (OpenWebif)", - "integration_type": "hub", - "config_flow": false, + "integration_type": "device", + "config_flow": true, "iot_class": "local_polling" }, "enmax": { diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ea115d4b29d..19aae180e4f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1173,6 +1173,9 @@ openerz-api==0.3.0 # homeassistant.components.openhome openhomedevice==2.2.0 +# homeassistant.components.enigma2 +openwebifpy==4.2.4 + # homeassistant.components.opower opower==0.4.3 diff --git a/tests/components/enigma2/__init__.py b/tests/components/enigma2/__init__.py new file mode 100644 index 00000000000..15580d55b17 --- /dev/null +++ b/tests/components/enigma2/__init__.py @@ -0,0 +1 @@ +"""Tests for the Enigma2 integration.""" diff --git a/tests/components/enigma2/conftest.py b/tests/components/enigma2/conftest.py new file mode 100644 index 00000000000..9bbbda895bd --- /dev/null +++ b/tests/components/enigma2/conftest.py @@ -0,0 +1,90 @@ +"""Test the Enigma2 config flow.""" + +from homeassistant.components.enigma2.const import ( + CONF_DEEP_STANDBY, + CONF_MAC_ADDRESS, + CONF_SOURCE_BOUQUET, + CONF_USE_CHANNEL_ICON, + DEFAULT_DEEP_STANDBY, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, +) +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) + +MAC_ADDRESS = "12:34:56:78:90:ab" + +TEST_REQUIRED = { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + CONF_SSL: DEFAULT_SSL, + CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL, +} + +TEST_FULL = { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + CONF_SSL: DEFAULT_SSL, + CONF_USERNAME: "root", + CONF_PASSWORD: "password", + CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL, +} + +TEST_IMPORT_FULL = { + CONF_HOST: "1.1.1.1", + CONF_PORT: DEFAULT_PORT, + CONF_SSL: DEFAULT_SSL, + CONF_USERNAME: "root", + CONF_PASSWORD: "password", + CONF_NAME: "My Player", + CONF_DEEP_STANDBY: DEFAULT_DEEP_STANDBY, + CONF_SOURCE_BOUQUET: "Favourites", + CONF_MAC_ADDRESS: MAC_ADDRESS, + CONF_USE_CHANNEL_ICON: False, +} + +TEST_IMPORT_REQUIRED = {CONF_HOST: "1.1.1.1"} + +EXPECTED_OPTIONS = { + CONF_DEEP_STANDBY: DEFAULT_DEEP_STANDBY, + CONF_SOURCE_BOUQUET: "Favourites", + CONF_USE_CHANNEL_ICON: False, +} + + +class MockDevice: + """A mock Enigma2 device.""" + + mac_address: str | None = "12:34:56:78:90:ab" + _base = "http://1.1.1.1" + + async def _call_api(self, url: str) -> dict: + if url.endswith("/api/about"): + return { + "info": { + "ifaces": [ + { + "mac": self.mac_address, + } + ] + } + } + + def get_version(self): + """Return the version.""" + return None + + async def get_about(self) -> dict: + """Get mock about endpoint.""" + return await self._call_api("/api/about") + + async def close(self): + """Mock close.""" diff --git a/tests/components/enigma2/test_config_flow.py b/tests/components/enigma2/test_config_flow.py new file mode 100644 index 00000000000..dcd249ad943 --- /dev/null +++ b/tests/components/enigma2/test_config_flow.py @@ -0,0 +1,149 @@ +"""Test the Enigma2 config flow.""" + +from typing import Any +from unittest.mock import patch + +from aiohttp.client_exceptions import ClientError +from openwebif.error import InvalidAuthError +import pytest + +from homeassistant import config_entries +from homeassistant.components.enigma2.const import DOMAIN +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from .conftest import ( + EXPECTED_OPTIONS, + TEST_FULL, + TEST_IMPORT_FULL, + TEST_IMPORT_REQUIRED, + TEST_REQUIRED, + MockDevice, +) + + +@pytest.fixture +async def user_flow(hass: HomeAssistant) -> str: + """Return a user-initiated flow after filling in host info.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + return result["flow_id"] + + +@pytest.mark.parametrize( + ("test_config"), + [(TEST_FULL), (TEST_REQUIRED)], +) +async def test_form_user( + hass: HomeAssistant, user_flow: str, test_config: dict[str, Any] +): + """Test a successful user initiated flow.""" + with ( + patch( + "openwebif.api.OpenWebIfDevice.__new__", + return_value=MockDevice(), + ), + patch( + "homeassistant.components.enigma2.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): + result = await hass.config_entries.flow.async_configure(user_flow, test_config) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == test_config[CONF_HOST] + assert result["data"] == test_config + + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("exception", "error_type"), + [ + (InvalidAuthError, "invalid_auth"), + (ClientError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_form_user_errors( + hass: HomeAssistant, user_flow, exception: Exception, error_type: str +) -> None: + """Test we handle errors.""" + with patch( + "homeassistant.components.enigma2.config_flow.OpenWebIfDevice.__new__", + side_effect=exception, + ): + result = await hass.config_entries.flow.async_configure(user_flow, TEST_FULL) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == config_entries.SOURCE_USER + assert result["errors"] == {"base": error_type} + + +@pytest.mark.parametrize( + ("test_config", "expected_data", "expected_options"), + [ + (TEST_IMPORT_FULL, TEST_FULL, EXPECTED_OPTIONS), + (TEST_IMPORT_REQUIRED, TEST_REQUIRED, {}), + ], +) +async def test_form_import( + hass: HomeAssistant, + test_config: dict[str, Any], + expected_data: dict[str, Any], + expected_options: dict[str, Any], +) -> None: + """Test we get the form with import source.""" + with ( + patch( + "homeassistant.components.enigma2.config_flow.OpenWebIfDevice.__new__", + return_value=MockDevice(), + ), + patch( + "homeassistant.components.enigma2.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=test_config, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == test_config[CONF_HOST] + assert result["data"] == expected_data + assert result["options"] == expected_options + + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("exception", "error_type"), + [ + (InvalidAuthError, "invalid_auth"), + (ClientError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_form_import_errors( + hass: HomeAssistant, exception: Exception, error_type: str +) -> None: + """Test we handle errors on import.""" + with patch( + "homeassistant.components.enigma2.config_flow.OpenWebIfDevice.__new__", + side_effect=exception, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=TEST_IMPORT_FULL, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": error_type} diff --git a/tests/components/enigma2/test_init.py b/tests/components/enigma2/test_init.py new file mode 100644 index 00000000000..93a130eef54 --- /dev/null +++ b/tests/components/enigma2/test_init.py @@ -0,0 +1,38 @@ +"""Test the Enigma2 integration init.""" + +from unittest.mock import patch + +from homeassistant.components.enigma2.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from .conftest import TEST_REQUIRED, MockDevice + +from tests.common import MockConfigEntry + + +async def test_unload_entry(hass: HomeAssistant) -> None: + """Test successful unload of entry.""" + with ( + patch( + "homeassistant.components.enigma2.OpenWebIfDevice.__new__", + return_value=MockDevice(), + ), + patch( + "homeassistant.components.enigma2.media_player.async_setup_entry", + return_value=True, + ), + ): + entry = MockConfigEntry(domain=DOMAIN, data=TEST_REQUIRED, title="name") + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state is ConfigEntryState.LOADED + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) -- GitLab