From 2ebbbcebe96ec577699a7c3ec68f854def00d648 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:21:27 +0200 Subject: [PATCH] Catch all library errors in La Marzocco entity actions (#184057) --- homeassistant/components/lamarzocco/button.py | 4 ++-- homeassistant/components/lamarzocco/number.py | 4 ++-- .../components/lamarzocco/quality_scale.yaml | 5 +---- homeassistant/components/lamarzocco/select.py | 4 ++-- homeassistant/components/lamarzocco/switch.py | 8 ++++---- homeassistant/components/lamarzocco/update.py | 4 ++-- tests/components/lamarzocco/test_button.py | 13 +++++++++++-- tests/components/lamarzocco/test_number.py | 15 +++++++++++---- tests/components/lamarzocco/test_select.py | 13 +++++++++++-- tests/components/lamarzocco/test_switch.py | 15 ++++++++++++--- tests/components/lamarzocco/test_update.py | 13 +++++++++++-- 11 files changed, 69 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/lamarzocco/button.py b/homeassistant/components/lamarzocco/button.py index 3979240614e2..6a2cbde70c08 100644 --- a/homeassistant/components/lamarzocco/button.py +++ b/homeassistant/components/lamarzocco/button.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass from typing import Any, override -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import LaMarzoccoError from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.core import HomeAssistant @@ -75,7 +75,7 @@ class LaMarzoccoButtonEntity(LaMarzoccoEntity, ButtonEntity): """Press button.""" try: await self.entity_description.press_fn(self.coordinator) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="button_error", diff --git a/homeassistant/components/lamarzocco/number.py b/homeassistant/components/lamarzocco/number.py index 7a015b8225d5..0a5435682698 100644 --- a/homeassistant/components/lamarzocco/number.py +++ b/homeassistant/components/lamarzocco/number.py @@ -6,7 +6,7 @@ from typing import Any, cast, override from pylamarzocco import LaMarzoccoMachine from pylamarzocco.const import DoseMode, ModelName, PreExtractionMode, WidgetType -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import LaMarzoccoError from pylamarzocco.models import ( BrewByWeightDoses, CoffeeBoiler, @@ -363,7 +363,7 @@ class LaMarzoccoNumberEntity(LaMarzoccoEntity, NumberEntity): await self.entity_description.set_value_fn( self.coordinator.device, value ) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="number_exception", diff --git a/homeassistant/components/lamarzocco/quality_scale.yaml b/homeassistant/components/lamarzocco/quality_scale.yaml index ec6d3d93e6d4..d7c381256f07 100644 --- a/homeassistant/components/lamarzocco/quality_scale.yaml +++ b/homeassistant/components/lamarzocco/quality_scale.yaml @@ -35,10 +35,7 @@ rules: unique-config-entry: done # Silver - action-exceptions: - status: exempt - comment: | - No custom actions are defined. + action-exceptions: done config-entry-unloading: done docs-configuration-parameters: done docs-installation-parameters: done diff --git a/homeassistant/components/lamarzocco/select.py b/homeassistant/components/lamarzocco/select.py index 3243e17ad023..8f1363ec218d 100644 --- a/homeassistant/components/lamarzocco/select.py +++ b/homeassistant/components/lamarzocco/select.py @@ -13,7 +13,7 @@ from pylamarzocco.const import ( WidgetType, ) from pylamarzocco.devices import LaMarzoccoMachine -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import LaMarzoccoError from pylamarzocco.models import BrewByWeightDoses, PreBrewing, SteamBoilerLevel from homeassistant.components.select import SelectEntity, SelectEntityDescription @@ -198,7 +198,7 @@ class LaMarzoccoSelectEntity(LaMarzoccoEntity, SelectEntity): await self.entity_description.select_option_fn( self.coordinator.device, option ) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="select_option_error", diff --git a/homeassistant/components/lamarzocco/switch.py b/homeassistant/components/lamarzocco/switch.py index 961af718ddde..57b5683425a2 100644 --- a/homeassistant/components/lamarzocco/switch.py +++ b/homeassistant/components/lamarzocco/switch.py @@ -6,7 +6,7 @@ from typing import Any, cast, override from pylamarzocco import LaMarzoccoMachine from pylamarzocco.const import MachineMode, ModelName, WidgetType -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import LaMarzoccoError from pylamarzocco.models import ( MachineStatus, SteamBoilerLevel, @@ -155,7 +155,7 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity): """Turn device on.""" try: await self.entity_description.control_fn(self.coordinator.device, True) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_on_error", @@ -168,7 +168,7 @@ class LaMarzoccoSwitchEntity(LaMarzoccoEntity, SwitchEntity): """Turn device off.""" try: await self.entity_description.control_fn(self.coordinator.device, False) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_off_error", @@ -219,7 +219,7 @@ class LaMarzoccoAutoOnOffSwitchEntity(LaMarzoccoBaseEntity, SwitchEntity): self._schedule_entry.enabled = state try: await self.coordinator.device.set_wakeup_schedule(self._schedule_entry) - except RequestNotSuccessful as exc: + except (LaMarzoccoError, TimeoutError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="auto_on_off_error", diff --git a/homeassistant/components/lamarzocco/update.py b/homeassistant/components/lamarzocco/update.py index 81bf6a83ec9c..0416788795f1 100644 --- a/homeassistant/components/lamarzocco/update.py +++ b/homeassistant/components/lamarzocco/update.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any, override from pylamarzocco.const import FirmwareType, UpdateStatus -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import LaMarzoccoError from homeassistant.components.update import ( UpdateDeviceClass, @@ -138,7 +138,7 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity): await asyncio.sleep(3) counter += 1 - except (TimeoutError, RequestNotSuccessful) as exc: + except (TimeoutError, LaMarzoccoError) as exc: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="update_failed", diff --git a/tests/components/lamarzocco/test_button.py b/tests/components/lamarzocco/test_button.py index 2272829965be..83133db17e49 100644 --- a/tests/components/lamarzocco/test_button.py +++ b/tests/components/lamarzocco/test_button.py @@ -2,7 +2,7 @@ from unittest.mock import AsyncMock, MagicMock, patch -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful import pytest from syrupy.assertion import SnapshotAssertion @@ -50,9 +50,18 @@ async def test_start_backflush( mock_lamarzocco.start_backflush.assert_called_once() +@pytest.mark.parametrize( + "side_effect", + [ + pytest.param(RequestNotSuccessful("Boom"), id="request_not_successful"), + pytest.param(AuthFail("Boom"), id="auth_fail"), + pytest.param(TimeoutError, id="timeout"), + ], +) async def test_button_error( hass: HomeAssistant, mock_lamarzocco: MagicMock, + side_effect: Exception | type[Exception], ) -> None: """Test the La Marzocco button error.""" serial_number = mock_lamarzocco.serial_number @@ -60,7 +69,7 @@ async def test_button_error( state = hass.states.get(f"button.{serial_number}_start_backflush") assert state - mock_lamarzocco.start_backflush.side_effect = RequestNotSuccessful("Boom.") + mock_lamarzocco.start_backflush.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( BUTTON_DOMAIN, diff --git a/tests/components/lamarzocco/test_number.py b/tests/components/lamarzocco/test_number.py index f73ca211f2a8..bc872104690d 100644 --- a/tests/components/lamarzocco/test_number.py +++ b/tests/components/lamarzocco/test_number.py @@ -10,7 +10,7 @@ from pylamarzocco.const import ( SmartStandByType, WidgetType, ) -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful import pytest from syrupy.assertion import SnapshotAssertion @@ -230,11 +230,20 @@ async def test_prebrew_off( ) +@pytest.mark.parametrize( + "side_effect", + [ + pytest.param(RequestNotSuccessful("Boom"), id="request_not_successful"), + pytest.param(AuthFail("Boom"), id="auth_fail"), + pytest.param(TimeoutError, id="timeout"), + ], +) @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_number_error( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, + side_effect: Exception | type[Exception], ) -> None: """Test number entities raise error on service call.""" await async_init_integration(hass, mock_config_entry) @@ -243,9 +252,7 @@ async def test_number_error( state = hass.states.get(f"number.{serial_number}_coffee_target_temperature") assert state - mock_lamarzocco.set_coffee_target_temperature.side_effect = RequestNotSuccessful( - "Boom" - ) + mock_lamarzocco.set_coffee_target_temperature.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( NUMBER_DOMAIN, diff --git a/tests/components/lamarzocco/test_select.py b/tests/components/lamarzocco/test_select.py index 97df0219294d..e9f5f31cd892 100644 --- a/tests/components/lamarzocco/test_select.py +++ b/tests/components/lamarzocco/test_select.py @@ -9,7 +9,7 @@ from pylamarzocco.const import ( SmartStandByType, SteamTargetLevel, ) -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful import pytest from syrupy.assertion import SnapshotAssertion @@ -169,10 +169,19 @@ async def test_smart_standby_mode( ) +@pytest.mark.parametrize( + "side_effect", + [ + pytest.param(RequestNotSuccessful("Boom"), id="request_not_successful"), + pytest.param(AuthFail("Boom"), id="auth_fail"), + pytest.param(TimeoutError, id="timeout"), + ], +) @pytest.mark.usefixtures("init_integration") async def test_select_errors( hass: HomeAssistant, mock_lamarzocco: MagicMock, + side_effect: Exception | type[Exception], ) -> None: """Test select errors.""" serial_number = mock_lamarzocco.serial_number @@ -180,7 +189,7 @@ async def test_select_errors( state = hass.states.get(f"select.{serial_number}_prebrew_infusion_mode") assert state - mock_lamarzocco.set_pre_extraction_mode.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_pre_extraction_mode.side_effect = side_effect # Test setting invalid option with pytest.raises(HomeAssistantError) as exc_info: diff --git a/tests/components/lamarzocco/test_switch.py b/tests/components/lamarzocco/test_switch.py index b65fa543894a..99e34be5862a 100644 --- a/tests/components/lamarzocco/test_switch.py +++ b/tests/components/lamarzocco/test_switch.py @@ -4,7 +4,7 @@ from typing import Any from unittest.mock import MagicMock, patch from pylamarzocco.const import MachineState, SmartStandByType, WidgetType -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful import pytest from syrupy.assertion import SnapshotAssertion @@ -150,10 +150,19 @@ async def test_auto_on_off_switches( mock_lamarzocco.set_wakeup_schedule.assert_called_with(wake_up_sleep_entry) +@pytest.mark.parametrize( + "side_effect", + [ + pytest.param(RequestNotSuccessful("Boom"), id="request_not_successful"), + pytest.param(AuthFail("Boom"), id="auth_fail"), + pytest.param(TimeoutError, id="timeout"), + ], +) async def test_switch_exceptions( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, + side_effect: Exception | type[Exception], ) -> None: """Test the La Marzocco switches.""" await async_init_integration(hass, mock_config_entry) @@ -163,7 +172,7 @@ async def test_switch_exceptions( state = hass.states.get(f"switch.{serial_number}") assert state - mock_lamarzocco.set_power.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_power.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( @@ -190,7 +199,7 @@ async def test_switch_exceptions( state = hass.states.get(f"switch.{serial_number}_auto_on_off_os2oswx") assert state - mock_lamarzocco.set_wakeup_schedule.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.set_wakeup_schedule.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call( SWITCH_DOMAIN, diff --git a/tests/components/lamarzocco/test_update.py b/tests/components/lamarzocco/test_update.py index b1bcbb5e0275..2fc6aa54894e 100644 --- a/tests/components/lamarzocco/test_update.py +++ b/tests/components/lamarzocco/test_update.py @@ -4,7 +4,7 @@ from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch from pylamarzocco.const import FirmwareType, UpdateProgressInfo, UpdateStatus -from pylamarzocco.exceptions import RequestNotSuccessful +from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful from pylamarzocco.models import UpdateDetails import pytest from syrupy.assertion import SnapshotAssertion @@ -100,10 +100,19 @@ async def test_update_process( mock_lamarzocco.update_firmware.assert_called_once_with() +@pytest.mark.parametrize( + "side_effect", + [ + pytest.param(RequestNotSuccessful("Boom"), id="request_not_successful"), + pytest.param(AuthFail("Boom"), id="auth_fail"), + pytest.param(TimeoutError, id="timeout"), + ], +) async def test_update_error( hass: HomeAssistant, mock_lamarzocco: MagicMock, mock_config_entry: MockConfigEntry, + side_effect: Exception | type[Exception], ) -> None: """Test error during update.""" @@ -112,7 +121,7 @@ async def test_update_error( state = hass.states.get(f"update.{mock_lamarzocco.serial_number}_gateway_firmware") assert state - mock_lamarzocco.update_firmware.side_effect = RequestNotSuccessful("Boom") + mock_lamarzocco.update_firmware.side_effect = side_effect with pytest.raises(HomeAssistantError) as exc_info: await hass.services.async_call(