From 762eb576363af584c9e160b0bc61464d91e44e2d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 6 Dec 2022 00:07:02 +0100 Subject: [PATCH] Validate state class and unit of measurement for non-numeric sensors (#83344) * Validate state class and unit of measurement for non-numeric sensors * Remove duration * Fix rest tests --- homeassistant/components/sensor/__init__.py | 35 ++++++++++------- tests/components/rest/test_sensor.py | 2 - tests/components/sensor/test_init.py | 42 +++++++++++++++------ 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index ce488c3c76b..e4bfca192ff 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -718,7 +718,6 @@ class SensorEntity(Entity): unit_of_measurement = self.unit_of_measurement value = self.native_value device_class = self.device_class - state_class = self.state_class # Received a datetime if value is not None and device_class == DEVICE_CLASS_TIMESTAMP: @@ -755,6 +754,27 @@ class SensorEntity(Entity): f"but provides state {value}:{type(value)} resulting in '{err}'" ) from err + # Sensors with device classes indicating a non-numeric value + # should not have a state class or unit of measurement + if device_class in { + SensorDeviceClass.DATE, + SensorDeviceClass.ENUM, + SensorDeviceClass.TIMESTAMP, + }: + if self.state_class: + raise ValueError( + f"Sensor {self.entity_id} has a state class and thus indicating " + "it has a numeric value; however, it has the non-numeric " + f"device class: {device_class}" + ) + + if unit_of_measurement: + raise ValueError( + f"Sensor {self.entity_id} has a unit of measurement and thus " + "indicating it has a numeric value; however, it has the " + f"non-numeric device class: {device_class}" + ) + # Enum checks if value is not None and ( device_class == SensorDeviceClass.ENUM or self.options is not None @@ -767,19 +787,6 @@ class SensorEntity(Entity): f"Sensor {self.entity_id} is providing enum options, but {reason}" ) - if state_class: - raise ValueError( - f"Sensor {self.entity_id} has an state_class and thus indicating " - "it has a numeric value; however, it has the enum device class" - ) - - if unit_of_measurement: - raise ValueError( - f"Sensor {self.entity_id} has an unit of measurement and thus " - "indicating it has a numeric value; " - "however, it has the enum device class" - ) - if (options := self.options) and value not in options: raise ValueError( f"Sensor {self.entity_id} provides state value '{value}', " diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 49ad69b1caa..50c8c6677b5 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -243,7 +243,6 @@ async def test_setup_timestamp( "method": "GET", "value_template": "{{ value_json.key }}", "device_class": SensorDeviceClass.TIMESTAMP, - "state_class": SensorStateClass.MEASUREMENT, } }, ) @@ -255,7 +254,6 @@ async def test_setup_timestamp( state = hass.states.get("sensor.rest_sensor") assert state.state == "2021-11-11T11:39:00+00:00" assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP - assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.MEASUREMENT assert "sensor.rest_sensor rendered invalid timestamp" not in caplog.text assert "sensor.rest_sensor rendered timestamp without timezone" not in caplog.text diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 3e68ba1a7fd..79fa7ef16d9 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -1018,18 +1018,27 @@ async def test_invalid_enumeration_entity_without_device_class( ) in caplog.text -async def test_invalid_enumeration_with_state_class( +@pytest.mark.parametrize( + "device_class", + { + SensorDeviceClass.DATE, + SensorDeviceClass.ENUM, + SensorDeviceClass.TIMESTAMP, + }, +) +async def test_non_numeric_device_class_with_state_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, enable_custom_integrations: None, + device_class: SensorDeviceClass, ): - """Test warning on numeric entities that provide an enum.""" + """Test error on numeric entities that provide an state class.""" platform = getattr(hass.components, "test.sensor") platform.init(empty=True) platform.ENTITIES["0"] = platform.MockSensor( name="Test", - native_value=42, - device_class=SensorDeviceClass.ENUM, + native_value=None, + device_class=device_class, state_class=SensorStateClass.MEASUREMENT, options=["option1", "option2"], ) @@ -1038,23 +1047,32 @@ async def test_invalid_enumeration_with_state_class( await hass.async_block_till_done() assert ( - "Sensor sensor.test has an state_class and thus indicating " - "it has a numeric value; however, it has the enum device class" + "Sensor sensor.test has a state class and thus indicating it has a numeric " + f"value; however, it has the non-numeric device class: {device_class}" ) in caplog.text -async def test_invalid_enumeration_with_unit_of_measurement( +@pytest.mark.parametrize( + "device_class", + { + SensorDeviceClass.DATE, + SensorDeviceClass.ENUM, + SensorDeviceClass.TIMESTAMP, + }, +) +async def test_non_numeric_device_class_with_unit_of_measurement( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, enable_custom_integrations: None, + device_class: SensorDeviceClass, ): - """Test warning on numeric entities that provide an enum.""" + """Test error on numeric entities that provide an unit of measurement.""" platform = getattr(hass.components, "test.sensor") platform.init(empty=True) platform.ENTITIES["0"] = platform.MockSensor( name="Test", - native_value=42, - device_class=SensorDeviceClass.ENUM, + native_value=None, + device_class=device_class, native_unit_of_measurement=UnitOfTemperature.CELSIUS, options=["option1", "option2"], ) @@ -1063,6 +1081,6 @@ async def test_invalid_enumeration_with_unit_of_measurement( await hass.async_block_till_done() assert ( - "Sensor sensor.test has an unit of measurement and thus indicating " - "it has a numeric value; however, it has the enum device class" + "Sensor sensor.test has a unit of measurement and thus indicating it has " + f"a numeric value; however, it has the non-numeric device class: {device_class}" ) in caplog.text