From 6f6d4855307f000b93bec278a0876d2a452fb846 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:12:21 +0000 Subject: [PATCH] Raise HomeAssistantError from tplink light effect service (#135081) --- homeassistant/components/tplink/light.py | 28 ++++++- homeassistant/components/tplink/strings.json | 3 + tests/components/tplink/test_light.py | 83 ++++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index c95b5086e3e..e65fda52e44 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -6,7 +6,7 @@ from collections.abc import Sequence import logging from typing import Any -from kasa import Device, DeviceType, LightState, Module +from kasa import Device, DeviceType, KasaException, LightState, Module from kasa.interfaces import Light, LightEffect from kasa.iot import IotDevice import voluptuous as vol @@ -24,12 +24,14 @@ from homeassistant.components.light import ( filter_supported_color_modes, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import VolDictType from . import TPLinkConfigEntry, legacy_device_id +from .const import DOMAIN from .coordinator import TPLinkDataUpdateCoordinator from .entity import CoordinatedTPLinkEntity, async_refresh_after @@ -462,7 +464,17 @@ class TPLinkLightEffectEntity(TPLinkLightEntity): if transition_range: effect["transition_range"] = transition_range effect["transition"] = 0 - await self._effect_module.set_custom_effect(effect) + try: + await self._effect_module.set_custom_effect(effect) + except KasaException as ex: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="set_custom_effect", + translation_placeholders={ + "effect": str(effect), + "exc": str(ex), + }, + ) from ex async def async_set_sequence_effect( self, @@ -484,4 +496,14 @@ class TPLinkLightEffectEntity(TPLinkLightEntity): "spread": spread, "direction": direction, } - await self._effect_module.set_custom_effect(effect) + try: + await self._effect_module.set_custom_effect(effect) + except KasaException as ex: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="set_custom_effect", + translation_placeholders={ + "effect": str(effect), + "exc": str(ex), + }, + ) from ex diff --git a/homeassistant/components/tplink/strings.json b/homeassistant/components/tplink/strings.json index 185705ff163..9cf302ed717 100644 --- a/homeassistant/components/tplink/strings.json +++ b/homeassistant/components/tplink/strings.json @@ -394,6 +394,9 @@ }, "device_authentication": { "message": "Device authentication error {func}: {exc}" + }, + "set_custom_effect": { + "message": "Error trying to set custom effect {effect}: {exc}" } }, "issues": { diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 6549711b7fc..e19f2e11a40 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta +import re from unittest.mock import MagicMock, PropertyMock from freezegun.api import FrozenDateTimeFactory @@ -36,6 +37,10 @@ from homeassistant.components.light import ( EFFECT_OFF, ) from homeassistant.components.tplink.const import DOMAIN +from homeassistant.components.tplink.light import ( + SERVICE_RANDOM_EFFECT, + SERVICE_SEQUENCE_EFFECT, +) from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import ( ATTR_ENTITY_ID, @@ -839,6 +844,84 @@ async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None: light_effect.set_custom_effect.reset_mock() +@pytest.mark.parametrize( + ("service_name", "service_params", "expected_extra_params"), + [ + pytest.param( + SERVICE_SEQUENCE_EFFECT, + { + "sequence": [[340, 20, 50], [20, 50, 50], [0, 100, 50]], + }, + { + "type": "sequence", + "sequence": [(340, 20, 50), (20, 50, 50), (0, 100, 50)], + "repeat_times": 0, + "spread": 1, + "direction": 4, + }, + id="sequence", + ), + pytest.param( + SERVICE_RANDOM_EFFECT, + {"init_states": [340, 20, 50]}, + {"type": "random", "init_states": [[340, 20, 50]], "random_seed": 100}, + id="random", + ), + ], +) +async def test_smart_strip_effect_service_error( + hass: HomeAssistant, + service_name: str, + service_params: dict, + expected_extra_params: dict, +) -> None: + """Test smart strip custom random effects.""" + already_migrated_config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS + ) + already_migrated_config_entry.add_to_hass(hass) + device = _mocked_device( + modules=[Module.Light, Module.LightEffect], alias="my_light" + ) + light_effect = device.modules[Module.LightEffect] + + with _patch_discovery(device=device), _patch_connect(device=device): + await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) + await hass.async_block_till_done() + + entity_id = "light.my_light" + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + light_effect.set_custom_effect.side_effect = KasaException("failed") + + base = { + "custom": 1, + "id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN", + "brightness": 100, + "name": "Custom", + "segments": [0], + "expansion_strategy": 1, + "enable": 1, + "duration": 0, + "transition": 0, + } + expected_params = {**base, **expected_extra_params} + expected_msg = f"Error trying to set custom effect {expected_params}: failed" + + with pytest.raises(HomeAssistantError, match=re.escape(expected_msg)): + await hass.services.async_call( + DOMAIN, + service_name, + { + ATTR_ENTITY_ID: entity_id, + **service_params, + }, + blocking=True, + ) + + async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> None: """Test smart strip custom random effects at startup.""" already_migrated_config_entry = MockConfigEntry(