From 927813ab3bfc08be397105a649c479e1b54f4957 Mon Sep 17 00:00:00 2001 From: IceBotYT <34712694+IceBotYT@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:36:10 -0400 Subject: [PATCH] Raise HomeAssistantError in entity action calls in Nice G.O. (#126439) --- homeassistant/components/nice_go/cover.py | 23 ++++++++- homeassistant/components/nice_go/light.py | 23 ++++++++- homeassistant/components/nice_go/strings.json | 20 ++++++++ homeassistant/components/nice_go/switch.py | 25 +++++++++- tests/components/nice_go/test_cover.py | 47 +++++++++++++++++++ tests/components/nice_go/test_light.py | 46 ++++++++++++++++++ tests/components/nice_go/test_switch.py | 47 +++++++++++++++++++ 7 files changed, 225 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/nice_go/cover.py b/homeassistant/components/nice_go/cover.py index 4098d9ef426..7ded43de165 100644 --- a/homeassistant/components/nice_go/cover.py +++ b/homeassistant/components/nice_go/cover.py @@ -2,15 +2,20 @@ from typing import Any +from aiohttp import ClientError +from nice_go import ApiError + from homeassistant.components.cover import ( CoverDeviceClass, CoverEntity, CoverEntityFeature, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import NiceGOConfigEntry +from .const import DOMAIN from .entity import NiceGOEntity PARALLEL_UPDATES = 1 @@ -62,11 +67,25 @@ class NiceGOCoverEntity(NiceGOEntity, CoverEntity): if self.is_closed: return - await self.coordinator.api.close_barrier(self._device_id) + try: + await self.coordinator.api.close_barrier(self._device_id) + except (ApiError, ClientError) as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="close_cover_error", + translation_placeholders={"exception": str(err)}, + ) from err async def async_open_cover(self, **kwargs: Any) -> None: """Open the garage door.""" if self.is_opened: return - await self.coordinator.api.open_barrier(self._device_id) + try: + await self.coordinator.api.open_barrier(self._device_id) + except (ApiError, ClientError) as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="open_cover_error", + translation_placeholders={"exception": str(err)}, + ) from err diff --git a/homeassistant/components/nice_go/light.py b/homeassistant/components/nice_go/light.py index aa606dbcb8f..6b5f5cd39ee 100644 --- a/homeassistant/components/nice_go/light.py +++ b/homeassistant/components/nice_go/light.py @@ -2,11 +2,16 @@ from typing import TYPE_CHECKING, Any +from aiohttp import ClientError +from nice_go import ApiError + from homeassistant.components.light import ColorMode, LightEntity from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import NiceGOConfigEntry +from .const import DOMAIN from .entity import NiceGOEntity @@ -43,9 +48,23 @@ class NiceGOLightEntity(NiceGOEntity, LightEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - await self.coordinator.api.light_on(self._device_id) + try: + await self.coordinator.api.light_on(self._device_id) + except (ApiError, ClientError) as error: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="light_on_error", + translation_placeholders={"exception": str(error)}, + ) from error async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - await self.coordinator.api.light_off(self._device_id) + try: + await self.coordinator.api.light_off(self._device_id) + except (ApiError, ClientError) as error: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="light_off_error", + translation_placeholders={"exception": str(error)}, + ) from error diff --git a/homeassistant/components/nice_go/strings.json b/homeassistant/components/nice_go/strings.json index f83207ad977..07dabf7d39f 100644 --- a/homeassistant/components/nice_go/strings.json +++ b/homeassistant/components/nice_go/strings.json @@ -53,5 +53,25 @@ "title": "Firmware update required", "description": "Your device ({device_name}) requires a firmware update on the Nice G.O. app in order to work with this integration. Please update the firmware on the Nice G.O. app and reconfigure this integration." } + }, + "exceptions": { + "close_cover_error": { + "message": "Error closing the barrier: {exception}" + }, + "open_cover_error": { + "message": "Error opening the barrier: {exception}" + }, + "light_on_error": { + "message": "Error while turning on the light: {exception}" + }, + "light_off_error": { + "message": "Error while turning off the light: {exception}" + }, + "switch_on_error": { + "message": "Error while turning on the switch: {exception}" + }, + "switch_off_error": { + "message": "Error while turning off the switch: {exception}" + } } } diff --git a/homeassistant/components/nice_go/switch.py b/homeassistant/components/nice_go/switch.py index 26d42dab124..a74a18328c9 100644 --- a/homeassistant/components/nice_go/switch.py +++ b/homeassistant/components/nice_go/switch.py @@ -5,11 +5,16 @@ from __future__ import annotations import logging from typing import Any +from aiohttp import ClientError +from nice_go import ApiError + from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import NiceGOConfigEntry +from .const import DOMAIN from .entity import NiceGOEntity _LOGGER = logging.getLogger(__name__) @@ -42,8 +47,24 @@ class NiceGOSwitchEntity(NiceGOEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - await self.coordinator.api.vacation_mode_on(self.data.id) + + try: + await self.coordinator.api.vacation_mode_on(self.data.id) + except (ApiError, ClientError) as error: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="switch_on_error", + translation_placeholders={"exception": str(error)}, + ) from error async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - await self.coordinator.api.vacation_mode_off(self.data.id) + + try: + await self.coordinator.api.vacation_mode_off(self.data.id) + except (ApiError, ClientError) as error: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="switch_off_error", + translation_placeholders={"exception": str(error)}, + ) from error diff --git a/tests/components/nice_go/test_cover.py b/tests/components/nice_go/test_cover.py index a6eb9bd27fb..737fa104d0c 100644 --- a/tests/components/nice_go/test_cover.py +++ b/tests/components/nice_go/test_cover.py @@ -2,7 +2,10 @@ from unittest.mock import AsyncMock +from aiohttp import ClientError from freezegun.api import FrozenDateTimeFactory +from nice_go import ApiError +import pytest from syrupy import SnapshotAssertion from homeassistant.components.cover import ( @@ -20,6 +23,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration @@ -113,3 +117,46 @@ async def test_update_cover_state( assert hass.states.get("cover.test_garage_1").state == STATE_OPENING assert hass.states.get("cover.test_garage_2").state == STATE_CLOSING + + +@pytest.mark.parametrize( + ("action", "error", "entity_id", "expected_error"), + [ + ( + SERVICE_OPEN_COVER, + ApiError, + "cover.test_garage_1", + "Error opening the barrier", + ), + ( + SERVICE_CLOSE_COVER, + ClientError, + "cover.test_garage_2", + "Error closing the barrier", + ), + ], +) +async def test_cover_exceptions( + hass: HomeAssistant, + mock_nice_go: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, + action: str, + error: Exception, + entity_id: str, + expected_error: str, +) -> None: + """Test that closing the cover works as intended.""" + + await setup_integration(hass, mock_config_entry, [Platform.COVER]) + + mock_nice_go.open_barrier.side_effect = error + mock_nice_go.close_barrier.side_effect = error + + with pytest.raises(HomeAssistantError, match=expected_error): + await hass.services.async_call( + COVER_DOMAIN, + action, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) diff --git a/tests/components/nice_go/test_light.py b/tests/components/nice_go/test_light.py index 9c860c0225f..f7aa015c3bd 100644 --- a/tests/components/nice_go/test_light.py +++ b/tests/components/nice_go/test_light.py @@ -2,6 +2,9 @@ from unittest.mock import AsyncMock +from aiohttp import ClientError +from nice_go import ApiError +import pytest from syrupy import SnapshotAssertion from homeassistant.components.light import ( @@ -12,6 +15,7 @@ from homeassistant.components.light import ( from homeassistant.components.nice_go.const import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration @@ -88,3 +92,45 @@ async def test_update_light_state( assert hass.states.get("light.test_garage_1_light").state == STATE_OFF assert hass.states.get("light.test_garage_2_light").state == STATE_ON assert hass.states.get("light.test_garage_3_light") is None + + +@pytest.mark.parametrize( + ("action", "error", "entity_id", "expected_error"), + [ + ( + SERVICE_TURN_OFF, + ApiError, + "light.test_garage_1_light", + "Error while turning off the light", + ), + ( + SERVICE_TURN_ON, + ClientError, + "light.test_garage_2_light", + "Error while turning on the light", + ), + ], +) +async def test_error( + hass: HomeAssistant, + mock_nice_go: AsyncMock, + mock_config_entry: MockConfigEntry, + action: str, + error: Exception, + entity_id: str, + expected_error: str, +) -> None: + """Test that errors are handled appropriately.""" + + await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) + + mock_nice_go.light_on.side_effect = error + mock_nice_go.light_off.side_effect = error + + with pytest.raises(HomeAssistantError, match=expected_error): + await hass.services.async_call( + LIGHT_DOMAIN, + action, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) diff --git a/tests/components/nice_go/test_switch.py b/tests/components/nice_go/test_switch.py index f34cba495c9..d3a2141eb2b 100644 --- a/tests/components/nice_go/test_switch.py +++ b/tests/components/nice_go/test_switch.py @@ -2,6 +2,10 @@ from unittest.mock import AsyncMock +from aiohttp import ClientError +from nice_go import ApiError +import pytest + from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -9,6 +13,7 @@ from homeassistant.components.switch import ( ) from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from . import setup_integration @@ -41,3 +46,45 @@ async def test_turn_off( blocking=True, ) mock_nice_go.vacation_mode_off.assert_called_once_with("2") + + +@pytest.mark.parametrize( + ("action", "error", "entity_id", "expected_error"), + [ + ( + SERVICE_TURN_OFF, + ApiError, + "switch.test_garage_1_vacation_mode", + "Error while turning off the switch", + ), + ( + SERVICE_TURN_ON, + ClientError, + "switch.test_garage_2_vacation_mode", + "Error while turning on the switch", + ), + ], +) +async def test_error( + hass: HomeAssistant, + mock_nice_go: AsyncMock, + mock_config_entry: MockConfigEntry, + action: str, + error: Exception, + entity_id: str, + expected_error: str, +) -> None: + """Test that errors are handled appropriately.""" + + await setup_integration(hass, mock_config_entry, [Platform.SWITCH]) + + mock_nice_go.vacation_mode_on.side_effect = error + mock_nice_go.vacation_mode_off.side_effect = error + + with pytest.raises(HomeAssistantError, match=expected_error): + await hass.services.async_call( + SWITCH_DOMAIN, + action, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + )