mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Mark Blebox sensor and climate entities unavailable on error state (#179128)
This commit is contained in:
@@ -59,6 +59,12 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and not self._feature.is_error
|
||||
|
||||
@property
|
||||
@override
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
|
||||
@@ -238,6 +238,12 @@ class BleBoxSensorEntity(BleBoxEntity[blebox_uniapi.sensor.BaseSensor], SensorEn
|
||||
self._attr_translation_key = f"{description.translation_key}_n"
|
||||
self._attr_translation_placeholders = {"index": str(index)}
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and not self._feature.is_error
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> StateType:
|
||||
|
||||
@@ -42,9 +42,30 @@ def setup_product_mock(category, feature_mocks, path=None):
|
||||
|
||||
def mock_only_feature(spec, set_spec: bool = True, **kwargs):
|
||||
"""Mock just the feature, without the product setup."""
|
||||
if issubclass(spec, blebox_uniapi.sensor.BaseSensor):
|
||||
# Autospec would leave is_error as a truthy Mock, making entities unavailable.
|
||||
kwargs.setdefault("is_error", False)
|
||||
return mock.create_autospec(spec, set_spec, True, **kwargs)
|
||||
|
||||
|
||||
def setup_multi_feature_product(category, features, name, model):
|
||||
"""Mock a product exposing several features of the same category."""
|
||||
product = setup_product_mock(category, features)
|
||||
type(product).name = PropertyMock(return_value=name)
|
||||
type(product).model = PropertyMock(return_value=model)
|
||||
type(product).product = PropertyMock(return_value=model)
|
||||
type(product).brand = PropertyMock(return_value="BleBox")
|
||||
type(product).firmware_version = PropertyMock(return_value="1.23")
|
||||
type(product).unique_id = PropertyMock(return_value="aabbcc112233")
|
||||
|
||||
for feature in features:
|
||||
type(feature).product = PropertyMock(return_value=product)
|
||||
type(feature).name = PropertyMock(return_value=None)
|
||||
feature.async_update = AsyncMock()
|
||||
|
||||
return product
|
||||
|
||||
|
||||
def mock_feature(category, spec, set_spec: bool = True, **kwargs):
|
||||
"""Mock a feature along with whole product setup."""
|
||||
feature_mock = mock_only_feature(spec, set_spec, **kwargs)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""BleBox climate entities tests."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, PropertyMock
|
||||
from unittest.mock import AsyncMock, Mock, PropertyMock
|
||||
|
||||
import blebox_uniapi
|
||||
import pytest
|
||||
@@ -25,6 +25,7 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_TEMPERATURE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -137,6 +138,20 @@ async def test_update(saunabox, hass: HomeAssistant, config) -> None:
|
||||
assert state.state == HVACMode.OFF
|
||||
|
||||
|
||||
async def test_update_with_safety_off_state_is_unavailable(
|
||||
hass: HomeAssistant, thermobox: tuple[Mock, str]
|
||||
) -> None:
|
||||
"""A thermostat switched off by its safety system must become unavailable."""
|
||||
|
||||
feature_mock, entity_id = thermobox
|
||||
feature_mock.is_error = True
|
||||
feature_mock.current = None
|
||||
await async_setup_entity(hass, entity_id)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_set_hvac_mode_heat(saunabox, hass: HomeAssistant) -> None:
|
||||
"""Test that setting HVAC mode to heat calls async_on."""
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Blebox sensors tests."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, PropertyMock
|
||||
from unittest.mock import AsyncMock, Mock, PropertyMock
|
||||
|
||||
import blebox_uniapi
|
||||
import pytest
|
||||
@@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
UnitOfDensity,
|
||||
UnitOfTemperature,
|
||||
@@ -25,7 +26,7 @@ from .conftest import (
|
||||
async_setup_entity,
|
||||
mock_feature,
|
||||
mock_only_feature,
|
||||
setup_product_mock,
|
||||
setup_multi_feature_product,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -133,6 +134,41 @@ async def test_update_failure(
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_update_with_error_state_is_unavailable(
|
||||
hass: HomeAssistant, tempsensor: tuple[Mock, str]
|
||||
) -> None:
|
||||
"""A sensor reporting an error state must become unavailable."""
|
||||
|
||||
feature_mock, entity_id = tempsensor
|
||||
feature_mock.is_error = True
|
||||
feature_mock.native_value = None
|
||||
await async_setup_entity(hass, entity_id)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_recovers_after_error_state_clears(
|
||||
hass: HomeAssistant, tempsensor: tuple[Mock, str], config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Entity becomes available again once the sensor reports a valid state."""
|
||||
|
||||
feature_mock, entity_id = tempsensor
|
||||
feature_mock.is_error = True
|
||||
feature_mock.native_value = None
|
||||
await async_setup_config_entry(hass, config_entry)
|
||||
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
feature_mock.is_error = False
|
||||
feature_mock.native_value = 21.5
|
||||
await config_entry.runtime_data.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "21.5"
|
||||
|
||||
|
||||
async def test_airsensor_init(
|
||||
airsensor, hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
) -> None:
|
||||
@@ -201,18 +237,7 @@ async def test_multi_sensor_multiple_have_channel_suffix(
|
||||
for i in range(4)
|
||||
]
|
||||
|
||||
product = setup_product_mock("sensors", features)
|
||||
type(product).name = PropertyMock(return_value="My smart meter")
|
||||
type(product).model = PropertyMock(return_value="smartMeter")
|
||||
type(product).product = PropertyMock(return_value="smartMeter")
|
||||
type(product).brand = PropertyMock(return_value="BleBox")
|
||||
type(product).firmware_version = PropertyMock(return_value="1.23")
|
||||
type(product).unique_id = PropertyMock(return_value="aabbcc112233")
|
||||
|
||||
for feature in features:
|
||||
type(feature).product = PropertyMock(return_value=product)
|
||||
type(feature).name = PropertyMock(return_value=None)
|
||||
feature.async_update = AsyncMock()
|
||||
setup_multi_feature_product("sensors", features, "My smart meter", "smartMeter")
|
||||
|
||||
entity_ids = [
|
||||
"sensor.my_smart_meter_voltage",
|
||||
@@ -228,6 +253,44 @@ async def test_multi_sensor_multiple_have_channel_suffix(
|
||||
assert hass.states.get(entity_ids[3]).name == "My smart meter Voltage 3"
|
||||
|
||||
|
||||
async def test_sensor_error_does_not_affect_sibling_sensors(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""A single errored sensor must not make sibling sensors unavailable."""
|
||||
ok_feature = mock_only_feature(
|
||||
blebox_uniapi.sensor.GenericSensor,
|
||||
unique_id="BleBox-multiSensor-aabbcc-humidity_0",
|
||||
full_name="multiSensor-humidity_0",
|
||||
device_class="humidity",
|
||||
unit="percentage",
|
||||
native_value=42,
|
||||
sensor_id=0,
|
||||
index=0,
|
||||
)
|
||||
error_feature = mock_only_feature(
|
||||
blebox_uniapi.sensor.Temperature,
|
||||
unique_id="BleBox-multiSensor-aabbcc-temperature_1",
|
||||
full_name="multiSensor-temperature_1",
|
||||
device_class="temperature",
|
||||
unit="celsius",
|
||||
current=None,
|
||||
native_value=None,
|
||||
is_error=True,
|
||||
sensor_id=1,
|
||||
index=1,
|
||||
)
|
||||
setup_multi_feature_product(
|
||||
"sensors", [ok_feature, error_feature], "My multi sensor", "multiSensor"
|
||||
)
|
||||
|
||||
ok_entity_id = "sensor.my_multi_sensor_humidity"
|
||||
error_entity_id = "sensor.my_multi_sensor_temperature"
|
||||
await async_setup_entities(hass, [ok_entity_id, error_entity_id])
|
||||
|
||||
assert hass.states.get(ok_entity_id).state == "42"
|
||||
assert hass.states.get(error_entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_airsensor_update(airsensor, hass: HomeAssistant) -> None:
|
||||
"""Test air quality sensor state after update."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user