From b37f49658ab6447f73fb9be6187b4ca963254983 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Mon, 7 Sep 2026 11:43:03 +0200 Subject: [PATCH] Handle errors in easyEnergy price actions (#181434) --- .../components/easyenergy/services.py | 37 ++++++---- .../components/easyenergy/strings.json | 3 + tests/components/easyenergy/test_services.py | 68 ++++++++++++++++++- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/easyenergy/services.py b/homeassistant/components/easyenergy/services.py index 19a2eb1235e1..ad7cb7f5cea0 100644 --- a/homeassistant/components/easyenergy/services.py +++ b/homeassistant/components/easyenergy/services.py @@ -6,6 +6,7 @@ from functools import partial from typing import Final from easyenergy import ( + EasyEnergyError, Electricity, ElectricityGranularity, ElectricityPriceType, @@ -23,7 +24,7 @@ from homeassistant.core import ( SupportsResponse, callback, ) -from homeassistant.exceptions import ServiceValidationError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import selector, service from homeassistant.util import dt as dt_util @@ -192,21 +193,33 @@ async def __get_prices( prices: list[dict[str, float | datetime]] if service_price_type == ServicePriceType.GAS: - data = await coordinator.easyenergy.gas_prices( - start_date=start_date, - end_date=end_date, - vat=vat, - ) + try: + data = await coordinator.easyenergy.gas_prices( + start_date=start_date, + end_date=end_date, + vat=vat, + ) + except EasyEnergyError as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="fetch_prices_error", + ) from err prices = __select_prices( data, call.data[ATTR_PRICE_TYPE] == ElectricityPriceType.INVOICE.value ) else: - data = await coordinator.easyenergy.energy_prices( - start_date=start_date, - end_date=end_date, - granularity=ElectricityGranularity(call.data[ATTR_GRANULARITY]), - vat=vat, - ) + try: + data = await coordinator.easyenergy.energy_prices( + start_date=start_date, + end_date=end_date, + granularity=ElectricityGranularity(call.data[ATTR_GRANULARITY]), + vat=vat, + ) + except EasyEnergyError as err: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="fetch_prices_error", + ) from err if service_price_type == ServicePriceType.ENERGY_USAGE: prices = __select_prices( diff --git a/homeassistant/components/easyenergy/strings.json b/homeassistant/components/easyenergy/strings.json index 06d6ab25da82..cab60617ea96 100644 --- a/homeassistant/components/easyenergy/strings.json +++ b/homeassistant/components/easyenergy/strings.json @@ -50,6 +50,9 @@ "connection_error": { "message": "Error communicating with the easyEnergy API." }, + "fetch_prices_error": { + "message": "Error fetching prices from the easyEnergy API." + }, "invalid_date": { "message": "Invalid date provided. Got {date}" } diff --git a/tests/components/easyenergy/test_services.py b/tests/components/easyenergy/test_services.py index d2121a600a9f..652fe384cc9a 100644 --- a/tests/components/easyenergy/test_services.py +++ b/tests/components/easyenergy/test_services.py @@ -3,7 +3,13 @@ from datetime import date from unittest.mock import MagicMock -from easyenergy import ElectricityGranularity, VatOption +from easyenergy import ( + EasyEnergyConnectionError, + EasyEnergyError, + EasyEnergyNoDataError, + ElectricityGranularity, + VatOption, +) import pytest from syrupy.assertion import SnapshotAssertion import voluptuous as vol @@ -18,7 +24,7 @@ from homeassistant.components.easyenergy.services import ( GAS_SERVICE_NAME, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ServiceValidationError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from tests.common import MockConfigEntry @@ -459,6 +465,7 @@ async def test_service_validation_config_entry_not_found( async def test_service_validation_invalid_date( hass: HomeAssistant, mock_config_entry: MockConfigEntry, + mock_easyenergy: MagicMock, service: str, date_field: str, date_value: str, @@ -471,6 +478,8 @@ async def test_service_validation_invalid_date( if service != ENERGY_RETURN_SERVICE_NAME: service_data["incl_vat"] = True + mock_easyenergy.reset_mock() + with pytest.raises(ServiceValidationError) as err: await hass.services.async_call( DOMAIN, @@ -483,3 +492,58 @@ async def test_service_validation_invalid_date( assert str(err.value) == f"Invalid date provided. Got {date_value}" assert err.value.translation_key == "invalid_date" assert err.value.translation_placeholders == {"date": date_value} + mock_easyenergy.gas_prices.assert_not_awaited() + mock_easyenergy.energy_prices.assert_not_awaited() + + +@pytest.mark.usefixtures("init_integration") +@pytest.mark.parametrize( + ("service", "method", "service_data"), + [ + (GAS_SERVICE_NAME, "gas_prices", {"incl_vat": True}), + (ENERGY_USAGE_SERVICE_NAME, "energy_prices", {"incl_vat": True}), + (ENERGY_RETURN_SERVICE_NAME, "energy_prices", {}), + ], +) +@pytest.mark.parametrize( + "exception", + [ + pytest.param( + EasyEnergyError("Unexpected response", {"response": "raw API data"}), + id="api_error", + ), + pytest.param( + EasyEnergyConnectionError("Connection failed"), id="connection_error" + ), + pytest.param(EasyEnergyNoDataError("No prices found"), id="no_data"), + ], +) +async def test_service_api_error( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_easyenergy: MagicMock, + service: str, + method: str, + service_data: dict[str, bool], + exception: EasyEnergyError, +) -> None: + """Test API failures raise translated execution errors for every action.""" + mock_method = getattr(mock_easyenergy, method) + mock_method.reset_mock() + mock_method.side_effect = exception + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + DOMAIN, + service, + {ATTR_CONFIG_ENTRY: mock_config_entry.entry_id} | service_data, + blocking=True, + return_response=True, + ) + + assert not isinstance(err.value, ServiceValidationError) + assert err.value.translation_domain == DOMAIN + assert err.value.translation_key == "fetch_prices_error" + assert str(err.value) == "Error fetching prices from the easyEnergy API" + assert err.value.__cause__ is exception + mock_method.assert_awaited_once()