From 178ab42316b74829af2a0aea4b29af0b72538a2c Mon Sep 17 00:00:00 2001 From: Abdellatif Anaflous <62770500+hktitof@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:34:06 +0100 Subject: [PATCH] Fix swallowed exceptions in pi_hole action handlers (#181574) --- homeassistant/components/pi_hole/strings.json | 8 ++++ homeassistant/components/pi_hole/switch.py | 17 +++++--- tests/components/pi_hole/test_init.py | 42 +++++++++++-------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/pi_hole/strings.json b/homeassistant/components/pi_hole/strings.json index 6e649daa15cd..141503a9eef2 100644 --- a/homeassistant/components/pi_hole/strings.json +++ b/homeassistant/components/pi_hole/strings.json @@ -94,6 +94,14 @@ } } }, + "exceptions": { + "disable_failed": { + "message": "Failed to disable Pi-hole: {error}" + }, + "enable_failed": { + "message": "Failed to enable Pi-hole: {error}" + } + }, "issues": { "v5_to_v6_migration": { "description": "You've likely updated your Pi-hole to API v6 from v5. Some sensors changed in the new API, the daily sensors were removed, and your old API token is invalid. Provide your new app password by re-authenticating in repairs or in **Settings -> Devices & services -> Pi-hole**.", diff --git a/homeassistant/components/pi_hole/switch.py b/homeassistant/components/pi_hole/switch.py index f57356fbb6c2..9e2e4d6d6f26 100644 --- a/homeassistant/components/pi_hole/switch.py +++ b/homeassistant/components/pi_hole/switch.py @@ -8,10 +8,11 @@ import voluptuous as vol from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import SERVICE_DISABLE, SERVICE_DISABLE_ATTR_DURATION +from .const import DOMAIN, SERVICE_DISABLE, SERVICE_DISABLE_ATTR_DURATION from .coordinator import PiHoleConfigEntry from .entity import PiHoleEntity @@ -78,9 +79,12 @@ class PiHoleSwitch(PiHoleEntity, SwitchEntity): try: await self.api.enable() await self.async_update() - # pylint: disable-next=home-assistant-action-swallowed-exception except HoleError as err: - _LOGGER.error("Unable to enable Pi-hole: %s", err) + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="enable_failed", + translation_placeholders={"error": str(err)}, + ) from err @override async def async_turn_off(self, **kwargs: Any) -> None: @@ -101,6 +105,9 @@ class PiHoleSwitch(PiHoleEntity, SwitchEntity): try: await self.api.disable(duration_seconds) await self.async_update() - # pylint: disable-next=home-assistant-action-swallowed-exception except HoleError as err: - _LOGGER.error("Unable to disable Pi-hole: %s", err) + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="disable_failed", + translation_placeholders={"error": str(err)}, + ) from err diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index 827a5e17c014..09da74466b2f 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -1,6 +1,5 @@ """Test pi_hole component.""" -import logging from unittest.mock import ANY, AsyncMock from hole.exceptions import HoleError @@ -23,6 +22,7 @@ from homeassistant.const import ( CONF_SSL, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from . import ( API_KEY, @@ -289,7 +289,7 @@ async def test_setup_name_from_entry_title(hass: HomeAssistant) -> None: assert hass.states.get("sensor.my_hole_ads_blocked").name == "My Hole Ads blocked" -async def test_switch(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: +async def test_switch(hass: HomeAssistant) -> None: """Test Pi-hole switch.""" mocked_hole = _create_mocked_hole() entry = MockConfigEntry( @@ -322,23 +322,29 @@ async def test_switch(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> # Failed calls mocked_hole.instances[-1].enable = AsyncMock(side_effect=HoleError("Error1")) - await hass.services.async_call( - switch.DOMAIN, - switch.SERVICE_TURN_ON, - {"entity_id": SWITCH_ENTITY_ID}, - blocking=True, - ) - mocked_hole.instances[-1].disable = AsyncMock(side_effect=HoleError("Error2")) - await hass.services.async_call( - switch.DOMAIN, - switch.SERVICE_TURN_OFF, - {"entity_id": SWITCH_ENTITY_ID}, - blocking=True, - ) - errors = [x for x in caplog.records if x.levelno == logging.ERROR] + with pytest.raises(HomeAssistantError) as enable_error: + await hass.services.async_call( + switch.DOMAIN, + switch.SERVICE_TURN_ON, + {"entity_id": SWITCH_ENTITY_ID}, + blocking=True, + ) - assert errors[-2].message == "Unable to enable Pi-hole: Error1" - assert errors[-1].message == "Unable to disable Pi-hole: Error2" + mocked_hole.instances[-1].disable = AsyncMock(side_effect=HoleError("Error2")) + with pytest.raises(HomeAssistantError) as disable_error: + await hass.services.async_call( + switch.DOMAIN, + switch.SERVICE_TURN_OFF, + {"entity_id": SWITCH_ENTITY_ID}, + blocking=True, + ) + + assert enable_error.value.translation_domain == pi_hole.DOMAIN + assert enable_error.value.translation_key == "enable_failed" + assert enable_error.value.translation_placeholders == {"error": "Error1"} + assert disable_error.value.translation_domain == pi_hole.DOMAIN + assert disable_error.value.translation_key == "disable_failed" + assert disable_error.value.translation_placeholders == {"error": "Error2"} async def test_disable_service_call(hass: HomeAssistant) -> None: