"""The tests for the climate component.""" from __future__ import annotations from enum import Enum from typing import Any from unittest.mock import MagicMock, Mock import pytest import voluptuous as vol from homeassistant.components.climate import ( DOMAIN, SET_TEMPERATURE_SCHEMA, ClimateEntity, HVACMode, ) from homeassistant.components.climate.const import ( ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_MAX_TEMP, ATTR_MIN_TEMP, ATTR_PRESET_MODE, ATTR_SWING_HORIZONTAL_MODE, ATTR_SWING_MODE, 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_HORIZONTAL_MODE, SERVICE_SET_SWING_MODE, SERVICE_SET_TEMPERATURE, SWING_HORIZONTAL_OFF, SWING_HORIZONTAL_ON, ClimateEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.entity_platform import AddEntitiesCallback from tests.common import ( MockConfigEntry, MockEntity, MockModule, MockPlatform, async_mock_service, mock_integration, mock_platform, setup_test_component_platform, ) async def test_set_temp_schema_no_req( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test the set temperature schema with missing required data.""" domain = "climate" service = "test_set_temperature" schema = SET_TEMPERATURE_SCHEMA calls = async_mock_service(hass, domain, service, schema) data = {"hvac_mode": "off", "entity_id": ["climate.test_id"]} with pytest.raises(vol.Invalid): await hass.services.async_call(domain, service, data) await hass.async_block_till_done() assert len(calls) == 0 async def test_set_temp_schema( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test the set temperature schema with ok required data.""" domain = "climate" service = "test_set_temperature" schema = SET_TEMPERATURE_SCHEMA calls = async_mock_service(hass, domain, service, schema) data = {"temperature": 20.0, "hvac_mode": "heat", "entity_id": ["climate.test_id"]} await hass.services.async_call(domain, service, data) await hass.async_block_till_done() assert len(calls) == 1 assert calls[-1].data == data class MockClimateEntity(MockEntity, ClimateEntity): """Mock Climate device to use in tests.""" _attr_supported_features = ( ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.SWING_MODE | ClimateEntityFeature.SWING_HORIZONTAL_MODE ) _attr_preset_mode = "home" _attr_preset_modes = ["home", "away"] _attr_fan_mode = "auto" _attr_fan_modes = ["auto", "off"] _attr_swing_mode = "auto" _attr_swing_modes = ["auto", "off"] _attr_swing_horizontal_mode = "on" _attr_swing_horizontal_modes = [SWING_HORIZONTAL_ON, SWING_HORIZONTAL_OFF] _attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_target_temperature = 20 _attr_target_temperature_high = 25 _attr_target_temperature_low = 15 @property def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVACMode.*. """ return HVACMode.HEAT @property def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. """ return [HVACMode.OFF, HVACMode.HEAT] def set_preset_mode(self, preset_mode: str) -> None: """Set preset mode.""" self._attr_preset_mode = preset_mode def set_fan_mode(self, fan_mode: str) -> None: """Set fan mode.""" self._attr_fan_mode = fan_mode def set_swing_mode(self, swing_mode: str) -> None: """Set swing mode.""" self._attr_swing_mode = swing_mode def set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None: """Set horizontal swing mode.""" self._attr_swing_horizontal_mode = swing_horizontal_mode def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._attr_hvac_mode = hvac_mode def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" if ATTR_TEMPERATURE in kwargs: self._attr_target_temperature = kwargs[ATTR_TEMPERATURE] if ATTR_TARGET_TEMP_HIGH in kwargs: self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH] self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW] class MockClimateEntityTestMethods(MockClimateEntity): """Mock Climate device.""" def turn_on(self) -> None: """Turn on.""" def turn_off(self) -> None: """Turn off.""" async def test_sync_turn_on(hass: HomeAssistant) -> None: """Test if async turn_on calls sync turn_on.""" climate = MockClimateEntityTestMethods() climate.hass = hass climate.turn_on = MagicMock() await climate.async_turn_on() assert climate.turn_on.called async def test_sync_turn_off(hass: HomeAssistant) -> None: """Test if async turn_off calls sync turn_off.""" climate = MockClimateEntityTestMethods() climate.hass = hass climate.turn_off = MagicMock() await climate.async_turn_off() assert climate.turn_off.called def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, str]]: return [ (enum_field, constant_prefix) for enum_field in enum if enum_field not in [ ClimateEntityFeature.TURN_ON, ClimateEntityFeature.TURN_OFF, ClimateEntityFeature.SWING_HORIZONTAL_MODE, ] ] async def test_temperature_features_is_valid( hass: HomeAssistant, register_test_integration: MockConfigEntry, caplog: pytest.LogCaptureFixture, ) -> None: """Test correct features for setting temperature.""" class MockClimateTempEntity(MockClimateEntity): @property def supported_features(self) -> int: """Return supported features.""" return ClimateEntityFeature.TARGET_TEMPERATURE_RANGE class MockClimateTempRangeEntity(MockClimateEntity): @property def supported_features(self) -> int: """Return supported features.""" return ClimateEntityFeature.TARGET_TEMPERATURE climate_temp_entity = MockClimateTempEntity( name="test", entity_id="climate.test_temp" ) climate_temp_range_entity = MockClimateTempRangeEntity( name="test", entity_id="climate.test_range" ) setup_test_component_platform( hass, DOMAIN, entities=[climate_temp_entity, climate_temp_range_entity], from_config_entry=True, ) await hass.config_entries.async_setup(register_test_integration.entry_id) await hass.async_block_till_done() with pytest.raises( ServiceValidationError, match="Set temperature action was used with the target temperature parameter but the entity does not support it", ): await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test_temp", "temperature": 20, }, blocking=True, ) with pytest.raises( ServiceValidationError, match="Set temperature action was used with the target temperature low/high parameter but the entity does not support it", ): await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test_range", "target_temp_low": 20, "target_temp_high": 25, }, blocking=True, ) async def test_mode_validation( hass: HomeAssistant, register_test_integration: MockConfigEntry, caplog: pytest.LogCaptureFixture, ) -> None: """Test mode validation for hvac_mode, fan, swing and preset.""" climate_entity = MockClimateEntity(name="test", entity_id="climate.test") setup_test_component_platform( hass, DOMAIN, entities=[climate_entity], 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.state == "heat" assert state.attributes.get(ATTR_PRESET_MODE) == "home" assert state.attributes.get(ATTR_FAN_MODE) == "auto" assert state.attributes.get(ATTR_SWING_MODE) == "auto" assert state.attributes.get(ATTR_SWING_HORIZONTAL_MODE) == "on" await hass.services.async_call( DOMAIN, SERVICE_SET_PRESET_MODE, { "entity_id": "climate.test", "preset_mode": "away", }, blocking=True, ) await hass.services.async_call( DOMAIN, SERVICE_SET_SWING_MODE, { "entity_id": "climate.test", "swing_mode": "off", }, blocking=True, ) await hass.services.async_call( DOMAIN, SERVICE_SET_SWING_HORIZONTAL_MODE, { "entity_id": "climate.test", "swing_horizontal_mode": "off", }, blocking=True, ) await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_MODE, { "entity_id": "climate.test", "fan_mode": "off", }, blocking=True, ) state = hass.states.get("climate.test") assert state.attributes.get(ATTR_PRESET_MODE) == "away" assert state.attributes.get(ATTR_FAN_MODE) == "off" assert state.attributes.get(ATTR_SWING_MODE) == "off" assert state.attributes.get(ATTR_SWING_HORIZONTAL_MODE) == "off" await hass.services.async_call( DOMAIN, SERVICE_SET_HVAC_MODE, { "entity_id": "climate.test", "hvac_mode": "auto", }, blocking=True, ) assert ( "MockClimateEntity sets the hvac_mode auto which is not valid " "for this entity with modes: off, heat. This will stop working " "in 2025.4 and raise an error instead. " "Please" in caplog.text ) with pytest.raises( ServiceValidationError, match="Preset mode invalid is not valid. Valid preset modes are: home, away", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_PRESET_MODE, { "entity_id": "climate.test", "preset_mode": "invalid", }, blocking=True, ) assert ( str(exc.value) == "Preset mode invalid is not valid. Valid preset modes are: home, away" ) assert exc.value.translation_key == "not_valid_preset_mode" with pytest.raises( ServiceValidationError, match="Swing mode invalid is not valid. Valid swing modes are: auto, off", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_SWING_MODE, { "entity_id": "climate.test", "swing_mode": "invalid", }, blocking=True, ) assert ( str(exc.value) == "Swing mode invalid is not valid. Valid swing modes are: auto, off" ) assert exc.value.translation_key == "not_valid_swing_mode" with pytest.raises( ServiceValidationError, match="Horizontal swing mode invalid is not valid. Valid horizontal swing modes are: on, off", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_SWING_HORIZONTAL_MODE, { "entity_id": "climate.test", "swing_horizontal_mode": "invalid", }, blocking=True, ) assert ( str(exc.value) == "Horizontal swing mode invalid is not valid. Valid horizontal swing modes are: on, off" ) assert exc.value.translation_key == "not_valid_horizontal_swing_mode" with pytest.raises( ServiceValidationError, match="Fan mode invalid is not valid. Valid fan modes are: auto, off", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_FAN_MODE, { "entity_id": "climate.test", "fan_mode": "invalid", }, blocking=True, ) assert ( str(exc.value) == "Fan mode invalid is not valid. Valid fan modes are: auto, off" ) assert exc.value.translation_key == "not_valid_fan_mode" async def test_turn_on_off_toggle(hass: HomeAssistant) -> None: """Test turn_on/turn_off/toggle methods.""" class MockClimateEntityTest(MockClimateEntity): """Mock Climate device.""" _attr_hvac_mode = HVACMode.OFF @property def hvac_mode(self) -> HVACMode: """Return hvac mode.""" return self._attr_hvac_mode async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._attr_hvac_mode = hvac_mode climate = MockClimateEntityTest() climate.hass = hass await climate.async_turn_on() assert climate.hvac_mode == HVACMode.HEAT await climate.async_turn_off() assert climate.hvac_mode == HVACMode.OFF await climate.async_toggle() assert climate.hvac_mode == HVACMode.HEAT await climate.async_toggle() assert climate.hvac_mode == HVACMode.OFF async def test_sync_toggle(hass: HomeAssistant) -> None: """Test if async toggle calls sync toggle.""" class MockClimateEntityTest(MockClimateEntity): """Mock Climate device.""" _attr_supported_features = ( ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON ) @property def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode. Need to be one of HVACMode.*. """ return HVACMode.HEAT @property def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. """ return [HVACMode.OFF, HVACMode.HEAT] def turn_on(self) -> None: """Turn on.""" def turn_off(self) -> None: """Turn off.""" def toggle(self) -> None: """Toggle.""" climate = MockClimateEntityTest() climate.hass = hass climate.toggle = Mock() await climate.async_toggle() assert climate.toggle.called ISSUE_TRACKER = "https://blablabla.com" @pytest.mark.parametrize( ( "manifest_extra", "translation_key", "translation_placeholders_extra", "report", "module", ), [ ( {}, "deprecated_climate_aux_no_url", {}, "report it to the author of the 'test' custom integration", "custom_components.test.climate", ), ( {"issue_tracker": ISSUE_TRACKER}, "deprecated_climate_aux_url_custom", {"issue_tracker": ISSUE_TRACKER}, "create a bug report at https://blablabla.com", "custom_components.test.climate", ), ], ) async def test_issue_aux_property_deprecated( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None, manifest_extra: dict[str, str], translation_key: str, translation_placeholders_extra: dict[str, str], report: str, module: str, issue_registry: ir.IssueRegistry, ) -> None: """Test the issue is raised on deprecated auxiliary heater attributes.""" class MockClimateEntityWithAux(MockClimateEntity): """Mock climate class with mocked aux heater.""" _attr_supported_features = ( ClimateEntityFeature.AUX_HEAT | ClimateEntityFeature.TARGET_TEMPERATURE ) @property def is_aux_heat(self) -> bool | None: """Return true if aux heater. Requires ClimateEntityFeature.AUX_HEAT. """ return True async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" await self.hass.async_add_executor_job(self.turn_aux_heat_on) async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" await self.hass.async_add_executor_job(self.turn_aux_heat_off) # Fake the module is custom component or built in MockClimateEntityWithAux.__module__ = module climate_entity = MockClimateEntityWithAux( name="Testing", entity_id="climate.testing", ) async def async_setup_entry_init( hass: HomeAssistant, config_entry: ConfigEntry ) -> bool: """Set up test config entry.""" await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) return True async def async_setup_entry_climate_platform( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up test weather platform via config entry.""" async_add_entities([climate_entity]) mock_integration( hass, MockModule( "test", async_setup_entry=async_setup_entry_init, partial_manifest=manifest_extra, ), built_in=False, ) mock_platform( hass, "test.climate", MockPlatform(async_setup_entry=async_setup_entry_climate_platform), ) config_entry = MockConfigEntry(domain="test") config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert climate_entity.state == HVACMode.HEAT issue = issue_registry.async_get_issue("climate", "deprecated_climate_aux_test") assert issue assert issue.issue_domain == "test" assert issue.issue_id == "deprecated_climate_aux_test" assert issue.translation_key == translation_key assert ( issue.translation_placeholders == {"platform": "test"} | translation_placeholders_extra ) assert ( "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " f"and will be unsupported from Home Assistant 2025.4. Please {report}" ) in caplog.text # Assert we only log warning once caplog.clear() await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test", "temperature": "25", }, blocking=True, ) await hass.async_block_till_done() assert ("implements the `is_aux_heat` property") not in caplog.text @pytest.mark.parametrize( ( "manifest_extra", "translation_key", "translation_placeholders_extra", "report", "module", ), [ ( {"issue_tracker": ISSUE_TRACKER}, "deprecated_climate_aux_url", {"issue_tracker": ISSUE_TRACKER}, "create a bug report at https://blablabla.com", "homeassistant.components.test.climate", ), ], ) async def test_no_issue_aux_property_deprecated_for_core( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, register_test_integration: MockConfigEntry, manifest_extra: dict[str, str], translation_key: str, translation_placeholders_extra: dict[str, str], report: str, module: str, issue_registry: ir.IssueRegistry, ) -> None: """Test the no issue on deprecated auxiliary heater attributes for core integrations.""" class MockClimateEntityWithAux(MockClimateEntity): """Mock climate class with mocked aux heater.""" _attr_supported_features = ClimateEntityFeature.AUX_HEAT @property def is_aux_heat(self) -> bool | None: """Return true if aux heater. Requires ClimateEntityFeature.AUX_HEAT. """ return True async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" await self.hass.async_add_executor_job(self.turn_aux_heat_on) async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" await self.hass.async_add_executor_job(self.turn_aux_heat_off) # Fake the module is custom component or built in MockClimateEntityWithAux.__module__ = module climate_entity = MockClimateEntityWithAux( name="Testing", entity_id="climate.testing", ) setup_test_component_platform( hass, DOMAIN, entities=[climate_entity], from_config_entry=True ) await hass.config_entries.async_setup(register_test_integration.entry_id) await hass.async_block_till_done() assert climate_entity.state == HVACMode.HEAT issue = issue_registry.async_get_issue("climate", "deprecated_climate_aux_test") assert not issue assert ( "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " f"and will be unsupported from Home Assistant 2024.10. Please {report}" ) not in caplog.text async def test_no_issue_no_aux_property( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, register_test_integration: MockConfigEntry, issue_registry: ir.IssueRegistry, ) -> None: """Test the issue is raised on deprecated auxiliary heater attributes.""" climate_entity = MockClimateEntity( name="Testing", entity_id="climate.testing", ) setup_test_component_platform( hass, DOMAIN, entities=[climate_entity], from_config_entry=True ) assert await hass.config_entries.async_setup(register_test_integration.entry_id) await hass.async_block_till_done() assert climate_entity.state == HVACMode.HEAT assert len(issue_registry.issues) == 0 assert ( "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " "and will be unsupported from Home Assistant 2024.10." ) 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: """Test validation for temperatures.""" class MockClimateEntityTemp(MockClimateEntity): """Mock climate class with mocked aux heater.""" _attr_supported_features = ( ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.SWING_MODE | ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) _attr_target_temperature = 15 _attr_target_temperature_high = 18 _attr_target_temperature_low = 10 _attr_target_temperature_step = PRECISION_WHOLE def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" if ATTR_TEMPERATURE in kwargs: self._attr_target_temperature = kwargs[ATTR_TEMPERATURE] if ATTR_TARGET_TEMP_HIGH in kwargs: self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH] self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW] test_climate = MockClimateEntityTemp( 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_CURRENT_TEMPERATURE) is None assert state.attributes.get(ATTR_MIN_TEMP) == 7 assert state.attributes.get(ATTR_MAX_TEMP) == 35 with pytest.raises( ServiceValidationError, match="Provided temperature 40.0 is not valid. Accepted range is 7 to 35", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test", ATTR_TEMPERATURE: "40", }, blocking=True, ) assert ( str(exc.value) == "Provided temperature 40.0 is not valid. Accepted range is 7 to 35" ) assert exc.value.translation_key == "temp_out_of_range" with pytest.raises( ServiceValidationError, match="Provided temperature 0.0 is not valid. Accepted range is 7 to 35", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test", ATTR_TARGET_TEMP_HIGH: "25", ATTR_TARGET_TEMP_LOW: "0", }, blocking=True, ) assert ( str(exc.value) == "Provided temperature 0.0 is not valid. Accepted range is 7 to 35" ) assert exc.value.translation_key == "temp_out_of_range" await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test", ATTR_TARGET_TEMP_HIGH: "25", ATTR_TARGET_TEMP_LOW: "10", }, blocking=True, ) state = hass.states.get("climate.test") assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 10 assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25 async def test_target_temp_high_higher_than_low( hass: HomeAssistant, register_test_integration: MockConfigEntry ) -> None: """Test that target high is higher than target low.""" class MockClimateEntityTemp(MockClimateEntity): """Mock climate class with mocked aux heater.""" _attr_supported_features = ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) _attr_current_temperature = 15 _attr_target_temperature = 15 _attr_target_temperature_high = 18 _attr_target_temperature_low = 10 _attr_target_temperature_step = PRECISION_WHOLE def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" if ATTR_TEMPERATURE in kwargs: self._attr_target_temperature = kwargs[ATTR_TEMPERATURE] if ATTR_TARGET_TEMP_HIGH in kwargs: self._attr_target_temperature_high = kwargs[ATTR_TARGET_TEMP_HIGH] self._attr_target_temperature_low = kwargs[ATTR_TARGET_TEMP_LOW] test_climate = MockClimateEntityTemp( 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_CURRENT_TEMPERATURE) == 15 assert state.attributes.get(ATTR_MIN_TEMP) == 7 assert state.attributes.get(ATTR_MAX_TEMP) == 35 with pytest.raises( ServiceValidationError, match="Target temperature low can not be higher than Target temperature high", ) as exc: await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE, { "entity_id": "climate.test", ATTR_TARGET_TEMP_HIGH: "15", ATTR_TARGET_TEMP_LOW: "20", }, blocking=True, ) assert ( str(exc.value) == "Target temperature low can not be higher than Target temperature high" ) assert exc.value.translation_key == "low_temp_higher_than_high_temp"