Fix - only enable AlexaModeController if at least one mode is offered (#148614)

This commit is contained in:
Jan Bouwhuis 2025-07-14 11:25:22 +02:00 committed by GitHub
parent 334d5f09fb
commit ad4e5459b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 183 additions and 5 deletions

View File

@ -505,8 +505,13 @@ class ClimateCapabilities(AlexaEntity):
): ):
yield AlexaThermostatController(self.hass, self.entity) yield AlexaThermostatController(self.hass, self.entity)
yield AlexaTemperatureSensor(self.hass, self.entity) yield AlexaTemperatureSensor(self.hass, self.entity)
if self.entity.domain == water_heater.DOMAIN and ( if (
supported_features & water_heater.WaterHeaterEntityFeature.OPERATION_MODE self.entity.domain == water_heater.DOMAIN
and (
supported_features
& water_heater.WaterHeaterEntityFeature.OPERATION_MODE
)
and self.entity.attributes.get(water_heater.ATTR_OPERATION_LIST)
): ):
yield AlexaModeController( yield AlexaModeController(
self.entity, self.entity,
@ -634,7 +639,9 @@ class FanCapabilities(AlexaEntity):
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
) )
force_range_controller = False force_range_controller = False
if supported & fan.FanEntityFeature.PRESET_MODE: if supported & fan.FanEntityFeature.PRESET_MODE and self.entity.attributes.get(
fan.ATTR_PRESET_MODES
):
yield AlexaModeController( yield AlexaModeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}" self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}"
) )
@ -672,7 +679,11 @@ class RemoteCapabilities(AlexaEntity):
yield AlexaPowerController(self.entity) yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or [] activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
if activities and supported & remote.RemoteEntityFeature.ACTIVITY: if (
activities
and (supported & remote.RemoteEntityFeature.ACTIVITY)
and self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST)
):
yield AlexaModeController( yield AlexaModeController(
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}" self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
) )
@ -692,7 +703,9 @@ class HumidifierCapabilities(AlexaEntity):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
yield AlexaPowerController(self.entity) yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & humidifier.HumidifierEntityFeature.MODES: if (
supported & humidifier.HumidifierEntityFeature.MODES
) and self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES):
yield AlexaModeController( yield AlexaModeController(
self.entity, instance=f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}" self.entity, instance=f"{humidifier.DOMAIN}.{humidifier.ATTR_MODE}"
) )

View File

@ -5,6 +5,7 @@ from unittest.mock import patch
import pytest import pytest
from homeassistant.components import fan, humidifier, remote, water_heater
from homeassistant.components.alexa import smart_home from homeassistant.components.alexa import smart_home
from homeassistant.const import EntityCategory, UnitOfTemperature, __version__ from homeassistant.const import EntityCategory, UnitOfTemperature, __version__
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -200,3 +201,167 @@ async def test_serialize_discovery_recovers(
"Error serializing Alexa.PowerController discovery" "Error serializing Alexa.PowerController discovery"
f" for {hass.states.get('switch.bla')}" f" for {hass.states.get('switch.bla')}"
) in caplog.text ) in caplog.text
@pytest.mark.parametrize(
("domain", "state", "state_attributes", "mode_controller_exists"),
[
("switch", "on", {}, False),
(
"fan",
"on",
{
"preset_modes": ["eco", "auto"],
"preset_mode": "eco",
"supported_features": fan.FanEntityFeature.PRESET_MODE.value,
},
True,
),
(
"fan",
"on",
{
"preset_modes": ["eco", "auto"],
"preset_mode": None,
"supported_features": fan.FanEntityFeature.PRESET_MODE.value,
},
True,
),
(
"fan",
"on",
{
"preset_modes": ["eco"],
"preset_mode": None,
"supported_features": fan.FanEntityFeature.PRESET_MODE.value,
},
True,
),
(
"fan",
"on",
{
"preset_modes": [],
"preset_mode": None,
"supported_features": fan.FanEntityFeature.PRESET_MODE.value,
},
False,
),
(
"humidifier",
"on",
{
"available_modes": ["auto", "manual"],
"mode": "auto",
"supported_features": humidifier.HumidifierEntityFeature.MODES.value,
},
True,
),
(
"humidifier",
"on",
{
"available_modes": ["auto"],
"mode": None,
"supported_features": humidifier.HumidifierEntityFeature.MODES.value,
},
True,
),
(
"humidifier",
"on",
{
"available_modes": [],
"mode": None,
"supported_features": humidifier.HumidifierEntityFeature.MODES.value,
},
False,
),
(
"remote",
"on",
{
"activity_list": ["tv", "dvd"],
"current_activity": "tv",
"supported_features": remote.RemoteEntityFeature.ACTIVITY.value,
},
True,
),
(
"remote",
"on",
{
"activity_list": ["tv"],
"current_activity": None,
"supported_features": remote.RemoteEntityFeature.ACTIVITY.value,
},
True,
),
(
"remote",
"on",
{
"activity_list": [],
"current_activity": None,
"supported_features": remote.RemoteEntityFeature.ACTIVITY.value,
},
False,
),
(
"water_heater",
"on",
{
"operation_list": ["on", "auto"],
"operation_mode": "auto",
"supported_features": water_heater.WaterHeaterEntityFeature.OPERATION_MODE.value,
},
True,
),
(
"water_heater",
"on",
{
"operation_list": ["on"],
"operation_mode": None,
"supported_features": water_heater.WaterHeaterEntityFeature.OPERATION_MODE.value,
},
True,
),
(
"water_heater",
"on",
{
"operation_list": [],
"operation_mode": None,
"supported_features": water_heater.WaterHeaterEntityFeature.OPERATION_MODE.value,
},
False,
),
],
)
async def test_mode_controller_is_omitted_if_no_modes_are_set(
hass: HomeAssistant,
domain: str,
state: str,
state_attributes: dict[str, Any],
mode_controller_exists: bool,
) -> None:
"""Test we do not generate an invalid discovery with AlexaModeController during serialize discovery.
AlexModeControllers need at least 2 modes. If one mode is set, an extra mode will be added for compatibility.
If no modes are offered, the mode controller should be omitted to prevent schema validations.
"""
request = get_new_request("Alexa.Discovery", "Discover")
hass.states.async_set(
f"{domain}.bla", state, {"friendly_name": "Boop Woz"} | state_attributes
)
msg = await smart_home.async_handle_message(hass, get_default_config(hass), request)
msg = msg["event"]
interfaces = {
ifc["interface"] for ifc in msg["payload"]["endpoints"][0]["capabilities"]
}
assert ("Alexa.ModeController" in interfaces) is mode_controller_exists