Compare commits

...

1 Commits

Author SHA1 Message Date
Erik
c633f9dd94 Improve sensor group tests 2026-01-13 16:30:56 +01:00

View File

@@ -41,7 +41,18 @@ from homeassistant.setup import async_setup_component
from tests.common import get_fixture_path
VALUES = [17, 20, 15.3]
VALUES_ERROR = [17, "string", 15.3]
STATES_ONE_ERROR = ["17", "string", "15.3"]
STATES_ONE_MISSING = ["17", None, "15.3"]
STATES_ONE_UNKNOWN = ["17", STATE_UNKNOWN, "15.3"]
STATES_ONE_UNAVAILABLE = ["17", STATE_UNAVAILABLE, "15.3"]
STATES_ALL_ERROR = ["string", "string", "string"]
STATES_ALL_MISSING = [None, None, None]
STATES_ALL_UNKNOWN = [STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN]
STATES_ALL_UNAVAILABLE = [STATE_UNAVAILABLE, STATE_UNAVAILABLE, STATE_UNAVAILABLE]
STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN = [None, STATE_UNAVAILABLE, STATE_UNKNOWN]
STATES_MIX_MISSING_UNAVAILABLE = [None, STATE_UNAVAILABLE, STATE_UNAVAILABLE]
COUNT = len(VALUES)
MIN_VALUE = min(VALUES)
MAX_VALUE = max(VALUES)
@@ -53,6 +64,18 @@ SUM_VALUE = sum(VALUES)
PRODUCT_VALUE = prod(VALUES)
def set_or_remove_state(
hass: HomeAssistant,
entity_id: str,
state: str | None,
) -> None:
"""Set or remove the state of an entity."""
if state is None:
hass.states.async_remove(entity_id)
else:
hass.states.async_set(entity_id, state)
@pytest.mark.parametrize(
("sensor_type", "result", "attributes"),
[
@@ -90,7 +113,7 @@ async def test_sensors2(
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
@@ -140,7 +163,7 @@ async def test_sensors_attributes_defined(hass: HomeAssistant) -> None:
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
@@ -185,7 +208,7 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
assert state.attributes.get("min_entity_id") is None
assert state.attributes.get("max_entity_id") is None
hass.states.async_set(entity_ids[1], VALUES[1])
hass.states.async_set(entity_ids[1], str(VALUES[1]))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_max")
@@ -210,8 +233,8 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload sensors."""
hass.states.async_set("sensor.test_1", 12345)
hass.states.async_set("sensor.test_2", 45678)
hass.states.async_set("sensor.test_1", "12345")
hass.states.async_set("sensor.test_2", "45678")
await async_setup_component(
hass,
@@ -249,8 +272,26 @@ async def test_reload(hass: HomeAssistant) -> None:
assert hass.states.get("sensor.second_test")
@pytest.mark.parametrize(
("states_list", "expected_group_state"),
[
(STATES_ONE_ERROR, "17.0"),
(STATES_ONE_MISSING, "17.0"),
(STATES_ONE_UNKNOWN, "17.0"),
(STATES_ONE_UNAVAILABLE, "17.0"),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
],
)
async def test_sensor_incorrect_state_with_ignore_non_numeric(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
states_list: list[str | None],
expected_group_state: str,
) -> None:
"""Test that non numeric values are ignored in a group."""
config = {
@@ -271,27 +312,46 @@ async def test_sensor_incorrect_state_with_ignore_non_numeric(
entity_ids = config["sensor"]["entities"]
# Check that the final sensor value ignores the non numeric input
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_ignore_non_numeric")
assert state.state == "17.0"
assert state.state == expected_group_state
assert (
"Unable to use state. Only numerical states are supported," not in caplog.text
)
# Check that the final sensor value with all numeric inputs
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_ignore_non_numeric")
assert state.state == "20.0"
@pytest.mark.parametrize(
("states_list", "expected_group_state", "error_count"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN, 1),
(STATES_ONE_MISSING, "17.0", 0),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN, 1),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN, 1),
(STATES_ALL_ERROR, STATE_UNAVAILABLE, 3),
(STATES_ALL_MISSING, STATE_UNAVAILABLE, 0),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE, 3),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE, 3),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE, 2),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE, 2),
],
)
async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
states_list: list[str | None],
expected_group_state: str,
error_count: int,
) -> None:
"""Test that non numeric values cause a group to be unknown."""
config = {
@@ -312,24 +372,44 @@ async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
entity_ids = config["sensor"]["entities"]
# Check that the final sensor value is unavailable if a non numeric input exists
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_failure")
assert state.state == "unknown"
assert "Unable to use state. Only numerical states are supported" in caplog.text
assert state.state == expected_group_state
assert (
caplog.text.count("Unable to use state. Only numerical states are supported")
== error_count
)
# Check that the final sensor value is correct with all numeric inputs
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_failure")
assert state.state == "20.0"
async def test_sensor_require_all_states(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("states_list", "expected_group_state"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN),
(STATES_ONE_MISSING, "32.3"),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
],
)
async def test_sensor_require_all_states(
hass: HomeAssistant, states_list: list[str | None], expected_group_state: str
) -> None:
"""Test the sum sensor with missing state require all."""
config = {
SENSOR_DOMAIN: {
@@ -348,13 +428,13 @@ async def test_sensor_require_all_states(hass: HomeAssistant) -> None:
entity_ids = config["sensor"]["entities"]
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_sum")
assert state.state == STATE_UNKNOWN
assert state.state == expected_group_state
async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
@@ -373,7 +453,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -382,7 +462,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -391,7 +471,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -413,7 +493,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
# is converted correctly by the group sensor
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -446,7 +526,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -455,7 +535,7 @@ async def test_sensor_with_uoms_but_no_device_class(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -464,7 +544,7 @@ async def test_sensor_with_uoms_but_no_device_class(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"unit_of_measurement": "W",
},
@@ -487,7 +567,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -508,7 +588,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -541,7 +621,7 @@ async def test_sensor_calculated_properties_not_same(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -550,7 +630,7 @@ async def test_sensor_calculated_properties_not_same(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -559,7 +639,7 @@ async def test_sensor_calculated_properties_not_same(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.CURRENT,
"state_class": SensorStateClass.MEASUREMENT,
@@ -604,7 +684,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -613,7 +693,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -622,7 +702,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -642,7 +722,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
hass.states.async_set(
entity_ids[2],
12,
"12",
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -677,7 +757,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -686,7 +766,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -695,7 +775,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -720,7 +800,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -760,7 +840,7 @@ async def test_last_sensor(hass: HomeAssistant) -> None:
entity_ids = config["sensor"]["entities"]
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_last")
assert str(float(value)) == state.state
@@ -797,7 +877,7 @@ async def test_sensors_attributes_added_when_entity_info_available(
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
@@ -843,9 +923,9 @@ async def test_sensor_state_class_no_uom_not_available(
"unit_of_measurement": PERCENTAGE,
}
hass.states.async_set(entity_ids[0], VALUES[0], input_attributes)
hass.states.async_set(entity_ids[1], VALUES[1], input_attributes)
hass.states.async_set(entity_ids[2], VALUES[2], input_attributes)
hass.states.async_set(entity_ids[0], str(VALUES[0]), input_attributes)
hass.states.async_set(entity_ids[1], str(VALUES[1]), input_attributes)
hass.states.async_set(entity_ids[2], str(VALUES[2]), input_attributes)
await hass.async_block_till_done()
assert await async_setup_component(hass, "sensor", config)
@@ -864,7 +944,7 @@ async def test_sensor_state_class_no_uom_not_available(
# sensor.test_3 drops the unit of measurement
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"state_class": SensorStateClass.MEASUREMENT,
},
@@ -914,7 +994,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
test_cases = [
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"unit_of_measurement": PERCENTAGE,
@@ -926,7 +1006,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[1],
"value": VALUES[1],
"value": str(VALUES[1]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -939,7 +1019,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[2],
"value": VALUES[2],
"value": str(VALUES[2]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.TEMPERATURE,
@@ -952,7 +1032,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[2],
"value": VALUES[2],
"value": str(VALUES[2]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -966,7 +1046,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -980,7 +1060,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
},