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
parent
e04fda3fad
commit
83f4d3af5c
|
@ -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()
|
||||
|
|
|
@ -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}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue