Use ServiceValidationError for invalid fan preset_mode and move check to fan entity component (#104560)
* Use ServiceValidationError for fan preset_mode * Use _valid_preset_mode_or_raise to raise * Move preset_mode validation to entity component * Fix bond fan and comments * Fixes baf, fjaraskupan and template * More integration adjustments * Add custom components mock and test code * Make NotValidPresetModeError subclass * Update homeassistant/components/fan/strings.json Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Keep bond has_action validation * Move demo test asserts outside context block * Follow up comment * Update homeassistant/components/fan/strings.json Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Fix demo tests * Remove pylint disable * Remove unreachable code * Update homeassistant/components/fan/__init__.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Use NotValidPresetModeError, Final methods * Address comments * Correct docst * Follow up comments * Update homeassistant/components/fan/__init__.py Co-authored-by: Erik Montnemery <erik@montnemery.com> --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: G Johansson <goran.johansson@shiftit.se> Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
49381cefa3
commit
953a212dd6
22 changed files with 260 additions and 118 deletions
|
@ -93,8 +93,6 @@ class BAFFan(BAFEntity, FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode != PRESET_MODE_AUTO:
|
|
||||||
raise ValueError(f"Invalid preset mode: {preset_mode}")
|
|
||||||
self._device.fan_mode = OffOnAuto.AUTO
|
self._device.fan_mode = OffOnAuto.AUTO
|
||||||
|
|
||||||
async def async_set_direction(self, direction: str) -> None:
|
async def async_set_direction(self, direction: str) -> None:
|
||||||
|
|
|
@ -199,10 +199,6 @@ class BondFan(BondEntity, FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode != PRESET_MODE_BREEZE or not self._device.has_action(
|
|
||||||
Action.BREEZE_ON
|
|
||||||
):
|
|
||||||
raise ValueError(f"Invalid preset mode: {preset_mode}")
|
|
||||||
await self._hub.bond.action(self._device.device_id, Action(Action.BREEZE_ON))
|
await self._hub.bond.action(self._device.device_id, Action(Action.BREEZE_ON))
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
|
|
@ -161,12 +161,9 @@ class DemoPercentageFan(BaseDemoFan, FanEntity):
|
||||||
|
|
||||||
def set_preset_mode(self, preset_mode: str) -> None:
|
def set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
if self.preset_modes and preset_mode in self.preset_modes:
|
self._preset_mode = preset_mode
|
||||||
self._preset_mode = preset_mode
|
self._percentage = None
|
||||||
self._percentage = None
|
self.schedule_update_ha_state()
|
||||||
self.schedule_update_ha_state()
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid preset mode: {preset_mode}")
|
|
||||||
|
|
||||||
def turn_on(
|
def turn_on(
|
||||||
self,
|
self,
|
||||||
|
@ -230,10 +227,6 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
if self.preset_modes is None or preset_mode not in self.preset_modes:
|
|
||||||
raise ValueError(
|
|
||||||
f"{preset_mode} is not a valid preset_mode: {self.preset_modes}"
|
|
||||||
)
|
|
||||||
self._preset_mode = preset_mode
|
self._preset_mode = preset_mode
|
||||||
self._percentage = None
|
self._percentage = None
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -18,7 +18,8 @@ from homeassistant.const import (
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
|
@ -77,8 +78,19 @@ ATTR_PRESET_MODES = "preset_modes"
|
||||||
# mypy: disallow-any-generics
|
# mypy: disallow-any-generics
|
||||||
|
|
||||||
|
|
||||||
class NotValidPresetModeError(ValueError):
|
class NotValidPresetModeError(ServiceValidationError):
|
||||||
"""Exception class when the preset_mode in not in the preset_modes list."""
|
"""Raised when the preset_mode is not in the preset_modes list."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, *args: object, translation_placeholders: dict[str, str] | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the exception."""
|
||||||
|
super().__init__(
|
||||||
|
*args,
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="not_valid_preset_mode",
|
||||||
|
translation_placeholders=translation_placeholders,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
|
@ -107,7 +119,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_PRESET_MODE): cv.string,
|
vol.Optional(ATTR_PRESET_MODE): cv.string,
|
||||||
},
|
},
|
||||||
"async_turn_on",
|
"async_handle_turn_on_service",
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
||||||
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
||||||
|
@ -156,7 +168,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
{vol.Required(ATTR_PRESET_MODE): cv.string},
|
{vol.Required(ATTR_PRESET_MODE): cv.string},
|
||||||
"async_set_preset_mode",
|
"async_handle_set_preset_mode_service",
|
||||||
[FanEntityFeature.SET_SPEED, FanEntityFeature.PRESET_MODE],
|
[FanEntityFeature.SET_SPEED, FanEntityFeature.PRESET_MODE],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -237,17 +249,30 @@ class FanEntity(ToggleEntity):
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@final
|
||||||
|
async def async_handle_set_preset_mode_service(self, preset_mode: str) -> None:
|
||||||
|
"""Validate and set new preset mode."""
|
||||||
|
self._valid_preset_mode_or_raise(preset_mode)
|
||||||
|
await self.async_set_preset_mode(preset_mode)
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode)
|
await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode)
|
||||||
|
|
||||||
|
@final
|
||||||
|
@callback
|
||||||
def _valid_preset_mode_or_raise(self, preset_mode: str) -> None:
|
def _valid_preset_mode_or_raise(self, preset_mode: str) -> None:
|
||||||
"""Raise NotValidPresetModeError on invalid preset_mode."""
|
"""Raise NotValidPresetModeError on invalid preset_mode."""
|
||||||
preset_modes = self.preset_modes
|
preset_modes = self.preset_modes
|
||||||
if not preset_modes or preset_mode not in preset_modes:
|
if not preset_modes or preset_mode not in preset_modes:
|
||||||
|
preset_modes_str: str = ", ".join(preset_modes or [])
|
||||||
raise NotValidPresetModeError(
|
raise NotValidPresetModeError(
|
||||||
f"The preset_mode {preset_mode} is not a valid preset_mode:"
|
f"The preset_mode {preset_mode} is not a valid preset_mode:"
|
||||||
f" {preset_modes}"
|
f" {preset_modes}",
|
||||||
|
translation_placeholders={
|
||||||
|
"preset_mode": preset_mode,
|
||||||
|
"preset_modes": preset_modes_str,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_direction(self, direction: str) -> None:
|
def set_direction(self, direction: str) -> None:
|
||||||
|
@ -267,6 +292,18 @@ class FanEntity(ToggleEntity):
|
||||||
"""Turn on the fan."""
|
"""Turn on the fan."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@final
|
||||||
|
async def async_handle_turn_on_service(
|
||||||
|
self,
|
||||||
|
percentage: int | None = None,
|
||||||
|
preset_mode: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Validate and turn on the fan."""
|
||||||
|
if preset_mode is not None:
|
||||||
|
self._valid_preset_mode_or_raise(preset_mode)
|
||||||
|
await self.async_turn_on(percentage, preset_mode, **kwargs)
|
||||||
|
|
||||||
async def async_turn_on(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
percentage: int | None = None,
|
percentage: int | None = None,
|
||||||
|
|
|
@ -144,5 +144,10 @@
|
||||||
"reverse": "Reverse"
|
"reverse": "Reverse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"not_valid_preset_mode": {
|
||||||
|
"message": "Preset mode {preset_mode} is not valid, valid preset modes are: {preset_modes}."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,11 +131,9 @@ class Fan(CoordinatorEntity[FjaraskupanCoordinator], FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
if command := PRESET_TO_COMMAND.get(preset_mode):
|
command = PRESET_TO_COMMAND[preset_mode]
|
||||||
async with self.coordinator.async_connect_and_update() as device:
|
async with self.coordinator.async_connect_and_update() as device:
|
||||||
await device.send_command(command)
|
await device.send_command(command)
|
||||||
else:
|
|
||||||
raise UnsupportedPreset(f"The preset {preset_mode} is unsupported")
|
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
|
|
|
@ -553,8 +553,6 @@ class MqttFan(MqttEntity, FanEntity):
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
self._valid_preset_mode_or_raise(preset_mode)
|
|
||||||
|
|
||||||
mqtt_payload = self._command_templates[ATTR_PRESET_MODE](preset_mode)
|
mqtt_payload = self._command_templates[ATTR_PRESET_MODE](preset_mode)
|
||||||
|
|
||||||
await self.async_publish(
|
await self.async_publish(
|
||||||
|
|
|
@ -282,15 +282,6 @@ class TemplateFan(TemplateEntity, FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset_mode of the fan."""
|
"""Set the preset_mode of the fan."""
|
||||||
if self.preset_modes and preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Received invalid preset_mode: %s for entity %s. Expected: %s",
|
|
||||||
preset_mode,
|
|
||||||
self.entity_id,
|
|
||||||
self.preset_modes,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._preset_mode = preset_mode
|
self._preset_mode = preset_mode
|
||||||
|
|
||||||
if self._set_preset_mode_script:
|
if self._set_preset_mode_script:
|
||||||
|
|
|
@ -119,8 +119,7 @@ class TradfriAirPurifierFan(TradfriBaseEntity, FanEntity):
|
||||||
if not self._device_control:
|
if not self._device_control:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not preset_mode == ATTR_AUTO:
|
# Preset must be 'Auto'
|
||||||
raise ValueError("Preset must be 'Auto'.")
|
|
||||||
|
|
||||||
await self._api(self._device_control.turn_on_auto_mode())
|
await self._api(self._device_control.turn_on_auto_mode())
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,7 @@ from vallox_websocket_api import (
|
||||||
ValloxInvalidInputException,
|
ValloxInvalidInputException,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.fan import (
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
FanEntity,
|
|
||||||
FanEntityFeature,
|
|
||||||
NotValidPresetModeError,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
@ -200,12 +196,6 @@ class ValloxFanEntity(ValloxEntity, FanEntity):
|
||||||
|
|
||||||
Returns true if the mode has been changed, false otherwise.
|
Returns true if the mode has been changed, false otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
self._valid_preset_mode_or_raise(preset_mode)
|
|
||||||
|
|
||||||
except NotValidPresetModeError as err:
|
|
||||||
raise ValueError(f"Not valid preset mode: {preset_mode}") from err
|
|
||||||
|
|
||||||
if preset_mode == self.preset_mode:
|
if preset_mode == self.preset_mode:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -530,9 +530,6 @@ class XiaomiAirPurifier(XiaomiGenericAirPurifier):
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
if await self._try_command(
|
if await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
@ -623,9 +620,6 @@ class XiaomiAirPurifierMB4(XiaomiGenericAirPurifier):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
if await self._try_command(
|
if await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
@ -721,9 +715,6 @@ class XiaomiAirFresh(XiaomiGenericAirPurifier):
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
if await self._try_command(
|
if await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
@ -809,9 +800,6 @@ class XiaomiAirFreshA1(XiaomiGenericAirPurifier):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan. This method is a coroutine."""
|
"""Set the preset mode of the fan. This method is a coroutine."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
if await self._try_command(
|
if await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
@ -958,10 +946,6 @@ class XiaomiFan(XiaomiGenericFan):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
|
|
||||||
if preset_mode == ATTR_MODE_NATURE:
|
if preset_mode == ATTR_MODE_NATURE:
|
||||||
await self._try_command(
|
await self._try_command(
|
||||||
"Setting natural fan speed percentage of the miio device failed.",
|
"Setting natural fan speed percentage of the miio device failed.",
|
||||||
|
@ -1034,9 +1018,6 @@ class XiaomiFanP5(XiaomiGenericFan):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
await self._try_command(
|
await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
@ -1093,9 +1074,6 @@ class XiaomiFanMiot(XiaomiGenericFan):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode of the fan."""
|
"""Set the preset mode of the fan."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
|
||||||
return
|
|
||||||
await self._try_command(
|
await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
|
|
|
@ -13,7 +13,6 @@ from homeassistant.components.fan import (
|
||||||
ATTR_PRESET_MODE,
|
ATTR_PRESET_MODE,
|
||||||
FanEntity,
|
FanEntity,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
NotValidPresetModeError,
|
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||||
|
@ -131,11 +130,6 @@ class BaseFan(FanEntity):
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set the preset mode for the fan."""
|
"""Set the preset mode for the fan."""
|
||||||
if preset_mode not in self.preset_modes:
|
|
||||||
raise NotValidPresetModeError(
|
|
||||||
f"The preset_mode {preset_mode} is not a valid preset_mode:"
|
|
||||||
f" {self.preset_modes}"
|
|
||||||
)
|
|
||||||
await self._async_set_fan_mode(self.preset_name_to_mode[preset_mode])
|
await self._async_set_fan_mode(self.preset_name_to_mode[preset_mode])
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
|
@ -18,7 +18,6 @@ from homeassistant.components.fan import (
|
||||||
DOMAIN as FAN_DOMAIN,
|
DOMAIN as FAN_DOMAIN,
|
||||||
FanEntity,
|
FanEntity,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
NotValidPresetModeError,
|
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
@ -181,11 +180,6 @@ class ValueMappingZwaveFan(ZwaveFan):
|
||||||
await self._async_set_value(self._target_value, zwave_value)
|
await self._async_set_value(self._target_value, zwave_value)
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotValidPresetModeError(
|
|
||||||
f"The preset_mode {preset_mode} is not a valid preset_mode:"
|
|
||||||
f" {self.preset_modes}"
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return whether the entity is available."""
|
"""Return whether the entity is available."""
|
||||||
|
|
|
@ -26,6 +26,7 @@ from homeassistant.components.fan import (
|
||||||
SERVICE_SET_PERCENTAGE,
|
SERVICE_SET_PERCENTAGE,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
|
NotValidPresetModeError,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
|
@ -251,10 +252,14 @@ async def test_turn_on_fan_preset_mode_not_supported(hass: HomeAssistant) -> Non
|
||||||
props={"max_speed": 6},
|
props={"max_speed": 6},
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch_bond_action(), patch_bond_device_state(), pytest.raises(ValueError):
|
with patch_bond_action(), patch_bond_device_state(), pytest.raises(
|
||||||
|
NotValidPresetModeError
|
||||||
|
):
|
||||||
await turn_fan_on(hass, "fan.name_1", preset_mode=PRESET_MODE_BREEZE)
|
await turn_fan_on(hass, "fan.name_1", preset_mode=PRESET_MODE_BREEZE)
|
||||||
|
|
||||||
with patch_bond_action(), patch_bond_device_state(), pytest.raises(ValueError):
|
with patch_bond_action(), patch_bond_device_state(), pytest.raises(
|
||||||
|
NotValidPresetModeError
|
||||||
|
):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN,
|
FAN_DOMAIN,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
|
|
@ -182,7 +182,7 @@ async def test_turn_on_with_preset_mode_only(
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(fan.NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
@ -190,6 +190,12 @@ async def test_turn_on_with_preset_mode_only(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert exc.value.translation_domain == fan.DOMAIN
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
assert exc.value.translation_placeholders == {
|
||||||
|
"preset_mode": "invalid",
|
||||||
|
"preset_modes": "auto, smart, sleep, on",
|
||||||
|
}
|
||||||
|
|
||||||
state = hass.states.get(fan_entity_id)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
@ -250,7 +256,7 @@ async def test_turn_on_with_preset_mode_and_speed(
|
||||||
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
assert state.attributes[fan.ATTR_PERCENTAGE] == 0
|
||||||
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
assert state.attributes[fan.ATTR_PRESET_MODE] is None
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(fan.NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
@ -258,6 +264,12 @@ async def test_turn_on_with_preset_mode_and_speed(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert exc.value.translation_domain == fan.DOMAIN
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
assert exc.value.translation_placeholders == {
|
||||||
|
"preset_mode": "invalid",
|
||||||
|
"preset_modes": "auto, smart, sleep, on",
|
||||||
|
}
|
||||||
|
|
||||||
state = hass.states.get(fan_entity_id)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
@ -343,7 +355,7 @@ async def test_set_preset_mode_invalid(hass: HomeAssistant, fan_entity_id) -> No
|
||||||
state = hass.states.get(fan_entity_id)
|
state = hass.states.get(fan_entity_id)
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(fan.NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
fan.SERVICE_SET_PRESET_MODE,
|
fan.SERVICE_SET_PRESET_MODE,
|
||||||
|
@ -351,8 +363,10 @@ async def test_set_preset_mode_invalid(hass: HomeAssistant, fan_entity_id) -> No
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert exc.value.translation_domain == fan.DOMAIN
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(fan.NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
@ -360,6 +374,8 @@ async def test_set_preset_mode_invalid(hass: HomeAssistant, fan_entity_id) -> No
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
assert exc.value.translation_domain == fan.DOMAIN
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
@pytest.mark.parametrize("fan_entity_id", FULL_FAN_ENTITY_IDS)
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
"""Tests for fan platforms."""
|
"""Tests for fan platforms."""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fan import FanEntity
|
from homeassistant.components.fan import (
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
ATTR_PRESET_MODES,
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
FanEntity,
|
||||||
|
NotValidPresetModeError,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.testing_config.custom_components.test.fan import MockFan
|
||||||
|
|
||||||
|
|
||||||
class BaseFan(FanEntity):
|
class BaseFan(FanEntity):
|
||||||
|
@ -82,3 +93,55 @@ def test_fanentity_attributes(attribute_name, attribute_value) -> None:
|
||||||
fan = BaseFan()
|
fan = BaseFan()
|
||||||
setattr(fan, f"_attr_{attribute_name}", attribute_value)
|
setattr(fan, f"_attr_{attribute_name}", attribute_value)
|
||||||
assert getattr(fan, attribute_name) == attribute_value
|
assert getattr(fan, attribute_name) == attribute_value
|
||||||
|
|
||||||
|
|
||||||
|
async def test_preset_mode_validation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
enable_custom_integrations: None,
|
||||||
|
) -> None:
|
||||||
|
"""Test preset mode validation."""
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
platform = getattr(hass.components, "test.fan")
|
||||||
|
platform.init(empty=False)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "fan", {"fan": {"platform": "test"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
test_fan: MockFan = platform.ENTITIES["support_preset_mode"]
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("fan.support_fan_with_preset_mode_support")
|
||||||
|
assert state.attributes.get(ATTR_PRESET_MODES) == ["auto", "eco"]
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{
|
||||||
|
"entity_id": "fan.support_fan_with_preset_mode_support",
|
||||||
|
"preset_mode": "eco",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("fan.support_fan_with_preset_mode_support")
|
||||||
|
assert state.attributes.get(ATTR_PRESET_MODE) == "eco"
|
||||||
|
|
||||||
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{
|
||||||
|
"entity_id": "fan.support_fan_with_preset_mode_support",
|
||||||
|
"preset_mode": "invalid",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
|
await test_fan._valid_preset_mode_or_raise("invalid")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
|
@ -705,8 +705,9 @@ async def test_sending_mqtt_commands_and_optimistic(
|
||||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "low")
|
await common.async_set_preset_mode(hass, "fan.test", "low")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
@ -916,11 +917,13 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(
|
||||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "low")
|
await common.async_set_preset_mode(hass, "fan.test", "low")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "auto")
|
await common.async_set_preset_mode(hass, "fan.test", "auto")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
@ -976,8 +979,9 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_turn_on(hass, "fan.test", preset_mode="freaking-high")
|
await common.async_turn_on(hass, "fan.test", preset_mode="freaking-high")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1078,11 +1082,13 @@ async def test_sending_mqtt_command_templates_(
|
||||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "low")
|
await common.async_set_preset_mode(hass, "fan.test", "low")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
@ -1140,8 +1146,9 @@ async def test_sending_mqtt_command_templates_(
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_turn_on(hass, "fan.test", preset_mode="low")
|
await common.async_turn_on(hass, "fan.test", preset_mode="low")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -1176,8 +1183,9 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
@ -1276,11 +1284,10 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_turn_on(hass, "fan.test", preset_mode="auto")
|
await common.async_turn_on(hass, "fan.test", preset_mode="auto")
|
||||||
assert mqtt_mock.async_publish.call_count == 1
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
# We can turn on, but the invalid preset mode will raise
|
assert mqtt_mock.async_publish.call_count == 0
|
||||||
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
mqtt_mock.async_publish.reset_mock()
|
||||||
|
|
||||||
await common.async_turn_on(hass, "fan.test", preset_mode="whoosh")
|
await common.async_turn_on(hass, "fan.test", preset_mode="whoosh")
|
||||||
|
@ -1428,11 +1435,13 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(
|
||||||
with pytest.raises(MultipleInvalid):
|
with pytest.raises(MultipleInvalid):
|
||||||
await common.async_set_percentage(hass, "fan.test", 101)
|
await common.async_set_percentage(hass, "fan.test", 101)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "low")
|
await common.async_set_preset_mode(hass, "fan.test", "low")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
await common.async_set_preset_mode(hass, "fan.test", "medium")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
|
||||||
mqtt_mock.async_publish.assert_called_once_with(
|
mqtt_mock.async_publish.assert_called_once_with(
|
||||||
|
@ -1452,8 +1461,9 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(
|
||||||
assert state.state == STATE_OFF
|
assert state.state == STATE_OFF
|
||||||
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
assert state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
|
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
mqtt_mock.async_publish.reset_mock()
|
mqtt_mock.async_publish.reset_mock()
|
||||||
state = hass.states.get("fan.test")
|
state = hass.states.get("fan.test")
|
||||||
|
|
|
@ -12,6 +12,7 @@ from homeassistant.components.fan import (
|
||||||
DIRECTION_REVERSE,
|
DIRECTION_REVERSE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
|
NotValidPresetModeError,
|
||||||
)
|
)
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -489,7 +490,11 @@ async def test_preset_modes(hass: HomeAssistant, calls) -> None:
|
||||||
("smart", "smart", 3),
|
("smart", "smart", 3),
|
||||||
("invalid", "smart", 3),
|
("invalid", "smart", 3),
|
||||||
]:
|
]:
|
||||||
await common.async_set_preset_mode(hass, _TEST_FAN, extra)
|
if extra != state:
|
||||||
|
with pytest.raises(NotValidPresetModeError):
|
||||||
|
await common.async_set_preset_mode(hass, _TEST_FAN, extra)
|
||||||
|
else:
|
||||||
|
await common.async_set_preset_mode(hass, _TEST_FAN, extra)
|
||||||
assert hass.states.get(_PRESET_MODE_INPUT_SELECT).state == state
|
assert hass.states.get(_PRESET_MODE_INPUT_SELECT).state == state
|
||||||
assert len(calls) == expected_calls
|
assert len(calls) == expected_calls
|
||||||
assert calls[-1].data["action"] == "set_preset_mode"
|
assert calls[-1].data["action"] == "set_preset_mode"
|
||||||
|
@ -550,6 +555,7 @@ async def test_no_value_template(hass: HomeAssistant, calls) -> None:
|
||||||
with assert_setup_component(1, "fan"):
|
with assert_setup_component(1, "fan"):
|
||||||
test_fan_config = {
|
test_fan_config = {
|
||||||
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
"preset_mode_template": "{{ states('input_select.preset_mode') }}",
|
||||||
|
"preset_modes": ["auto"],
|
||||||
"percentage_template": "{{ states('input_number.percentage') }}",
|
"percentage_template": "{{ states('input_number.percentage') }}",
|
||||||
"oscillating_template": "{{ states('input_select.osc') }}",
|
"oscillating_template": "{{ states('input_select.osc') }}",
|
||||||
"direction_template": "{{ states('input_select.direction') }}",
|
"direction_template": "{{ states('input_select.direction') }}",
|
||||||
|
@ -625,18 +631,18 @@ async def test_no_value_template(hass: HomeAssistant, calls) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
await common.async_turn_on(hass, _TEST_FAN)
|
await common.async_turn_on(hass, _TEST_FAN)
|
||||||
_verify(hass, STATE_ON, 0, None, None, None)
|
_verify(hass, STATE_ON, 0, None, None, "auto")
|
||||||
|
|
||||||
await common.async_turn_off(hass, _TEST_FAN)
|
await common.async_turn_off(hass, _TEST_FAN)
|
||||||
_verify(hass, STATE_OFF, 0, None, None, None)
|
_verify(hass, STATE_OFF, 0, None, None, "auto")
|
||||||
|
|
||||||
percent = 100
|
percent = 100
|
||||||
await common.async_set_percentage(hass, _TEST_FAN, percent)
|
await common.async_set_percentage(hass, _TEST_FAN, percent)
|
||||||
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == percent
|
assert int(float(hass.states.get(_PERCENTAGE_INPUT_NUMBER).state)) == percent
|
||||||
_verify(hass, STATE_ON, percent, None, None, None)
|
_verify(hass, STATE_ON, percent, None, None, "auto")
|
||||||
|
|
||||||
await common.async_turn_off(hass, _TEST_FAN)
|
await common.async_turn_off(hass, _TEST_FAN)
|
||||||
_verify(hass, STATE_OFF, percent, None, None, None)
|
_verify(hass, STATE_OFF, percent, None, None, "auto")
|
||||||
|
|
||||||
preset = "auto"
|
preset = "auto"
|
||||||
await common.async_set_preset_mode(hass, _TEST_FAN, preset)
|
await common.async_set_preset_mode(hass, _TEST_FAN, preset)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from homeassistant.components.fan import (
|
||||||
DOMAIN as FAN_DOMAIN,
|
DOMAIN as FAN_DOMAIN,
|
||||||
SERVICE_SET_PERCENTAGE,
|
SERVICE_SET_PERCENTAGE,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
NotValidPresetModeError,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -179,7 +180,7 @@ async def test_set_invalid_preset_mode(
|
||||||
"""Test set preset mode."""
|
"""Test set preset mode."""
|
||||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN,
|
FAN_DOMAIN,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
@ -189,6 +190,7 @@ async def test_set_invalid_preset_mode(
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
|
|
||||||
async def test_set_preset_mode_exception(
|
async def test_set_preset_mode_exception(
|
||||||
|
|
|
@ -222,10 +222,11 @@ async def test_fan(
|
||||||
|
|
||||||
# set invalid preset_mode from HA
|
# set invalid preset_mode from HA
|
||||||
cluster.write_attributes.reset_mock()
|
cluster.write_attributes.reset_mock()
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await async_set_preset_mode(
|
await async_set_preset_mode(
|
||||||
hass, entity_id, preset_mode="invalid does not exist"
|
hass, entity_id, preset_mode="invalid does not exist"
|
||||||
)
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
assert len(cluster.write_attributes.mock_calls) == 0
|
assert len(cluster.write_attributes.mock_calls) == 0
|
||||||
|
|
||||||
# test adding new fan to the network and HA
|
# test adding new fan to the network and HA
|
||||||
|
@ -624,10 +625,11 @@ async def test_fan_ikea(
|
||||||
|
|
||||||
# set invalid preset_mode from HA
|
# set invalid preset_mode from HA
|
||||||
cluster.write_attributes.reset_mock()
|
cluster.write_attributes.reset_mock()
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await async_set_preset_mode(
|
await async_set_preset_mode(
|
||||||
hass, entity_id, preset_mode="invalid does not exist"
|
hass, entity_id, preset_mode="invalid does not exist"
|
||||||
)
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
assert len(cluster.write_attributes.mock_calls) == 0
|
assert len(cluster.write_attributes.mock_calls) == 0
|
||||||
|
|
||||||
# test adding new fan to the network and HA
|
# test adding new fan to the network and HA
|
||||||
|
@ -813,8 +815,9 @@ async def test_fan_kof(
|
||||||
|
|
||||||
# set invalid preset_mode from HA
|
# set invalid preset_mode from HA
|
||||||
cluster.write_attributes.reset_mock()
|
cluster.write_attributes.reset_mock()
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_AUTO)
|
await async_set_preset_mode(hass, entity_id, preset_mode=PRESET_MODE_AUTO)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
assert len(cluster.write_attributes.mock_calls) == 0
|
assert len(cluster.write_attributes.mock_calls) == 0
|
||||||
|
|
||||||
# test adding new fan to the network and HA
|
# test adding new fan to the network and HA
|
||||||
|
|
|
@ -536,13 +536,14 @@ async def test_inovelli_lzw36(
|
||||||
assert args["value"] == 1
|
assert args["value"] == 1
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
with pytest.raises(NotValidPresetModeError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"fan",
|
"fan",
|
||||||
"turn_on",
|
"turn_on",
|
||||||
{"entity_id": entity_id, "preset_mode": "wheeze"},
|
{"entity_id": entity_id, "preset_mode": "wheeze"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
assert len(client.async_send_command.call_args_list) == 0
|
assert len(client.async_send_command.call_args_list) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -675,13 +676,14 @@ async def test_thermostat_fan(
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
# Test setting unknown preset mode
|
# Test setting unknown preset mode
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(NotValidPresetModeError) as exc:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
FAN_DOMAIN,
|
FAN_DOMAIN,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "Turbo"},
|
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "Turbo"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
assert exc.value.translation_key == "not_valid_preset_mode"
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
|
|
||||||
|
|
64
tests/testing_config/custom_components/test/fan.py
Normal file
64
tests/testing_config/custom_components/test/fan.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
"""Provide a mock fan platform.
|
||||||
|
|
||||||
|
Call init before using it in your tests to ensure clean test data.
|
||||||
|
"""
|
||||||
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
|
from tests.common import MockEntity
|
||||||
|
|
||||||
|
ENTITIES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def init(empty=False):
|
||||||
|
"""Initialize the platform with entities."""
|
||||||
|
global ENTITIES
|
||||||
|
|
||||||
|
ENTITIES = (
|
||||||
|
{}
|
||||||
|
if empty
|
||||||
|
else {
|
||||||
|
"support_preset_mode": MockFan(
|
||||||
|
name="Support fan with preset_mode support",
|
||||||
|
supported_features=FanEntityFeature.PRESET_MODE,
|
||||||
|
unique_id="unique_support_preset_mode",
|
||||||
|
preset_modes=["auto", "eco"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
async_add_entities_callback: AddEntitiesCallback,
|
||||||
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
|
):
|
||||||
|
"""Return mock entities."""
|
||||||
|
async_add_entities_callback(list(ENTITIES.values()))
|
||||||
|
|
||||||
|
|
||||||
|
class MockFan(MockEntity, FanEntity):
|
||||||
|
"""Mock Fan class."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return preset mode."""
|
||||||
|
return self._handle("preset_mode")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self) -> list[str] | None:
|
||||||
|
"""Return preset mode."""
|
||||||
|
return self._handle("preset_modes")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Return the class of this fan."""
|
||||||
|
return self._handle("supported_features")
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set preset mode."""
|
||||||
|
self._attr_preset_mode = preset_mode
|
||||||
|
await self.async_update_ha_state()
|
Loading…
Add table
Reference in a new issue