Fix Hydrawise sensor availability (#118669)

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Pete Sage 2024-06-05 08:18:41 -04:00 committed by Franck Nijhof
parent f1e6375406
commit 0084d6c5bd
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
4 changed files with 104 additions and 3 deletions

View File

@ -24,13 +24,17 @@ class HydrawiseBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes Hydrawise binary sensor."""
value_fn: Callable[[HydrawiseBinarySensor], bool | None]
always_available: bool = False
CONTROLLER_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = (
HydrawiseBinarySensorEntityDescription(
key="status",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
value_fn=lambda status_sensor: status_sensor.coordinator.last_update_success,
value_fn=lambda status_sensor: status_sensor.coordinator.last_update_success
and status_sensor.controller.online,
# Connectivtiy sensor is always available
always_available=True,
),
)
@ -98,3 +102,10 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
def _update_attrs(self) -> None:
"""Update state attributes."""
self._attr_is_on = self.entity_description.value_fn(self)
@property
def available(self) -> bool:
"""Set the entity availability."""
if self.entity_description.always_available:
return True
return super().available

View File

@ -70,3 +70,8 @@ class HydrawiseEntity(CoordinatorEntity[HydrawiseDataUpdateCoordinator]):
self.controller = self.coordinator.data.controllers[self.controller.id]
self._update_attrs()
super()._handle_coordinator_update()
@property
def available(self) -> bool:
"""Set the entity availability."""
return super().available and self.controller.online

View File

@ -6,10 +6,11 @@ from unittest.mock import AsyncMock, patch
from aiohttp import ClientError
from freezegun.api import FrozenDateTimeFactory
from pydrawise.schema import Controller
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.hydrawise.const import SCAN_INTERVAL
from homeassistant.const import Platform
from homeassistant.const import STATE_OFF, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -47,4 +48,23 @@ async def test_update_data_fails(
connectivity = hass.states.get("binary_sensor.home_controller_connectivity")
assert connectivity is not None
assert connectivity.state == "unavailable"
assert connectivity.state == STATE_OFF
async def test_controller_offline(
hass: HomeAssistant,
mock_added_config_entry: MockConfigEntry,
mock_pydrawise: AsyncMock,
freezer: FrozenDateTimeFactory,
controller: Controller,
) -> None:
"""Test the binary_sensor for the controller being online."""
# Make the coordinator refresh data.
controller.online = False
freezer.tick(SCAN_INTERVAL + timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done()
connectivity = hass.states.get("binary_sensor.home_controller_connectivity")
assert connectivity
assert connectivity.state == STATE_OFF

View File

@ -0,0 +1,65 @@
"""Test entity availability."""
from collections.abc import Awaitable, Callable
from datetime import timedelta
from unittest.mock import AsyncMock
from aiohttp import ClientError
from freezegun.api import FrozenDateTimeFactory
from pydrawise.schema import Controller
from homeassistant.components.hydrawise.const import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, async_fire_time_changed
_SPECIAL_ENTITIES = {"binary_sensor.home_controller_connectivity": STATE_OFF}
async def test_controller_offline(
hass: HomeAssistant,
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
entity_registry: er.EntityRegistry,
controller: Controller,
) -> None:
"""Test availability for sensors when controller is offline."""
controller.online = False
config_entry = await mock_add_config_entry()
_test_availability(hass, config_entry, entity_registry)
async def test_api_offline(
hass: HomeAssistant,
mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]],
entity_registry: er.EntityRegistry,
mock_pydrawise: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test availability of sensors when API call fails."""
config_entry = await mock_add_config_entry()
mock_pydrawise.get_user.reset_mock(return_value=True)
mock_pydrawise.get_user.side_effect = ClientError
freezer.tick(SCAN_INTERVAL + timedelta(seconds=30))
async_fire_time_changed(hass)
await hass.async_block_till_done()
_test_availability(hass, config_entry, entity_registry)
def _test_availability(
hass: HomeAssistant,
config_entry: ConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
entity_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
assert entity_entries
for entity_entry in entity_entries:
state = hass.states.get(entity_entry.entity_id)
assert state, f"State not found for {entity_entry.entity_id}"
assert state.state == _SPECIAL_ENTITIES.get(
entity_entry.entity_id, STATE_UNAVAILABLE
)