From cd4aa8ccd6285f4cfcbded8b5d81b1a70632dd8c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 24 Oct 2024 13:32:27 +0200 Subject: [PATCH] Add config flow to Smarty (#127540) Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> --- CODEOWNERS | 1 + homeassistant/components/smarty/__init__.py | 102 ++++++++--- .../components/smarty/binary_sensor.py | 18 +- .../components/smarty/config_flow.py | 62 +++++++ homeassistant/components/smarty/const.py | 5 + homeassistant/components/smarty/fan.py | 16 +- homeassistant/components/smarty/manifest.json | 1 + homeassistant/components/smarty/sensor.py | 23 ++- homeassistant/components/smarty/strings.json | 33 ++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- requirements_test_all.txt | 3 + tests/components/smarty/__init__.py | 13 ++ tests/components/smarty/conftest.py | 46 +++++ tests/components/smarty/test_config_flow.py | 165 ++++++++++++++++++ tests/components/smarty/test_init.py | 62 +++++++ 16 files changed, 492 insertions(+), 61 deletions(-) create mode 100644 homeassistant/components/smarty/config_flow.py create mode 100644 homeassistant/components/smarty/const.py create mode 100644 homeassistant/components/smarty/strings.json create mode 100644 tests/components/smarty/__init__.py create mode 100644 tests/components/smarty/conftest.py create mode 100644 tests/components/smarty/test_config_flow.py create mode 100644 tests/components/smarty/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index a02d2036454..0c74e06a087 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1350,6 +1350,7 @@ build.json @home-assistant/supervisor /homeassistant/components/smarttub/ @mdz /tests/components/smarttub/ @mdz /homeassistant/components/smarty/ @z0mbieprocess +/tests/components/smarty/ @z0mbieprocess /homeassistant/components/smhi/ @gjohansson-ST /tests/components/smhi/ @gjohansson-ST /homeassistant/components/smlight/ @tl-sl diff --git a/homeassistant/components/smarty/__init__.py b/homeassistant/components/smarty/__init__.py index 17c4bd0a26a..57874a6db3e 100644 --- a/homeassistant/components/smarty/__init__.py +++ b/homeassistant/components/smarty/__init__.py @@ -7,17 +7,17 @@ import logging from pysmarty2 import Smarty import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_NAME, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import issue_registry as ir import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.event import track_time_interval +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType -DOMAIN = "smarty" -DATA_SMARTY = "smarty" -SMARTY_NAME = "Smarty" +from .const import DOMAIN, SIGNAL_UPDATE_SMARTY _LOGGER = logging.getLogger(__name__) @@ -26,48 +26,96 @@ CONFIG_SCHEMA = vol.Schema( DOMAIN: vol.Schema( { vol.Required(CONF_HOST): vol.All(ipaddress.ip_address, cv.string), - vol.Optional(CONF_NAME, default=SMARTY_NAME): cv.string, + vol.Optional(CONF_NAME, default="Smarty"): cv.string, } ) }, extra=vol.ALLOW_EXTRA, ) -RPM = "rpm" -SIGNAL_UPDATE_SMARTY = "smarty_update" +PLATFORMS = [Platform.BINARY_SENSOR, Platform.FAN, Platform.SENSOR] + +type SmartyConfigEntry = ConfigEntry[Smarty] -def setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool: + """Create a smarty system.""" + if config := hass_config.get(DOMAIN): + hass.async_create_task(_async_import(hass, config)) + return True + + +async def _async_import(hass: HomeAssistant, config: ConfigType) -> None: """Set up the smarty environment.""" - conf = config[DOMAIN] + if not hass.config_entries.async_entries(DOMAIN): + # Start import flow + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) + if result["type"] == FlowResultType.ABORT: + ir.async_create_issue( + hass, + DOMAIN, + f"deprecated_yaml_import_issue_{result['reason']}", + breaks_in_ha_version="2025.5.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=ir.IssueSeverity.WARNING, + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Smarty", + }, + ) + return - host = conf[CONF_HOST] - name = conf[CONF_NAME] + ir.async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2025.5.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=ir.IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Smarty", + }, + ) - _LOGGER.debug("Name: %s, host: %s", name, host) - smarty = Smarty(host=host) +async def async_setup_entry(hass: HomeAssistant, entry: SmartyConfigEntry) -> bool: + """Set up the Smarty environment from a config entry.""" - hass.data[DOMAIN] = {"api": smarty, "name": name} + def _setup_smarty() -> Smarty: + smarty = Smarty(host=entry.data[CONF_HOST]) + smarty.update() + return smarty - # Initial update - smarty.update() + smarty = await hass.async_add_executor_job(_setup_smarty) - # Load platforms - discovery.load_platform(hass, Platform.FAN, DOMAIN, {}, config) - discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) - discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) + entry.runtime_data = smarty - def poll_device_update(event_time): + async def poll_device_update(event_time) -> None: """Update Smarty device.""" _LOGGER.debug("Updating Smarty device") - if smarty.update(): + if await hass.async_add_executor_job(smarty.update): _LOGGER.debug("Update success") - dispatcher_send(hass, SIGNAL_UPDATE_SMARTY) + async_dispatcher_send(hass, SIGNAL_UPDATE_SMARTY) else: _LOGGER.debug("Update failed") - track_time_interval(hass, poll_device_update, timedelta(seconds=30)) + entry.async_on_unload( + async_track_time_interval(hass, poll_device_update, timedelta(seconds=30)) + ) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: SmartyConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/smarty/binary_sensor.py b/homeassistant/components/smarty/binary_sensor.py index b31c51244b8..0c2999ff2f3 100644 --- a/homeassistant/components/smarty/binary_sensor.py +++ b/homeassistant/components/smarty/binary_sensor.py @@ -13,27 +13,25 @@ from homeassistant.components.binary_sensor import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import DOMAIN, SIGNAL_UPDATE_SMARTY +from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, + entry: SmartyConfigEntry, async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Smarty Binary Sensor Platform.""" - smarty: Smarty = hass.data[DOMAIN]["api"] - name: str = hass.data[DOMAIN]["name"] + + smarty = entry.runtime_data sensors = [ - AlarmSensor(name, smarty), - WarningSensor(name, smarty), - BoostSensor(name, smarty), + AlarmSensor(entry.title, smarty), + WarningSensor(entry.title, smarty), + BoostSensor(entry.title, smarty), ] async_add_entities(sensors, True) diff --git a/homeassistant/components/smarty/config_flow.py b/homeassistant/components/smarty/config_flow.py new file mode 100644 index 00000000000..9a55356a990 --- /dev/null +++ b/homeassistant/components/smarty/config_flow.py @@ -0,0 +1,62 @@ +"""Config flow for Smarty integration.""" + +from typing import Any + +from pysmarty2 import Smarty +import voluptuous as vol + +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_HOST, CONF_NAME + +from .const import DOMAIN + + +class SmartyConfigFlow(ConfigFlow, domain=DOMAIN): + """Smarty config flow.""" + + def _test_connection(self, host: str) -> str | None: + """Test the connection to the Smarty API.""" + smarty = Smarty(host=host) + try: + if smarty.update(): + return None + except Exception: # noqa: BLE001 + return "unknown" + else: + return "cannot_connect" + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a flow initialized by the user.""" + errors: dict[str, str] = {} + + if user_input is not None: + self._async_abort_entries_match(user_input) + error = await self.hass.async_add_executor_job( + self._test_connection, user_input[CONF_HOST] + ) + if not error: + return self.async_create_entry( + title=user_input[CONF_HOST], data=user_input + ) + errors["base"] = error + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + errors=errors, + ) + + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: + """Handle a flow initialized by import.""" + error = await self.hass.async_add_executor_job( + self._test_connection, import_config[CONF_HOST] + ) + if not error: + return self.async_create_entry( + title=import_config[CONF_NAME], + data={CONF_HOST: import_config[CONF_HOST]}, + ) + return self.async_abort(reason=error) diff --git a/homeassistant/components/smarty/const.py b/homeassistant/components/smarty/const.py new file mode 100644 index 00000000000..b241a10afc9 --- /dev/null +++ b/homeassistant/components/smarty/const.py @@ -0,0 +1,5 @@ +"""Constants for the Smarty component.""" + +DOMAIN = "smarty" + +SIGNAL_UPDATE_SMARTY = "smarty_update" diff --git a/homeassistant/components/smarty/fan.py b/homeassistant/components/smarty/fan.py index a2d72250197..f80dd90773b 100644 --- a/homeassistant/components/smarty/fan.py +++ b/homeassistant/components/smarty/fan.py @@ -6,21 +6,18 @@ import logging import math from typing import Any -from pysmarty2 import Smarty - from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.percentage import ( percentage_to_ranged_value, ranged_value_to_percentage, ) from homeassistant.util.scaling import int_states_in_range -from . import DOMAIN, SIGNAL_UPDATE_SMARTY +from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry _LOGGER = logging.getLogger(__name__) @@ -28,17 +25,16 @@ DEFAULT_ON_PERCENTAGE = 66 SPEED_RANGE = (1, 3) # off is not included -async def async_setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, + entry: SmartyConfigEntry, async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Smarty Fan Platform.""" - smarty: Smarty = hass.data[DOMAIN]["api"] - name: str = hass.data[DOMAIN]["name"] - async_add_entities([SmartyFan(name, smarty)], True) + smarty = entry.runtime_data + + async_add_entities([SmartyFan(entry.title, smarty)], True) class SmartyFan(FanEntity): diff --git a/homeassistant/components/smarty/manifest.json b/homeassistant/components/smarty/manifest.json index b83319b6744..ca3133d8add 100644 --- a/homeassistant/components/smarty/manifest.json +++ b/homeassistant/components/smarty/manifest.json @@ -2,6 +2,7 @@ "domain": "smarty", "name": "Salda Smarty", "codeowners": ["@z0mbieprocess"], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smarty", "integration_type": "hub", "iot_class": "local_polling", diff --git a/homeassistant/components/smarty/sensor.py b/homeassistant/components/smarty/sensor.py index 3c6873611b4..70527039e20 100644 --- a/homeassistant/components/smarty/sensor.py +++ b/homeassistant/components/smarty/sensor.py @@ -12,31 +12,28 @@ from homeassistant.const import UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util -from . import DOMAIN, SIGNAL_UPDATE_SMARTY +from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry _LOGGER = logging.getLogger(__name__) -async def async_setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, + entry: SmartyConfigEntry, async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Smarty Sensor Platform.""" - smarty: Smarty = hass.data[DOMAIN]["api"] - name: str = hass.data[DOMAIN]["name"] + smarty = entry.runtime_data sensors = [ - SupplyAirTemperatureSensor(name, smarty), - ExtractAirTemperatureSensor(name, smarty), - OutdoorAirTemperatureSensor(name, smarty), - SupplyFanSpeedSensor(name, smarty), - ExtractFanSpeedSensor(name, smarty), - FilterDaysLeftSensor(name, smarty), + SupplyAirTemperatureSensor(entry.title, smarty), + ExtractAirTemperatureSensor(entry.title, smarty), + OutdoorAirTemperatureSensor(entry.title, smarty), + SupplyFanSpeedSensor(entry.title, smarty), + ExtractFanSpeedSensor(entry.title, smarty), + FilterDaysLeftSensor(entry.title, smarty), ] async_add_entities(sensors, True) diff --git a/homeassistant/components/smarty/strings.json b/homeassistant/components/smarty/strings.json new file mode 100644 index 00000000000..dedc717da30 --- /dev/null +++ b/homeassistant/components/smarty/strings.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of the Smarty device" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "issues": { + "deprecated_yaml_import_issue_unknown": { + "title": "YAML import failed with unknown error", + "description": "Configuring {integration_title} using YAML is being removed but there was an unknown error while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your {integration_title} is operating correctly and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the `{domain}` configuration from your configuration.yaml entirely, restart Home Assistant, and add the {integration_title} integration manually." + }, + "deprecated_yaml_import_issue_auth_error": { + "title": "YAML import failed due to an authentication error", + "description": "Configuring {integration_title} using YAML is being removed but there was an authentication error while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your {integration_title} is operating correctly and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the `{domain}` configuration from your configuration.yaml entirely, restart Home Assistant, and add the {integration_title} integration manually." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 8bf1abbe3bc..557f1b4796f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -540,6 +540,7 @@ FLOWS = { "smart_meter_texas", "smartthings", "smarttub", + "smarty", "smhi", "smlight", "sms", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index b282064d4d2..11f5f211b43 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5663,7 +5663,7 @@ "smarty": { "name": "Salda Smarty", "integration_type": "hub", - "config_flow": false, + "config_flow": true, "iot_class": "local_polling" }, "smhi": { diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 36cfcc7200b..883c6400467 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1804,6 +1804,9 @@ pysmartapp==0.3.5 # homeassistant.components.smartthings pysmartthings==0.7.8 +# homeassistant.components.smarty +pysmarty2==0.10.1 + # homeassistant.components.edl21 pysml==0.0.12 diff --git a/tests/components/smarty/__init__.py b/tests/components/smarty/__init__.py new file mode 100644 index 00000000000..c5ae7f2d382 --- /dev/null +++ b/tests/components/smarty/__init__.py @@ -0,0 +1,13 @@ +"""Tests for the Smarty integration.""" + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: + """Set up the component.""" + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/smarty/conftest.py b/tests/components/smarty/conftest.py new file mode 100644 index 00000000000..f05c7256115 --- /dev/null +++ b/tests/components/smarty/conftest.py @@ -0,0 +1,46 @@ +"""Smarty tests configuration.""" + +from collections.abc import Generator +from unittest.mock import patch + +import pytest + +from homeassistant.components.smarty import DOMAIN +from homeassistant.const import CONF_HOST + +from tests.common import MockConfigEntry +from tests.components.smhi.common import AsyncMock + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock]: + """Override integration setup.""" + with patch( + "homeassistant.components.smarty.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture +def mock_smarty() -> Generator[AsyncMock]: + """Mock a Smarty client.""" + with ( + patch( + "homeassistant.components.smarty.Smarty", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.smarty.config_flow.Smarty", + new=mock_client, + ), + ): + client = mock_client.return_value + client.update.return_value = True + yield client + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry(domain=DOMAIN, data={CONF_HOST: "192.168.0.2"}) diff --git a/tests/components/smarty/test_config_flow.py b/tests/components/smarty/test_config_flow.py new file mode 100644 index 00000000000..fad4f27ca1c --- /dev/null +++ b/tests/components/smarty/test_config_flow.py @@ -0,0 +1,165 @@ +"""Test the smarty config flow.""" + +from unittest.mock import AsyncMock + +from homeassistant.components.smarty.const import DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + + +async def test_full_flow( + hass: HomeAssistant, mock_smarty: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test the full flow.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "192.168.0.2" + assert result["data"] == {CONF_HOST: "192.168.0.2"} + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_cannot_connect( + hass: HomeAssistant, mock_smarty: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test we handle cannot connect error.""" + + mock_smarty.update.return_value = False + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} + + mock_smarty.update.return_value = True + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +async def test_unknown_error( + hass: HomeAssistant, mock_smarty: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test we handle unknown error.""" + + mock_smarty.update.side_effect = Exception + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "unknown"} + + mock_smarty.update.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + + +async def test_existing_entry( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test we handle existing entry.""" + mock_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.2"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_import_flow( + hass: HomeAssistant, mock_smarty: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test the import flow.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_HOST: "192.168.0.2", CONF_NAME: "Smarty"}, + ) + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "Smarty" + assert result["data"] == {CONF_HOST: "192.168.0.2"} + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_cannot_connect( + hass: HomeAssistant, mock_smarty: AsyncMock +) -> None: + """Test we handle cannot connect error.""" + + mock_smarty.update.return_value = False + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_HOST: "192.168.0.2", CONF_NAME: "Smarty"}, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "cannot_connect" + + +async def test_import_unknown_error( + hass: HomeAssistant, mock_smarty: AsyncMock +) -> None: + """Test we handle unknown error.""" + + mock_smarty.update.side_effect = Exception + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_HOST: "192.168.0.2", CONF_NAME: "Smarty"}, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "unknown" diff --git a/tests/components/smarty/test_init.py b/tests/components/smarty/test_init.py new file mode 100644 index 00000000000..8c9100cb8b6 --- /dev/null +++ b/tests/components/smarty/test_init.py @@ -0,0 +1,62 @@ +"""Tests for the Smarty component.""" + +from unittest.mock import AsyncMock + +from homeassistant.components.smarty import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.helpers import issue_registry as ir +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +async def test_import_flow( + hass: HomeAssistant, + mock_smarty: AsyncMock, + issue_registry: ir.IssueRegistry, + mock_setup_entry: AsyncMock, +) -> None: + """Test import flow.""" + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: "192.168.0.2", CONF_NAME: "smarty"}} + ) + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert (HOMEASSISTANT_DOMAIN, "deprecated_yaml_smarty") in issue_registry.issues + + +async def test_import_flow_already_exists( + hass: HomeAssistant, + mock_smarty: AsyncMock, + issue_registry: ir.IssueRegistry, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test import flow when entry already exists.""" + mock_config_entry.add_to_hass(hass) + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: "192.168.0.2", CONF_NAME: "smarty"}} + ) + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert (HOMEASSISTANT_DOMAIN, "deprecated_yaml_smarty") in issue_registry.issues + + +async def test_import_flow_error( + hass: HomeAssistant, + mock_smarty: AsyncMock, + issue_registry: ir.IssueRegistry, + mock_setup_entry: AsyncMock, +) -> None: + """Test import flow when error occurs.""" + mock_smarty.update.return_value = False + assert await async_setup_component( + hass, DOMAIN, {DOMAIN: {CONF_HOST: "192.168.0.2", CONF_NAME: "smarty"}} + ) + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert ( + DOMAIN, + "deprecated_yaml_import_issue_cannot_connect", + ) in issue_registry.issues