From 5e6ba594aab59102198de9b72ca96f1825d07a33 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 18 Jan 2023 10:03:13 -0500 Subject: [PATCH] Change Honeywell somecomfort API to AIOSomecomfort API (#86102) * Move to AIOSomecomfort * Remove unused constant * Improve test coverage to 100 * Update homeassistant/components/honeywell/__init__.py remove "todo" from code Co-authored-by: Erik Montnemery * Missing cannot_connect translation * add asyncio errors update devices per entity rework retry login Co-authored-by: Erik Montnemery --- CODEOWNERS | 4 +- .../components/honeywell/__init__.py | 125 +++++------------- homeassistant/components/honeywell/climate.py | 108 +++++++++------ .../components/honeywell/config_flow.py | 35 +++-- .../components/honeywell/manifest.json | 4 +- homeassistant/components/honeywell/sensor.py | 2 +- .../components/honeywell/strings.json | 3 +- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/honeywell/conftest.py | 18 +-- .../components/honeywell/test_config_flow.py | 42 +++--- tests/components/honeywell/test_init.py | 21 ++- tests/components/honeywell/test_sensor.py | 10 +- 13 files changed, 184 insertions(+), 200 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b7030bc2cb9..0e090732d4c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -509,8 +509,8 @@ build.json @home-assistant/supervisor /tests/components/homematic/ @pvizeli @danielperna84 /homeassistant/components/homewizard/ @DCSBL /tests/components/homewizard/ @DCSBL -/homeassistant/components/honeywell/ @rdfurman -/tests/components/honeywell/ @rdfurman +/homeassistant/components/honeywell/ @rdfurman @mkmer +/tests/components/honeywell/ @rdfurman @mkmer /homeassistant/components/http/ @home-assistant/core /tests/components/http/ @home-assistant/core /homeassistant/components/huawei_lte/ @scop @fphammerle diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index 99f682fd7a4..2ebec5c061a 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -1,14 +1,13 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" import asyncio -from datetime import timedelta -import somecomfort +import AIOSomecomfort from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.util import Throttle +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( _LOGGER, @@ -20,7 +19,6 @@ from .const import ( ) UPDATE_LOOP_SLEEP_TIME = 5 -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) PLATFORMS = [Platform.CLIMATE, Platform.SENSOR] MIGRATE_OPTIONS_KEYS = {CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE} @@ -51,18 +49,33 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b username = config_entry.data[CONF_USERNAME] password = config_entry.data[CONF_PASSWORD] - client = await hass.async_add_executor_job( - get_somecomfort_client, username, password + client = AIOSomecomfort.AIOSomeComfort( + username, password, session=async_get_clientsession(hass) ) + try: + await client.login() + await client.discover() - if client is None: - return False + except AIOSomecomfort.AuthError as ex: + raise ConfigEntryNotReady( + "Failed to initialize the Honeywell client: " + "Check your configuration (username, password), " + ) from ex + + except ( + AIOSomecomfort.ConnectionError, + AIOSomecomfort.ConnectionTimeout, + asyncio.TimeoutError, + ) as ex: + raise ConfigEntryNotReady( + "Failed to initialize the Honeywell client: " + "Connection error: maybe you have exceeded the API rate limit?" + ) from ex loc_id = config_entry.data.get(CONF_LOC_ID) dev_id = config_entry.data.get(CONF_DEV_ID) devices = {} - for location in client.locations_by_id.values(): if not loc_id or location.locationid == loc_id: for device in location.devices_by_id.values(): @@ -74,7 +87,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return False data = HoneywellData(hass, config_entry, client, username, password, devices) - await data.async_update() hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][config_entry.entry_id] = data await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) @@ -99,21 +111,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return unload_ok -def get_somecomfort_client(username: str, password: str) -> somecomfort.SomeComfort: - """Initialize the somecomfort client.""" - try: - return somecomfort.SomeComfort(username, password) - except somecomfort.AuthError: - _LOGGER.error("Failed to login to honeywell account %s", username) - return None - except somecomfort.SomeComfortError as ex: - raise ConfigEntryNotReady( - "Failed to initialize the Honeywell client: " - "Check your configuration (username, password), " - "or maybe you have exceeded the API rate limit?" - ) from ex - - class HoneywellData: """Get the latest data and update.""" @@ -121,10 +118,10 @@ class HoneywellData: self, hass: HomeAssistant, config_entry: ConfigEntry, - client: somecomfort.SomeComfort, + client: AIOSomecomfort.AIOSomeComfort, username: str, password: str, - devices: dict[str, somecomfort.Device], + devices: dict[str, AIOSomecomfort.device.Device], ) -> None: """Initialize the data object.""" self._hass = hass @@ -134,73 +131,13 @@ class HoneywellData: self._password = password self.devices = devices - async def _retry(self) -> bool: - """Recreate a new somecomfort client. + async def retry_login(self) -> bool: + """Fire of a login retry.""" - When we got an error, the best way to be sure that the next query - will succeed, is to recreate a new somecomfort client. - """ - self._client = await self._hass.async_add_executor_job( - get_somecomfort_client, self._username, self._password - ) - - if self._client is None: - return False - - refreshed_devices = [ - device - for location in self._client.locations_by_id.values() - for device in location.devices_by_id.values() - ] - - if len(refreshed_devices) == 0: - _LOGGER.error("Failed to find any devices after retry") - return False - - for updated_device in refreshed_devices: - if updated_device.deviceid in self.devices: - self.devices[updated_device.deviceid] = updated_device - else: - _LOGGER.info( - "New device with ID %s detected, reload the honeywell integration" - " if you want to access it in Home Assistant" - ) - - await self._hass.config_entries.async_reload(self._config.entry_id) - return True - - async def _refresh_devices(self): - """Refresh each enabled device.""" - for device in self.devices.values(): - await self._hass.async_add_executor_job(device.refresh) + try: + await self._client.login() + except AIOSomecomfort.SomeComfortError: await asyncio.sleep(UPDATE_LOOP_SLEEP_TIME) + return False - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def async_update(self) -> None: - """Update the state.""" - retries = 3 - while retries > 0: - try: - await self._refresh_devices() - break - except ( - somecomfort.client.APIRateLimited, - somecomfort.client.ConnectionError, - somecomfort.client.ConnectionTimeout, - OSError, - ) as exp: - retries -= 1 - if retries == 0: - _LOGGER.error( - "Ran out of retry attempts (3 attempts allocated). Error: %s", - exp, - ) - raise exp - - result = await self._retry() - - if not result: - _LOGGER.error("Retry result was empty. Error: %s", exp) - raise exp - - _LOGGER.info("SomeComfort update failed, retrying. Error: %s", exp) + return True diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index 3bc5e0e7ef8..11cf2a24ac1 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -4,7 +4,7 @@ from __future__ import annotations import datetime from typing import Any -import somecomfort +import AIOSomecomfort from homeassistant.components.climate import ( ATTR_TARGET_TEMP_HIGH, @@ -70,7 +70,7 @@ HW_FAN_MODE_TO_HA = { "follow schedule": FAN_AUTO, } -PARALLEL_UPDATES = 1 +SCAN_INTERVAL = datetime.timedelta(seconds=10) async def async_setup_entry( @@ -230,7 +230,7 @@ class HoneywellUSThermostat(ClimateEntity): cool_status = self._device.raw_ui_data.get("StatusCool", 0) return heat_status == 2 or cool_status == 2 - def _set_temperature(self, **kwargs) -> None: + async def _set_temperature(self, **kwargs) -> None: """Set new target temperature.""" if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: return @@ -246,35 +246,43 @@ class HoneywellUSThermostat(ClimateEntity): # Get next period time hour, minute = divmod(next_period * 15, 60) # Set hold time - setattr(self._device, f"hold_{mode}", datetime.time(hour, minute)) + if mode == HVACMode.COOL: + await self._device.set_hold_cool(datetime.time(hour, minute)) + elif mode == HVACMode.HEAT: + await self._device.set_hold_heat(datetime.time(hour, minute)) + # Set temperature - setattr(self._device, f"setpoint_{mode}", temperature) - except somecomfort.SomeComfortError: + if mode == HVACMode.COOL: + await self._device.set_setpoint_cool(temperature) + elif mode == HVACMode.HEAT: + await self._device.set_setpoint_heat(temperature) + + except AIOSomecomfort.SomeComfortError: _LOGGER.error("Temperature %.1f out of range", temperature) - def set_temperature(self, **kwargs: Any) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" if {HVACMode.COOL, HVACMode.HEAT} & set(self._hvac_mode_map): - self._set_temperature(**kwargs) + await self._set_temperature(**kwargs) try: if HVACMode.HEAT_COOL in self._hvac_mode_map: if temperature := kwargs.get(ATTR_TARGET_TEMP_HIGH): - self._device.setpoint_cool = temperature + await self._device.set_setpoint_cool(temperature) if temperature := kwargs.get(ATTR_TARGET_TEMP_LOW): - self._device.setpoint_heat = temperature - except somecomfort.SomeComfortError as err: + await self._device.set_setpoint_heat(temperature) + except AIOSomecomfort.SomeComfortError as err: _LOGGER.error("Invalid temperature %s: %s", temperature, err) - def set_fan_mode(self, fan_mode: str) -> None: + async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" - self._device.fan_mode = self._fan_mode_map[fan_mode] + await self._device.set_fan_mode(self._fan_mode_map[fan_mode]) - def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - self._device.system_mode = self._hvac_mode_map[hvac_mode] + await self._device.set_system_mode(self._hvac_mode_map[hvac_mode]) - def _turn_away_mode_on(self) -> None: + async def _turn_away_mode_on(self) -> None: """Turn away on. Somecomfort does have a proprietary away mode, but it doesn't really @@ -285,73 +293,87 @@ class HoneywellUSThermostat(ClimateEntity): try: # Get current mode mode = self._device.system_mode - except somecomfort.SomeComfortError: + except AIOSomecomfort.SomeComfortError: _LOGGER.error("Can not get system mode") return try: # Set permanent hold - setattr(self._device, f"hold_{mode}", True) - # Set temperature - setattr( - self._device, - f"setpoint_{mode}", - getattr(self, f"_{mode}_away_temp"), - ) - except somecomfort.SomeComfortError: + # and Set temperature + away_temp = getattr(self, f"_{mode}_away_temp") + if mode == HVACMode.COOL: + self._device.set_hold_cool(True) + self._device.set_setpoint_cool(away_temp) + elif mode == HVACMode.HEAT: + self._device.set_hold_heat(True) + self._device.set_setpoint_heat(away_temp) + + except AIOSomecomfort.SomeComfortError: _LOGGER.error( "Temperature %.1f out of range", getattr(self, f"_{mode}_away_temp") ) - def _turn_hold_mode_on(self) -> None: + async def _turn_hold_mode_on(self) -> None: """Turn permanent hold on.""" try: # Get current mode mode = self._device.system_mode - except somecomfort.SomeComfortError: + except AIOSomecomfort.SomeComfortError: _LOGGER.error("Can not get system mode") return # Check that we got a valid mode back if mode in HW_MODE_TO_HVAC_MODE: try: # Set permanent hold - setattr(self._device, f"hold_{mode}", True) - except somecomfort.SomeComfortError: + if mode == HVACMode.COOL: + await self._device.set_hold_cool(True) + elif mode == HVACMode.HEAT: + await self._device.set_hold_heat(True) + + except AIOSomecomfort.SomeComfortError: _LOGGER.error("Couldn't set permanent hold") else: _LOGGER.error("Invalid system mode returned: %s", mode) - def _turn_away_mode_off(self) -> None: + async def _turn_away_mode_off(self) -> None: """Turn away/hold off.""" self._away = False try: # Disabling all hold modes - self._device.hold_cool = False - self._device.hold_heat = False - except somecomfort.SomeComfortError: + await self._device.set_hold_cool(False) + await self._device.set_hold_heat(False) + except AIOSomecomfort.SomeComfortError: _LOGGER.error("Can not stop hold mode") - def set_preset_mode(self, preset_mode: str) -> None: + async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" if preset_mode == PRESET_AWAY: - self._turn_away_mode_on() + await self._turn_away_mode_on() elif preset_mode == PRESET_HOLD: self._away = False - self._turn_hold_mode_on() + await self._turn_hold_mode_on() else: - self._turn_away_mode_off() + await self._turn_away_mode_off() - def turn_aux_heat_on(self) -> None: + async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - self._device.system_mode = "emheat" + await self._device.system_mode("emheat") - def turn_aux_heat_off(self) -> None: + async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" if HVACMode.HEAT in self.hvac_modes: - self.set_hvac_mode(HVACMode.HEAT) + await self.async_set_hvac_mode(HVACMode.HEAT) else: - self.set_hvac_mode(HVACMode.OFF) + await self.async_set_hvac_mode(HVACMode.OFF) async def async_update(self) -> None: """Get the latest state from the service.""" - await self._data.async_update() + try: + await self._device.refresh() + except ( + AIOSomecomfort.device.APIRateLimited, + AIOSomecomfort.device.ConnectionError, + AIOSomecomfort.device.ConnectionTimeout, + OSError, + ): + await self._data.retry_login() diff --git a/homeassistant/components/honeywell/config_flow.py b/homeassistant/components/honeywell/config_flow.py index 7f7d7d7281a..71419577d76 100644 --- a/homeassistant/components/honeywell/config_flow.py +++ b/homeassistant/components/honeywell/config_flow.py @@ -1,13 +1,17 @@ """Config flow to configure the honeywell integration.""" from __future__ import annotations +import asyncio + +import AIOSomecomfort import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession -from . import get_somecomfort_client from .const import ( CONF_COOL_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE, @@ -22,20 +26,28 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input=None): + async def async_step_user(self, user_input=None) -> FlowResult: """Create config entry. Show the setup form to the user.""" errors = {} - + valid = False if user_input is not None: - valid = await self.is_valid(**user_input) + try: + valid = await self.is_valid(**user_input) + except AIOSomecomfort.AuthError: + errors["base"] = "invalid_auth" + except ( + AIOSomecomfort.ConnectionError, + AIOSomecomfort.ConnectionTimeout, + asyncio.TimeoutError, + ): + errors["base"] = "cannot_connect" + if valid: return self.async_create_entry( title=DOMAIN, data=user_input, ) - errors["base"] = "invalid_auth" - data_schema = { vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, @@ -46,11 +58,14 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def is_valid(self, **kwargs) -> bool: """Check if login credentials are valid.""" - client = await self.hass.async_add_executor_job( - get_somecomfort_client, kwargs[CONF_USERNAME], kwargs[CONF_PASSWORD] + client = AIOSomecomfort.AIOSomeComfort( + kwargs[CONF_USERNAME], + kwargs[CONF_PASSWORD], + session=async_get_clientsession(self.hass), ) - return client is not None + await client.login() + return True @staticmethod @callback @@ -68,7 +83,7 @@ class HoneywellOptionsFlowHandler(config_entries.OptionsFlow): """Initialize Honeywell options flow.""" self.config_entry = entry - async def async_step_init(self, user_input=None): + async def async_step_init(self, user_input=None) -> FlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title=DOMAIN, data=user_input) diff --git a/homeassistant/components/honeywell/manifest.json b/homeassistant/components/honeywell/manifest.json index 7ea878f074e..92a2f48a2e1 100644 --- a/homeassistant/components/honeywell/manifest.json +++ b/homeassistant/components/honeywell/manifest.json @@ -3,8 +3,8 @@ "name": "Honeywell Total Connect Comfort (US)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/honeywell", - "requirements": ["somecomfort==0.8.0"], - "codeowners": ["@rdfurman"], + "requirements": ["aiosomecomfort==0.0.2"], + "codeowners": ["@rdfurman", "@mkmer"], "iot_class": "cloud_polling", "loggers": ["somecomfort"] } diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py index ca7320d7c4c..59f00472700 100644 --- a/homeassistant/components/honeywell/sensor.py +++ b/homeassistant/components/honeywell/sensor.py @@ -5,7 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from typing import Any -from somecomfort import Device +from AIOSomecomfort.device import Device from homeassistant.components.sensor import ( SensorDeviceClass, diff --git a/homeassistant/components/honeywell/strings.json b/homeassistant/components/honeywell/strings.json index 8e085ad7e86..87f3e025917 100644 --- a/homeassistant/components/honeywell/strings.json +++ b/homeassistant/components/honeywell/strings.json @@ -10,7 +10,8 @@ } }, "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } }, "options": { diff --git a/requirements_all.txt b/requirements_all.txt index 6b4c2923aac..a36231aa96e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -278,6 +278,9 @@ aioskybell==22.7.0 # homeassistant.components.slimproto aioslimproto==2.1.1 +# homeassistant.components.honeywell +aiosomecomfort==0.0.2 + # homeassistant.components.steamist aiosteamist==0.3.2 @@ -2362,9 +2365,6 @@ solaredge==0.0.2 # homeassistant.components.solax solax==0.3.0 -# homeassistant.components.honeywell -somecomfort==0.8.0 - # homeassistant.components.somfy_mylink somfy-mylink-synergy==1.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7dfb9470e0a..bf09058933e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -256,6 +256,9 @@ aioskybell==22.7.0 # homeassistant.components.slimproto aioslimproto==2.1.1 +# homeassistant.components.honeywell +aiosomecomfort==0.0.2 + # homeassistant.components.steamist aiosteamist==0.3.2 @@ -1659,9 +1662,6 @@ solaredge==0.0.2 # homeassistant.components.solax solax==0.3.0 -# homeassistant.components.honeywell -somecomfort==0.8.0 - # homeassistant.components.somfy_mylink somfy-mylink-synergy==1.0.6 diff --git a/tests/components/honeywell/conftest.py b/tests/components/honeywell/conftest.py index dcb8edb4015..bead64c71d1 100644 --- a/tests/components/honeywell/conftest.py +++ b/tests/components/honeywell/conftest.py @@ -1,9 +1,9 @@ """Fixtures for honeywell tests.""" -from unittest.mock import create_autospec, patch +from unittest.mock import AsyncMock, create_autospec, patch +import AIOSomecomfort import pytest -import somecomfort from homeassistant.components.honeywell.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -30,7 +30,7 @@ def config_entry(config_data): @pytest.fixture def device(): """Mock a somecomfort.Device.""" - mock_device = create_autospec(somecomfort.Device, instance=True) + mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True) mock_device.deviceid = 1234567 mock_device._data = { "canControlHumidification": False, @@ -48,7 +48,7 @@ def device(): @pytest.fixture def device_with_outdoor_sensor(): """Mock a somecomfort.Device.""" - mock_device = create_autospec(somecomfort.Device, instance=True) + mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True) mock_device.deviceid = 1234567 mock_device._data = { "canControlHumidification": False, @@ -67,7 +67,7 @@ def device_with_outdoor_sensor(): @pytest.fixture def another_device(): """Mock a somecomfort.Device.""" - mock_device = create_autospec(somecomfort.Device, instance=True) + mock_device = create_autospec(AIOSomecomfort.device.Device, instance=True) mock_device.deviceid = 7654321 mock_device._data = { "canControlHumidification": False, @@ -85,7 +85,7 @@ def another_device(): @pytest.fixture def location(device): """Mock a somecomfort.Location.""" - mock_location = create_autospec(somecomfort.Location, instance=True) + mock_location = create_autospec(AIOSomecomfort.location.Location, instance=True) mock_location.locationid.return_value = "location1" mock_location.devices_by_id = {device.deviceid: device} return mock_location @@ -94,11 +94,13 @@ def location(device): @pytest.fixture(autouse=True) def client(location): """Mock a somecomfort.SomeComfort client.""" - client_mock = create_autospec(somecomfort.SomeComfort, instance=True) + client_mock = create_autospec(AIOSomecomfort.AIOSomeComfort, instance=True) client_mock.locations_by_id = {location.locationid: location} + client_mock.login = AsyncMock(return_value=True) + client_mock.discover = AsyncMock() with patch( - "homeassistant.components.honeywell.somecomfort.SomeComfort" + "homeassistant.components.honeywell.AIOSomecomfort.AIOSomeComfort" ) as sc_class_mock: sc_class_mock.return_value = client_mock yield client_mock diff --git a/tests/components/honeywell/test_config_flow.py b/tests/components/honeywell/test_config_flow.py index d877133bdcd..84fbd29325d 100644 --- a/tests/components/honeywell/test_config_flow.py +++ b/tests/components/honeywell/test_config_flow.py @@ -1,7 +1,7 @@ """Tests for honeywell config flow.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch -import somecomfort +import AIOSomecomfort from homeassistant import data_entry_flow from homeassistant.components.honeywell.const import ( @@ -33,28 +33,32 @@ async def test_show_authenticate_form(hass: HomeAssistant) -> None: assert result["step_id"] == "user" -async def test_connection_error(hass: HomeAssistant) -> None: +async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None: + """Test that an error message is shown on connection fail.""" + client.login.side_effect = AIOSomecomfort.ConnectionError + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG + ) + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_auth_error(hass: HomeAssistant, client: MagicMock) -> None: """Test that an error message is shown on login fail.""" - with patch( - "somecomfort.SomeComfort", - side_effect=somecomfort.AuthError, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG - ) - assert result["errors"] == {"base": "invalid_auth"} + client.login.side_effect = AIOSomecomfort.AuthError + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG + ) + assert result["errors"] == {"base": "invalid_auth"} async def test_create_entry(hass: HomeAssistant) -> None: """Test that the config entry is created.""" - with patch( - "somecomfort.SomeComfort", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG - ) - assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert result["data"] == FAKE_CONFIG + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG + ) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["data"] == FAKE_CONFIG @patch("homeassistant.components.honeywell.UPDATE_LOOP_SLEEP_TIME", 0) diff --git a/tests/components/honeywell/test_init.py b/tests/components/honeywell/test_init.py index 83be3a05873..4ecd2a3172d 100644 --- a/tests/components/honeywell/test_init.py +++ b/tests/components/honeywell/test_init.py @@ -2,7 +2,7 @@ from unittest.mock import create_autospec, patch -import somecomfort +import AIOSomecomfort from homeassistant.components.honeywell.const import ( CONF_COOL_AWAY_TEMPERATURE, @@ -46,7 +46,7 @@ async def test_setup_multiple_thermostats_with_same_deviceid( hass: HomeAssistant, caplog, config_entry: MockConfigEntry, device, client ) -> None: """Test Honeywell TCC API returning duplicate device IDs.""" - mock_location2 = create_autospec(somecomfort.Location, instance=True) + mock_location2 = create_autospec(AIOSomecomfort.Location, instance=True) mock_location2.locationid.return_value = "location2" mock_location2.devices_by_id = {device.deviceid: device} client.locations_by_id["location2"] = mock_location2 @@ -71,13 +71,10 @@ async def test_away_temps_migration(hass: HomeAssistant) -> None: options={}, ) - with patch( - "homeassistant.components.honeywell.somecomfort.SomeComfort", - ): - legacy_config.add_to_hass(hass) - await hass.config_entries.async_setup(legacy_config.entry_id) - await hass.async_block_till_done() - assert legacy_config.options == { - CONF_COOL_AWAY_TEMPERATURE: 1, - CONF_HEAT_AWAY_TEMPERATURE: 2, - } + legacy_config.add_to_hass(hass) + await hass.config_entries.async_setup(legacy_config.entry_id) + await hass.async_block_till_done() + assert legacy_config.options == { + CONF_COOL_AWAY_TEMPERATURE: 1, + CONF_HEAT_AWAY_TEMPERATURE: 2, + } diff --git a/tests/components/honeywell/test_sensor.py b/tests/components/honeywell/test_sensor.py index 6a5b5636745..7ed047262bf 100644 --- a/tests/components/honeywell/test_sensor.py +++ b/tests/components/honeywell/test_sensor.py @@ -1,18 +1,24 @@ """Test honeywell sensor.""" -from somecomfort import Device, Location +from AIOSomecomfort.device import Device +from AIOSomecomfort.location import Location +import pytest from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +@pytest.mark.parametrize("unit,temp", [("C", "5"), ("F", "-15")]) async def test_outdoor_sensor( hass: HomeAssistant, config_entry: MockConfigEntry, location: Location, device_with_outdoor_sensor: Device, + unit, + temp, ): """Test outdoor temperature sensor.""" + device_with_outdoor_sensor.temperature_unit = unit location.devices_by_id[ device_with_outdoor_sensor.deviceid ] = device_with_outdoor_sensor @@ -25,5 +31,5 @@ async def test_outdoor_sensor( assert temperature_state assert humidity_state - assert temperature_state.state == "5" + assert temperature_state.state == temp assert humidity_state.state == "25"