From 1caed79895fbd8ff1cb8a094a4b6f05249aedc2a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 16 Sep 2024 10:09:44 +0200 Subject: [PATCH] Validate set_humidity in ClimateEntity (#125242) * Implementation validation for set_humidity in ClimateEntity * Fixes --- homeassistant/components/climate/__init__.py | 29 +++++++- homeassistant/components/climate/strings.json | 3 + tests/components/climate/test_init.py | 67 +++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6cdb3339a7b..7b016d9c90b 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -202,7 +202,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component.async_register_entity_service( SERVICE_SET_HUMIDITY, {vol.Required(ATTR_HUMIDITY): vol.Coerce(int)}, - "async_set_humidity", + async_service_humidity_set, [ClimateEntityFeature.TARGET_HUMIDITY], ) component.async_register_entity_service( @@ -930,6 +930,33 @@ async def async_service_aux_heat( await entity.async_turn_aux_heat_off() +async def async_service_humidity_set( + entity: ClimateEntity, service_call: ServiceCall +) -> None: + """Handle set humidity service.""" + humidity = service_call.data[ATTR_HUMIDITY] + min_humidity = entity.min_humidity + max_humidity = entity.max_humidity + _LOGGER.debug( + "Check valid humidity %d in range %d - %d", + humidity, + min_humidity, + max_humidity, + ) + if humidity < min_humidity or humidity > max_humidity: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="humidity_out_of_range", + translation_placeholders={ + "humidity": str(humidity), + "min_humidity": str(min_humidity), + "max_humidity": str(max_humidity), + }, + ) + + await entity.async_set_humidity(humidity) + + async def async_service_temperature_set( entity: ClimateEntity, service_call: ServiceCall ) -> None: diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 1af21815b9f..3ff8d325da5 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -269,6 +269,9 @@ }, "temp_out_of_range": { "message": "Provided temperature {check_temp} is not valid. Accepted range is {min_temp} to {max_temp}." + }, + "humidity_out_of_range": { + "message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}." } } } diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index b0322e9ddd8..1c9144b40f7 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -20,6 +20,7 @@ from homeassistant.components.climate import ( from homeassistant.components.climate.const import ( ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, + ATTR_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_PRESET_MODE, @@ -27,6 +28,7 @@ from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, SERVICE_SET_FAN_MODE, + SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE, @@ -1060,6 +1062,71 @@ async def test_no_issue_no_aux_property( ) not in caplog.text +async def test_humidity_validation( + hass: HomeAssistant, + register_test_integration: MockConfigEntry, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test validation for humidity.""" + + class MockClimateEntityHumidity(MockClimateEntity): + """Mock climate class with mocked aux heater.""" + + _attr_supported_features = ClimateEntityFeature.TARGET_HUMIDITY + _attr_target_humidity = 50 + _attr_min_humidity = 50 + _attr_max_humidity = 60 + + def set_humidity(self, humidity: int) -> None: + """Set new target humidity.""" + self._attr_target_humidity = humidity + + test_climate = MockClimateEntityHumidity( + name="Test", + unique_id="unique_climate_test", + ) + + setup_test_component_platform( + hass, DOMAIN, entities=[test_climate], from_config_entry=True + ) + await hass.config_entries.async_setup(register_test_integration.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("climate.test") + assert state.attributes.get(ATTR_HUMIDITY) == 50 + + with pytest.raises( + ServiceValidationError, + match="Provided humidity 1 is not valid. Accepted range is 50 to 60", + ) as exc: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HUMIDITY, + { + "entity_id": "climate.test", + ATTR_HUMIDITY: "1", + }, + blocking=True, + ) + + assert exc.value.translation_key == "humidity_out_of_range" + assert "Check valid humidity 1 in range 50 - 60" in caplog.text + + with pytest.raises( + ServiceValidationError, + match="Provided humidity 70 is not valid. Accepted range is 50 to 60", + ) as exc: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HUMIDITY, + { + "entity_id": "climate.test", + ATTR_HUMIDITY: "70", + }, + blocking=True, + ) + + async def test_temperature_validation( hass: HomeAssistant, register_test_integration: MockConfigEntry ) -> None: