Implement state error handling in HomeWizard (#84991)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>pull/85955/head
parent
1a0bce715a
commit
209c47383d
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)},
|
||||
)
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue