Implement mode validation in Climate entity component (#105745)

* Implement mode validation in Climate entity component

* Fix some tests

* more tests

* Fix translations

* fix deconz tests

* Fix switcher_kis tests

* not None

* Fix homematicip_cloud test

* Always validate

* Fix shelly

* reverse logic in validation

* modes_str

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
pull/106478/head
G Johansson 2023-12-27 14:51:39 +01:00 committed by GitHub
parent e04fda3fad
commit 83f4d3af5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 342 additions and 77 deletions

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import timedelta
import functools as ft
import logging
from typing import TYPE_CHECKING, Any, final
from typing import TYPE_CHECKING, Any, Literal, final
import voluptuous as vol
@ -19,7 +19,8 @@ from homeassistant.const import (
STATE_ON,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
@ -166,7 +167,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component.async_register_entity_service(
SERVICE_SET_PRESET_MODE,
{vol.Required(ATTR_PRESET_MODE): cv.string},
"async_set_preset_mode",
"async_handle_set_preset_mode_service",
[ClimateEntityFeature.PRESET_MODE],
)
component.async_register_entity_service(
@ -193,13 +194,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component.async_register_entity_service(
SERVICE_SET_FAN_MODE,
{vol.Required(ATTR_FAN_MODE): cv.string},
"async_set_fan_mode",
"async_handle_set_fan_mode_service",
[ClimateEntityFeature.FAN_MODE],
)
component.async_register_entity_service(
SERVICE_SET_SWING_MODE,
{vol.Required(ATTR_SWING_MODE): cv.string},
"async_set_swing_mode",
"async_handle_set_swing_mode_service",
[ClimateEntityFeature.SWING_MODE],
)
@ -515,6 +516,35 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""
return self._attr_swing_modes
@final
@callback
def _valid_mode_or_raise(
self,
mode_type: Literal["preset", "swing", "fan"],
mode: str,
modes: list[str] | None,
) -> None:
"""Raise ServiceValidationError on invalid modes."""
if modes and mode in modes:
return
modes_str: str = ", ".join(modes) if modes else ""
if mode_type == "preset":
translation_key = "not_valid_preset_mode"
elif mode_type == "swing":
translation_key = "not_valid_swing_mode"
elif mode_type == "fan":
translation_key = "not_valid_fan_mode"
raise ServiceValidationError(
f"The {mode_type}_mode {mode} is not a valid {mode_type}_mode:"
f" {modes_str}",
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders={
"mode": mode,
"modes": modes_str,
},
)
def set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
raise NotImplementedError()
@ -533,6 +563,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Set new target humidity."""
await self.hass.async_add_executor_job(self.set_humidity, humidity)
@final
async def async_handle_set_fan_mode_service(self, fan_mode: str) -> None:
"""Validate and set new preset mode."""
self._valid_mode_or_raise("fan", fan_mode, self.fan_modes)
await self.async_set_fan_mode(fan_mode)
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
raise NotImplementedError()
@ -549,6 +585,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Set new target hvac mode."""
await self.hass.async_add_executor_job(self.set_hvac_mode, hvac_mode)
@final
async def async_handle_set_swing_mode_service(self, swing_mode: str) -> None:
"""Validate and set new preset mode."""
self._valid_mode_or_raise("swing", swing_mode, self.swing_modes)
await self.async_set_swing_mode(swing_mode)
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
raise NotImplementedError()
@ -557,6 +599,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Set new target swing operation."""
await self.hass.async_add_executor_job(self.set_swing_mode, swing_mode)
@final
async def async_handle_set_preset_mode_service(self, preset_mode: str) -> None:
"""Validate and set new preset mode."""
self._valid_mode_or_raise("preset", preset_mode, self.preset_modes)
await self.async_set_preset_mode(preset_mode)
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
raise NotImplementedError()

View File

@ -233,5 +233,16 @@
"heat": "Heat"
}
}
},
"exceptions": {
"not_valid_preset_mode": {
"message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}."
},
"not_valid_swing_mode": {
"message": "Swing mode {mode} is not valid. Valid swing modes are: {modes}."
},
"not_valid_fan_mode": {
"message": "Fan mode {mode} is not valid. Valid fan modes are: {modes}."
}
}
}

View File

@ -73,7 +73,7 @@ async def async_setup_entry(
target_temperature=None,
unit_of_measurement=UnitOfTemperature.CELSIUS,
preset="home",
preset_modes=["home", "eco"],
preset_modes=["home", "eco", "away"],
current_temperature=23,
fan_mode="Auto Low",
target_humidity=None,

View File

@ -26,6 +26,7 @@ from homeassistant.components.climate import (
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import ServiceValidationError
from . import init_integration
@ -146,7 +147,7 @@ async def test_spa_preset_modes(
assert state
assert state.attributes[ATTR_PRESET_MODE] == mode
with pytest.raises(KeyError):
with pytest.raises(ServiceValidationError):
await common.async_set_preset_mode(hass, 2, ENTITY_CLIMATE)
# put it in RNR and test assertion

View File

@ -0,0 +1,22 @@
"""Fixtures for Climate platform tests."""
from collections.abc import Generator
import pytest
from homeassistant.config_entries import ConfigFlow
from homeassistant.core import HomeAssistant
from tests.common import mock_config_flow, mock_platform
class MockFlow(ConfigFlow):
"""Test flow."""
@pytest.fixture
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
"""Mock config flow."""
mock_platform(hass, "test.config_flow")
with mock_config_flow("test", MockFlow):
yield

View File

@ -10,16 +10,36 @@ import voluptuous as vol
from homeassistant.components import climate
from homeassistant.components.climate import (
DOMAIN,
SET_TEMPERATURE_SCHEMA,
ClimateEntity,
HVACMode,
)
from homeassistant.components.climate.const import (
ATTR_FAN_MODE,
ATTR_PRESET_MODE,
ATTR_SWING_MODE,
SERVICE_SET_FAN_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_SWING_MODE,
ClimateEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from tests.common import (
MockConfigEntry,
MockEntity,
MockModule,
MockPlatform,
async_mock_service,
import_and_test_deprecated_constant,
import_and_test_deprecated_constant_enum,
mock_integration,
mock_platform,
)
@ -57,9 +77,22 @@ async def test_set_temp_schema(
assert calls[-1].data == data
class MockClimateEntity(ClimateEntity):
class MockClimateEntity(MockEntity, ClimateEntity):
"""Mock Climate device to use in tests."""
_attr_supported_features = (
ClimateEntityFeature.FAN_MODE
| ClimateEntityFeature.PRESET_MODE
| ClimateEntityFeature.SWING_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_temperature_unit = UnitOfTemperature.CELSIUS
@property
def hvac_mode(self) -> HVACMode:
"""Return hvac operation ie. heat, cool mode.
@ -82,6 +115,18 @@ class MockClimateEntity(ClimateEntity):
def turn_off(self) -> None:
"""Turn off."""
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
async def test_sync_turn_on(hass: HomeAssistant) -> None:
"""Test if async turn_on calls sync turn_on."""
@ -158,3 +203,133 @@ def test_deprecated_current_constants(
enum,
"2025.1",
)
async def test_preset_mode_validation(
hass: HomeAssistant, config_flow_fixture: None
) -> None:
"""Test mode validation for fan, swing and preset."""
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 climate platform via config entry."""
async_add_entities([MockClimateEntity(name="test", entity_id="climate.test")])
mock_integration(
hass,
MockModule(
"test",
async_setup_entry=async_setup_entry_init,
),
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()
state = hass.states.get("climate.test")
assert state.attributes.get(ATTR_PRESET_MODE) == "home"
assert state.attributes.get(ATTR_FAN_MODE) == "auto"
assert state.attributes.get(ATTR_SWING_MODE) == "auto"
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_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"
with pytest.raises(
ServiceValidationError,
match="The preset_mode invalid is not a valid preset_mode: 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)
== "The preset_mode invalid is not a valid preset_mode: home, away"
)
assert exc.value.translation_key == "not_valid_preset_mode"
with pytest.raises(
ServiceValidationError,
match="The swing_mode invalid is not a valid swing_mode: 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) == "The swing_mode invalid is not a valid swing_mode: auto, off"
)
assert exc.value.translation_key == "not_valid_swing_mode"
with pytest.raises(
ServiceValidationError,
match="The fan_mode invalid is not a valid fan_mode: 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) == "The fan_mode invalid is not a valid fan_mode: auto, off"
assert exc.value.translation_key == "not_valid_fan_mode"

View File

@ -41,6 +41,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from .test_gateway import (
DECONZ_WEB_REQUEST,
@ -602,7 +603,7 @@ async def test_climate_device_with_fan_support(
# Service set fan mode to unsupported value
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
@ -725,7 +726,7 @@ async def test_climate_device_with_preset(
# Service set preset to unsupported value
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,

View File

@ -278,12 +278,12 @@ async def test_set_fan_mode(hass: HomeAssistant) -> None:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_FAN_MODE: "On Low"},
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_FAN_MODE: "on_low"},
blocking=True,
)
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_FAN_MODE) == "On Low"
assert state.attributes.get(ATTR_FAN_MODE) == "on_low"
async def test_set_swing_mode_bad_attr(hass: HomeAssistant) -> None:
@ -311,12 +311,12 @@ async def test_set_swing(hass: HomeAssistant) -> None:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_SWING_MODE,
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_SWING_MODE: "Auto"},
{ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_SWING_MODE: "auto"},
blocking=True,
)
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get(ATTR_SWING_MODE) == "Auto"
assert state.attributes.get(ATTR_SWING_MODE) == "auto"
async def test_set_hvac_bad_attr_and_state(hass: HomeAssistant) -> None:

View File

@ -41,6 +41,7 @@ from homeassistant.core import (
State,
callback,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
@ -388,7 +389,7 @@ async def test_set_preset_mode_invalid(hass: HomeAssistant, setup_comp_2) -> Non
await common.async_set_preset_mode(hass, "none")
state = hass.states.get(ENTITY)
assert state.attributes.get("preset_mode") == "none"
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await common.async_set_preset_mode(hass, "Sleep")
state = hass.states.get(ENTITY)
assert state.attributes.get("preset_mode") == "none"

View File

@ -50,6 +50,7 @@ from homeassistant.const import (
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
import homeassistant.util.dt as dt_util
@ -538,7 +539,7 @@ async def test_send_invalid_preset_mode(
"""Test for sending preset mode command to the device."""
await async_setup_gree(hass)
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
@ -699,7 +700,7 @@ async def test_send_invalid_fan_mode(
"""Test for sending fan mode command to the device."""
await async_setup_gree(hass)
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_FAN_MODE,
@ -780,7 +781,7 @@ async def test_send_invalid_swing_mode(
"""Test for sending swing mode command to the device."""
await async_setup_gree(hass)
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_SWING_MODE,

View File

@ -3,6 +3,7 @@ import datetime
from homematicip.base.enums import AbsenceType
from homematicip.functionalHomes import IndoorClimateHome
import pytest
from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE,
@ -23,6 +24,7 @@ from homeassistant.components.homematicip_cloud.climate import (
PERMANENT_END_TIME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.setup import async_setup_component
from .helper import HAPID, async_manipulate_test_data, get_and_check_entity_basics
@ -340,12 +342,13 @@ async def test_hmip_heating_group_cool(
assert ha_state.attributes[ATTR_PRESET_MODE] == "none"
assert ha_state.attributes[ATTR_PRESET_MODES] == []
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Cool2"},
blocking=True,
)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "Cool2"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 12
# fire_update_event shows that set_active_profile has not been called.

View File

@ -50,6 +50,7 @@ from homeassistant.const import (
ATTR_TEMPERATURE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.util import utcnow
@ -370,7 +371,7 @@ async def test_thermostat_set_invalid_preset(
hass: HomeAssistant, cube: MaxCube, thermostat: MaxThermostat
) -> None:
"""Set hvac mode to heat."""
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,

View File

@ -33,6 +33,7 @@ from homeassistant.components.mqtt.climate import (
)
from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from .test_common import (
help_custom_config,
@ -1130,8 +1131,9 @@ async def test_set_preset_mode_optimistic(
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get("preset_mode") == "comfort"
await common.async_set_preset_mode(hass, "invalid", ENTITY_CLIMATE)
assert "'invalid' is not a valid preset mode" in caplog.text
with pytest.raises(ServiceValidationError):
await common.async_set_preset_mode(hass, "invalid", ENTITY_CLIMATE)
assert "'invalid' is not a valid preset mode" in caplog.text
@pytest.mark.parametrize(
@ -1187,8 +1189,9 @@ async def test_set_preset_mode_explicit_optimistic(
state = hass.states.get(ENTITY_CLIMATE)
assert state.attributes.get("preset_mode") == "comfort"
await common.async_set_preset_mode(hass, "invalid", ENTITY_CLIMATE)
assert "'invalid' is not a valid preset mode" in caplog.text
with pytest.raises(ServiceValidationError):
await common.async_set_preset_mode(hass, "invalid", ENTITY_CLIMATE)
assert "'invalid' is not a valid preset mode" in caplog.text
@pytest.mark.parametrize(

View File

@ -39,7 +39,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .common import (
DEVICE_COMMAND,
@ -1192,7 +1192,7 @@ async def test_thermostat_invalid_fan_mode(
assert thermostat.attributes[ATTR_FAN_MODE] == FAN_ON
assert thermostat.attributes[ATTR_FAN_MODES] == [FAN_ON, FAN_OFF]
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await common.async_set_fan_mode(hass, FAN_LOW)
await hass.async_block_till_done()
@ -1474,7 +1474,7 @@ async def test_thermostat_invalid_set_preset_mode(
assert thermostat.attributes[ATTR_PRESET_MODES] == [PRESET_ECO, PRESET_NONE]
# Set preset mode that is invalid
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await common.async_set_preset_mode(hass, PRESET_SLEEP)
await hass.async_block_till_done()

View File

@ -33,6 +33,7 @@ from homeassistant.components.netatmo.const import (
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.util import dt as dt_util
from .common import selected_platforms, simulate_webhook
@ -879,15 +880,14 @@ async def test_service_preset_mode_invalid(
await hass.async_block_till_done()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: "climate.cocina", ATTR_PRESET_MODE: "invalid"},
blocking=True,
)
await hass.async_block_till_done()
assert "Preset mode 'invalid' not available" in caplog.text
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: "climate.cocina", ATTR_PRESET_MODE: "invalid"},
blocking=True,
)
await hass.async_block_till_done()
async def test_valves_service_turn_off(

View File

@ -1330,10 +1330,7 @@ async def test_climate_fan_mode_and_swing_mode_not_supported(
with patch(
"homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property",
), pytest.raises(
HomeAssistantError,
match="Climate swing mode faulty_swing_mode is not supported by the integration, please open an issue",
):
), pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_SWING_MODE,
@ -1343,10 +1340,7 @@ async def test_climate_fan_mode_and_swing_mode_not_supported(
with patch(
"homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property",
), pytest.raises(
HomeAssistantError,
match="Climate fan mode faulty_fan_mode is not supported by the integration, please open an issue",
):
), pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,

View File

@ -25,7 +25,7 @@ from homeassistant.components.shelly.const import DOMAIN, MODEL_WALL_DISPLAY
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
import homeassistant.helpers.issue_registry as ir
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
@ -382,12 +382,13 @@ async def test_block_restored_climate_set_preset_before_online(
assert hass.states.get(entity_id).state == HVACMode.HEAT
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: "Profile1"},
blocking=True,
)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: "Profile1"},
blocking=True,
)
mock_block_device.http_request.assert_not_called()

View File

@ -25,7 +25,7 @@ from homeassistant.components.climate import (
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.util import slugify
from . import init_integration
@ -336,9 +336,8 @@ async def test_climate_control_errors(
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 24},
blocking=True,
)
# Test exception when trying set fan level
with pytest.raises(HomeAssistantError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
@ -347,7 +346,7 @@ async def test_climate_control_errors(
)
# Test exception when trying set swing mode
with pytest.raises(HomeAssistantError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_SWING_MODE,

View File

@ -43,6 +43,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from . import init_integration
@ -337,7 +338,7 @@ async def test_service_calls(
mock_instance.set_fanspeed.reset_mock()
# FAN_MIDDLE is not supported
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,

View File

@ -52,7 +52,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from .common import async_enable_traffic, find_entity_id, send_attributes_report
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
@ -860,12 +860,13 @@ async def test_preset_setting_invalid(
state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "invalid_preset"},
blocking=True,
)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "invalid_preset"},
blocking=True,
)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
@ -1251,13 +1252,14 @@ async def test_set_fan_mode_not_supported(
entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
fan_cluster = device_climate_fan.device.endpoints[1].fan
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
blocking=True,
)
assert fan_cluster.write_attributes.await_count == 0
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
blocking=True,
)
assert fan_cluster.write_attributes.await_count == 0
async def test_set_fan_mode(hass: HomeAssistant, device_climate_fan) -> None:

View File

@ -40,6 +40,7 @@ from homeassistant.const import (
ATTR_TEMPERATURE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import issue_registry as ir
from .common import (
@ -278,7 +279,7 @@ async def test_thermostat_v2(
client.async_send_command.reset_mock()
# Test setting invalid fan mode
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
@ -692,7 +693,7 @@ async def test_preset_and_no_setpoint(
assert state.attributes[ATTR_TEMPERATURE] is None
assert state.attributes[ATTR_PRESET_MODE] == "Full power"
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
# Test setting invalid preset mode
await hass.services.async_call(
CLIMATE_DOMAIN,