From e5cd1e28307e5dc409af872ed8077b03bba3eae1 Mon Sep 17 00:00:00 2001 From: fender4645 Date: Thu, 23 Apr 2026 13:47:15 -0700 Subject: [PATCH] Tessie: log warning instead of raising UpdateFailed for missing energy history (#168068) Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/tessie/coordinator.py | 7 ++- homeassistant/components/tessie/sensor.py | 2 +- tests/components/tessie/test_coordinator.py | 56 ++++++++++++++++--- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/tessie/coordinator.py b/homeassistant/components/tessie/coordinator.py index cbb5d1d27bf4..b0dbf32ba76d 100644 --- a/homeassistant/components/tessie/coordinator.py +++ b/homeassistant/components/tessie/coordinator.py @@ -224,10 +224,11 @@ class TessieEnergyHistoryCoordinator(DataUpdateCoordinator[dict[str, Any]]): or not isinstance(data.get("time_series"), list) or not data["time_series"] ): - raise UpdateFailed( - translation_domain=DOMAIN, - translation_key="invalid_energy_history_data", + _LOGGER.warning( + "Tessie returned no energy history time_series for coordinator %s; skipping update", + self.config_entry.entry_id, ) + return self.data time_series = data["time_series"] output: dict[str, Any] = {} diff --git a/homeassistant/components/tessie/sensor.py b/homeassistant/components/tessie/sensor.py index 18ea9afc90fd..f4cd3ef8e398 100644 --- a/homeassistant/components/tessie/sensor.py +++ b/homeassistant/components/tessie/sensor.py @@ -638,4 +638,4 @@ class TessieEnergyHistorySensorEntity(TessieEnergyHistoryEntity, SensorEntity): """Update the attributes of the sensor.""" self._attr_available = self._value is not None self._attr_native_value = self._value - self._attr_last_reset = self.coordinator.data["_period_start"] + self._attr_last_reset = self.coordinator.data.get("_period_start") diff --git a/tests/components/tessie/test_coordinator.py b/tests/components/tessie/test_coordinator.py index 91b7c2331356..271d2f489fc8 100644 --- a/tests/components/tessie/test_coordinator.py +++ b/tests/components/tessie/test_coordinator.py @@ -1,5 +1,6 @@ """Test the Tessie sensor platform.""" +from copy import deepcopy from datetime import timedelta from freezegun.api import FrozenDateTimeFactory @@ -14,11 +15,17 @@ from homeassistant.components.tessie.coordinator import ( TESSIE_SYNC_INTERVAL, ) from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, Platform +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import UpdateFailed -from .common import ERROR_AUTH, ERROR_CONNECTION, ERROR_UNKNOWN, setup_platform +from .common import ( + ENERGY_HISTORY, + ERROR_AUTH, + ERROR_CONNECTION, + ERROR_UNKNOWN, + setup_platform, +) from tests.common import async_fire_time_changed @@ -184,21 +191,52 @@ async def test_coordinator_energy_history_error( async def test_coordinator_energy_history_invalid_data( hass: HomeAssistant, mock_energy_history, freezer: FrozenDateTimeFactory ) -> None: - """Tests that the energy history coordinator handles invalid data.""" + """Tests that the energy history coordinator handles invalid data gracefully.""" entry = await setup_platform(hass, [Platform.SENSOR]) coordinator = entry.runtime_data.energysites[0].history_coordinator assert coordinator is not None + # Capture state after successful initial load + state_before = hass.states.get("sensor.energy_site_grid_imported").state + mock_energy_history.reset_mock() mock_energy_history.side_effect = lambda *a, **kw: {"response": {}} freezer.tick(TESSIE_ENERGY_HISTORY_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() mock_energy_history.assert_called_once() - assert ( - hass.states.get("sensor.energy_site_grid_imported").state == STATE_UNAVAILABLE - ) - assert isinstance(coordinator.last_exception, UpdateFailed) - assert coordinator.last_exception.translation_domain == DOMAIN - assert coordinator.last_exception.translation_key == "invalid_energy_history_data" + + # Sensor should retain last good state rather than becoming unavailable + assert hass.states.get("sensor.energy_site_grid_imported").state == state_before + assert coordinator.last_exception is None + + +async def test_coordinator_energy_history_cold_start_invalid_data( + hass: HomeAssistant, mock_energy_history, freezer: FrozenDateTimeFactory +) -> None: + """Tests cold-start fallback when the very first energy history fetch has invalid data.""" + + mock_energy_history.side_effect = lambda *a, **kw: {"response": {}} + entry = await setup_platform(hass, [Platform.SENSOR]) + coordinator = entry.runtime_data.energysites[0].history_coordinator + assert coordinator is not None + + # Coordinator should not have raised an exception; data stays empty + assert coordinator.last_exception is None + assert coordinator.data == {} + + # Sensor should be unknown until the first successful fetch + assert hass.states.get("sensor.energy_site_grid_imported").state == STATE_UNKNOWN + + # Now recover: restore valid energy history data and trigger an update + mock_energy_history.side_effect = lambda *a, **kw: deepcopy(ENERGY_HISTORY) + mock_energy_history.reset_mock() + freezer.tick(TESSIE_ENERGY_HISTORY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + mock_energy_history.assert_called_once() + + # Coordinator should have real data and no exception + assert coordinator.last_exception is None + assert coordinator.data["solar_energy_exported"] == 724