diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index df58ccccd30..acc78d0d380 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -5,7 +5,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, ConfigEnt from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import entity_registry as er from .const import DOMAIN, PLATFORMS from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator @@ -64,12 +64,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.async_create_task(hass.config_entries.async_remove(old_config_entry_id)) # Create coordinator - coordinator = Coordinator(hass, entry.entry_id, entry.data[CONF_IP_ADDRESS]) + coordinator = Coordinator(hass, entry, entry.data[CONF_IP_ADDRESS]) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: - await coordinator.api.close() if coordinator.api_disabled: @@ -86,17 +85,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator - # Register device - device_registry = dr.async_get(hass) - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - name=entry.title, - manufacturer="HomeWizard", - sw_version=coordinator.data["device"].firmware_version, - model=coordinator.data["device"].product_type, - identifiers={(DOMAIN, coordinator.data["device"].serial)}, - ) - # Finalize await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/homewizard/button.py b/homeassistant/components/homewizard/button.py index 4bcc5016dec..6523ae705c1 100644 --- a/homeassistant/components/homewizard/button.py +++ b/homeassistant/components/homewizard/button.py @@ -7,10 +7,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .coordinator import HWEnergyDeviceUpdateCoordinator +from .entity import HomeWizardEntity +from .helpers import homewizard_exception_handler _LOGGER = logging.getLogger(__name__) @@ -26,13 +27,9 @@ async def async_setup_entry( async_add_entities([HomeWizardIdentifyButton(coordinator, entry)]) -class HomeWizardIdentifyButton( - CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], ButtonEntity -): +class HomeWizardIdentifyButton(HomeWizardEntity, ButtonEntity): """Representation of a identify button.""" - _attr_has_entity_name = True - def __init__( self, coordinator: HWEnergyDeviceUpdateCoordinator, @@ -41,17 +38,11 @@ class HomeWizardIdentifyButton( """Initialize button.""" super().__init__(coordinator) self._attr_unique_id = f"{entry.unique_id}_identify" - self._attr_device_info = { - "name": entry.title, - "manufacturer": "HomeWizard", - "sw_version": coordinator.data["device"].firmware_version, - "model": coordinator.data["device"].product_type, - "identifiers": {(DOMAIN, coordinator.data["device"].serial)}, - } self._attr_name = "Identify" self._attr_icon = "mdi:magnify" self._attr_entity_category = EntityCategory.DIAGNOSTIC + @homewizard_exception_handler async def async_press(self) -> None: """Identify the device.""" await self.coordinator.api.identify() diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index df4d99e23ef..c97a1319407 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -6,9 +6,9 @@ import logging from homewizard_energy import HomeWizardEnergy from homewizard_energy.errors import DisabledError, RequestError +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, UPDATE_INTERVAL, DeviceResponseEntry @@ -25,22 +25,15 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] def __init__( self, hass: HomeAssistant, - entry_id: str, + entry: ConfigEntry, host: str, ) -> None: """Initialize Update Coordinator.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - self.entry_id = entry_id + self.entry = entry self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass)) - @property - def device_info(self) -> DeviceInfo: - """Return device_info.""" - return DeviceInfo( - identifiers={(DOMAIN, self.data["device"].serial)}, - ) - async def _async_update_data(self) -> DeviceResponseEntry: """Fetch all device and sensor data from api.""" @@ -66,7 +59,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] # Do not reload when performing first refresh if self.data is not None: - await self.hass.config_entries.async_reload(self.entry_id) + await self.hass.config_entries.async_reload(self.entry.entry_id) raise UpdateFailed(ex) from ex diff --git a/homeassistant/components/homewizard/entity.py b/homeassistant/components/homewizard/entity.py new file mode 100644 index 00000000000..604759e1e91 --- /dev/null +++ b/homeassistant/components/homewizard/entity.py @@ -0,0 +1,25 @@ +"""Base entity for the HomeWizard integration.""" +from __future__ import annotations + +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import HWEnergyDeviceUpdateCoordinator + + +class HomeWizardEntity(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator]): + """Defines a HomeWizard entity.""" + + _attr_has_entity_name = True + + def __init__(self, coordinator: HWEnergyDeviceUpdateCoordinator) -> None: + """Initialize the HomeWizard entity.""" + super().__init__(coordinator=coordinator) + self._attr_device_info = DeviceInfo( + name=coordinator.entry.title, + manufacturer="HomeWizard", + sw_version=coordinator.data["device"].firmware_version, + model=coordinator.data["device"].product_type, + identifiers={(DOMAIN, coordinator.data["device"].serial)}, + ) diff --git a/homeassistant/components/homewizard/helpers.py b/homeassistant/components/homewizard/helpers.py new file mode 100644 index 00000000000..59aae7367ec --- /dev/null +++ b/homeassistant/components/homewizard/helpers.py @@ -0,0 +1,39 @@ +"""Helpers for HomeWizard.""" +from __future__ import annotations + +from collections.abc import Callable, Coroutine +from typing import Any, TypeVar + +from homewizard_energy.errors import DisabledError, RequestError +from typing_extensions import Concatenate, ParamSpec + +from homeassistant.exceptions import HomeAssistantError + +from .entity import HomeWizardEntity + +_HomeWizardEntityT = TypeVar("_HomeWizardEntityT", bound=HomeWizardEntity) +_P = ParamSpec("_P") + + +def homewizard_exception_handler( + func: Callable[Concatenate[_HomeWizardEntityT, _P], Coroutine[Any, Any, Any]] +) -> Callable[Concatenate[_HomeWizardEntityT, _P], Coroutine[Any, Any, None]]: + """Decorate HomeWizard Energy calls to handle HomeWizardEnergy exceptions. + + A decorator that wraps the passed in function, catches HomeWizardEnergy errors, + and reloads the integration when the API was disabled so the reauth flow is triggered. + """ + + async def handler( + self: _HomeWizardEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> None: + try: + await func(self, *args, **kwargs) + + except RequestError as ex: + raise HomeAssistantError from ex + except DisabledError as ex: + await self.hass.config_entries.async_reload(self.coordinator.entry.entry_id) + raise HomeAssistantError from ex + + return handler diff --git a/homeassistant/components/homewizard/number.py b/homeassistant/components/homewizard/number.py index 783841168ed..82218a11d5a 100644 --- a/homeassistant/components/homewizard/number.py +++ b/homeassistant/components/homewizard/number.py @@ -9,10 +9,11 @@ from homeassistant.const import PERCENTAGE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .coordinator import HWEnergyDeviceUpdateCoordinator +from .entity import HomeWizardEntity +from .helpers import homewizard_exception_handler async def async_setup_entry( @@ -31,13 +32,10 @@ async def async_setup_entry( ) -class HWEnergyNumberEntity( - CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], NumberEntity -): +class HWEnergyNumberEntity(HomeWizardEntity, NumberEntity): """Representation of status light number.""" _attr_entity_category = EntityCategory.CONFIG - _attr_has_entity_name = True def __init__( self, @@ -50,8 +48,8 @@ class HWEnergyNumberEntity( self._attr_name = "Status light brightness" self._attr_native_unit_of_measurement = PERCENTAGE self._attr_icon = "mdi:lightbulb-on" - self._attr_device_info = coordinator.device_info + @homewizard_exception_handler async def async_set_native_value(self, value: float) -> None: """Set a new value.""" await self.coordinator.api.state_set(brightness=value * (255 / 100)) diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index edd6a40a7ef..79fb2a72aef 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -15,10 +15,10 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, DeviceResponseEntry from .coordinator import HWEnergyDeviceUpdateCoordinator +from .entity import HomeWizardEntity PARALLEL_UPDATES = 1 @@ -145,11 +145,9 @@ async def async_setup_entry( async_add_entities(entities) -class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorEntity): +class HWEnergySensor(HomeWizardEntity, SensorEntity): """Representation of a HomeWizard Sensor.""" - _attr_has_entity_name = True - def __init__( self, coordinator: HWEnergyDeviceUpdateCoordinator, @@ -165,7 +163,6 @@ class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorE # Config attributes. self.data_type = description.key self._attr_unique_id = f"{entry.unique_id}_{description.key}" - self._attr_device_info = coordinator.device_info # Special case for export, not everyone has solarpanels # The chance that 'export' is non-zero when you have solar panels is nil diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index 3b255d195b1..fcc157734f4 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -8,10 +8,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .coordinator import HWEnergyDeviceUpdateCoordinator +from .entity import HomeWizardEntity +from .helpers import homewizard_exception_handler async def async_setup_entry( @@ -34,13 +35,9 @@ async def async_setup_entry( async_add_entities(entities) -class HWEnergySwitchEntity( - CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SwitchEntity -): +class HWEnergySwitchEntity(HomeWizardEntity, SwitchEntity): """Representation switchable entity.""" - _attr_has_entity_name = True - def __init__( self, coordinator: HWEnergyDeviceUpdateCoordinator, @@ -50,7 +47,6 @@ class HWEnergySwitchEntity( """Initialize the switch.""" super().__init__(coordinator) self._attr_unique_id = f"{entry.unique_id}_{key}" - self._attr_device_info = coordinator.device_info class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): @@ -64,11 +60,13 @@ class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): """Initialize the switch.""" super().__init__(coordinator, entry, "power_on") + @homewizard_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" await self.coordinator.api.state_set(power_on=True) await self.coordinator.async_refresh() + @homewizard_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" await self.coordinator.api.state_set(power_on=False) @@ -107,11 +105,13 @@ class HWEnergySwitchLockEntity(HWEnergySwitchEntity): """Initialize the switch.""" super().__init__(coordinator, entry, "switch_lock") + @homewizard_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn switch-lock on.""" await self.coordinator.api.state_set(switch_lock=True) await self.coordinator.async_refresh() + @homewizard_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn switch-lock off.""" await self.coordinator.api.state_set(switch_lock=False) @@ -146,11 +146,13 @@ class HWEnergyEnableCloudEntity(HWEnergySwitchEntity): self.hass = hass self.entry = entry + @homewizard_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn cloud connection on.""" await self.coordinator.api.system_set(cloud_enabled=True) await self.coordinator.async_refresh() + @homewizard_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn cloud connection off.""" await self.coordinator.api.system_set(cloud_enabled=False) diff --git a/tests/components/homewizard/test_button.py b/tests/components/homewizard/test_button.py index 79c6fe3c4a9..819162f04b5 100644 --- a/tests/components/homewizard/test_button.py +++ b/tests/components/homewizard/test_button.py @@ -1,8 +1,12 @@ """Test the identify button for HomeWizard.""" from unittest.mock import patch +from homewizard_energy.errors import DisabledError, RequestError +from pytest import raises + from homeassistant.components import button from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNKNOWN +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from .generator import get_mock_device @@ -60,8 +64,8 @@ async def test_identify_button_is_loaded( assert entry.unique_id == "aabbccddeeff_identify" -async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config_entry): - """Test the creation and values of the Litter-Robot button.""" +async def test_identify_press(hass, mock_config_entry_data, mock_config_entry): + """Test button press is handled correctly.""" api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02") @@ -89,3 +93,79 @@ async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config blocking=True, ) assert api.identify.call_count == 1 + + +async def test_identify_press_catches_requesterror( + hass, mock_config_entry_data, mock_config_entry +): + """Test button press is handled RequestError correctly.""" + + api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02") + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert ( + hass.states.get("button.product_name_aabbccddeeff_identify").state + == STATE_UNKNOWN + ) + + # Raise RequestError when identify is called + api.identify.side_effect = RequestError() + + assert api.identify.call_count == 0 + + with raises(HomeAssistantError): + await hass.services.async_call( + button.DOMAIN, + button.SERVICE_PRESS, + {"entity_id": "button.product_name_aabbccddeeff_identify"}, + blocking=True, + ) + assert api.identify.call_count == 1 + + +async def test_identify_press_catches_disablederror( + hass, mock_config_entry_data, mock_config_entry +): + """Test button press is handled DisabledError correctly.""" + + api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02") + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert ( + hass.states.get("button.product_name_aabbccddeeff_identify").state + == STATE_UNKNOWN + ) + + # Raise RequestError when identify is called + api.identify.side_effect = DisabledError() + + assert api.identify.call_count == 0 + + with raises(HomeAssistantError): + await hass.services.async_call( + button.DOMAIN, + button.SERVICE_PRESS, + {"entity_id": "button.product_name_aabbccddeeff_identify"}, + blocking=True, + ) + assert api.identify.call_count == 1 diff --git a/tests/components/homewizard/test_number.py b/tests/components/homewizard/test_number.py index 9538fd3cef9..54c14a38407 100644 --- a/tests/components/homewizard/test_number.py +++ b/tests/components/homewizard/test_number.py @@ -2,11 +2,14 @@ from unittest.mock import AsyncMock, patch +from homewizard_energy.errors import DisabledError, RequestError from homewizard_energy.models import State +from pytest import raises from homeassistant.components import number from homeassistant.components.number import ATTR_VALUE, SERVICE_SET_VALUE from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from .generator import get_mock_device @@ -139,3 +142,118 @@ async def test_brightness_level_set(hass, mock_config_entry_data, mock_config_en == "0" ) assert len(api.state_set.mock_calls) == 2 + + +async def test_brightness_level_set_catches_requesterror( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity raises HomeAssistantError when RequestError was raised.""" + + api = get_mock_device() + api.state = AsyncMock(return_value=State.from_dict({"brightness": 255})) + + api.state_set = AsyncMock(side_effect=RequestError()) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Set level halfway + with raises(HomeAssistantError): + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: 50, + }, + blocking=True, + ) + + +async def test_brightness_level_set_catches_disablederror( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity raises HomeAssistantError when DisabledError was raised.""" + + api = get_mock_device() + api.state = AsyncMock(return_value=State.from_dict({"brightness": 255})) + + api.state_set = AsyncMock(side_effect=DisabledError()) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Set level halfway + with raises(HomeAssistantError): + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: 50, + }, + blocking=True, + ) + + +async def test_brightness_level_set_catches_invalid_value( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity raises ValueError when value was invalid.""" + + api = get_mock_device() + api.state = AsyncMock(return_value=State.from_dict({"brightness": 255})) + + def state_set(brightness): + api.state = AsyncMock(return_value=State.from_dict({"brightness": brightness})) + + api.state_set = AsyncMock(side_effect=state_set) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + with raises(ValueError): + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: -1, + }, + blocking=True, + ) + + with raises(ValueError): + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: 101, + }, + blocking=True, + ) diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index a964d548dd3..1826dc23fec 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -2,7 +2,9 @@ from unittest.mock import AsyncMock, patch +from homewizard_energy.errors import DisabledError, RequestError from homewizard_energy.models import State, System +from pytest import raises from homeassistant.components import switch from homeassistant.components.switch import SwitchDeviceClass @@ -16,6 +18,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from .generator import get_mock_device @@ -346,3 +349,157 @@ async def test_cloud_connection_on_off(hass, mock_config_entry_data, mock_config == STATE_OFF ) assert len(api.system_set.mock_calls) == 2 + + +async def test_switch_handles_requesterror( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity raises HomeAssistantError when RequestError was raised.""" + + api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02") + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) + api.system = AsyncMock(return_value=System.from_dict({"cloud_enabled": False})) + + api.state_set = AsyncMock(side_effect=RequestError()) + api.system_set = AsyncMock(side_effect=RequestError()) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Power on toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + ) + + # Switch Lock toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff_switch_lock"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_switch_lock"}, + blocking=True, + ) + + # Disable Cloud toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + ) + + +async def test_switch_handles_disablederror( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity raises HomeAssistantError when Disabled was raised.""" + + api = get_mock_device(product_type="HWE-SKT", firmware_version="3.02") + api.state = AsyncMock( + return_value=State.from_dict({"power_on": False, "switch_lock": False}) + ) + api.system = AsyncMock(return_value=System.from_dict({"cloud_enabled": False})) + + api.state_set = AsyncMock(side_effect=DisabledError()) + api.system_set = AsyncMock(side_effect=DisabledError()) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Power on toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + ) + + # Switch Lock toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff_switch_lock"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_switch_lock"}, + blocking=True, + ) + + # Disable Cloud toggle + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + ) + + with raises(HomeAssistantError): + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {"entity_id": "switch.product_name_aabbccddeeff_cloud_connection"}, + blocking=True, + )