From 9e9c8f5724cc0dfce74f6df6a3cc4ece2db03018 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Wed, 22 Oct 2025 08:13:46 +0200 Subject: [PATCH] SMA: add sensor availability and expand tests (#154953) --- homeassistant/components/sma/sensor.py | 9 ++++++ tests/components/sma/test_init.py | 34 ++++++++++++++++++-- tests/components/sma/test_sensor.py | 44 ++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 49cba402b61..1c103b53cc4 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -873,6 +873,7 @@ class SMAsensor(CoordinatorEntity[SMADataUpdateCoordinator], SensorEntity): url = f"{protocol}://{entry.data[CONF_HOST]}" self._sensor = pysma_sensor + self._serial = coordinator.data.sma_device_info["serial"] assert entry.unique_id self._attr_device_info = DeviceInfo( @@ -902,6 +903,14 @@ class SMAsensor(CoordinatorEntity[SMADataUpdateCoordinator], SensorEntity): return f"{name_prefix} {super().name}" + @property + def available(self) -> bool: + """Return if the device is available.""" + return ( + super().available + and self._serial == self.coordinator.data.sma_device_info["serial"] + ) + @property def native_value(self) -> StateType: """Return the state of the sensor.""" diff --git a/tests/components/sma/test_init.py b/tests/components/sma/test_init.py index 57c3cab33e7..0c20a1acc43 100644 --- a/tests/components/sma/test_init.py +++ b/tests/components/sma/test_init.py @@ -1,12 +1,19 @@ """Test the sma init file.""" -from collections.abc import AsyncGenerator +from collections.abc import AsyncGenerator, Generator + +from pysma.exceptions import ( + SmaAuthenticationException, + SmaConnectionException, + SmaReadException, +) +import pytest from homeassistant.components.sma.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntryState from homeassistant.core import HomeAssistant -from . import MOCK_DEVICE, MOCK_USER_INPUT +from . import MOCK_DEVICE, MOCK_USER_INPUT, setup_integration from tests.common import MockConfigEntry @@ -30,3 +37,24 @@ async def test_migrate_entry_minor_version_1_2( assert entry.version == 1 assert entry.minor_version == 2 assert entry.unique_id == str(MOCK_DEVICE["serial"]) + + +@pytest.mark.parametrize( + ("exception", "expected_state"), + [ + (SmaConnectionException, ConfigEntryState.SETUP_RETRY), + (SmaAuthenticationException, ConfigEntryState.SETUP_ERROR), + (SmaReadException, ConfigEntryState.SETUP_RETRY), + ], +) +async def test_setup_exceptions( + hass: HomeAssistant, + mock_sma_client: Generator, + mock_config_entry: MockConfigEntry, + exception: Exception, + expected_state: ConfigEntryState, +) -> None: + """Test the _async_setup.""" + mock_sma_client.device_info.side_effect = exception + await setup_integration(hass, mock_config_entry) + assert mock_config_entry.state == expected_state diff --git a/tests/components/sma/test_sensor.py b/tests/components/sma/test_sensor.py index 8199e8fc163..d289962d798 100644 --- a/tests/components/sma/test_sensor.py +++ b/tests/components/sma/test_sensor.py @@ -3,16 +3,25 @@ from collections.abc import Generator from unittest.mock import patch +from freezegun.api import FrozenDateTimeFactory +from pysma.exceptions import ( + SmaAuthenticationException, + SmaConnectionException, + SmaReadException, +) import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.const import Platform +from homeassistant.components.sma.const import DEFAULT_SCAN_INTERVAL +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt as dt_util from . import setup_integration -from tests.common import MockConfigEntry, snapshot_platform +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @pytest.mark.usefixtures("entity_registry_enabled_by_default") @@ -32,3 +41,34 @@ async def test_all_entities( await snapshot_platform( hass, entity_registry, snapshot, mock_config_entry.entry_id ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + ("exception"), + [ + (SmaConnectionException), + (SmaAuthenticationException), + (SmaReadException), + (Exception), + ], +) +async def test_refresh_exceptions( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_sma_client: Generator, + freezer: FrozenDateTimeFactory, + exception: Exception, +) -> None: + """Test the coordinator refresh exceptions.""" + await setup_integration(hass, mock_config_entry) + assert mock_config_entry.state is ConfigEntryState.LOADED + + mock_sma_client.read.side_effect = exception + + freezer.tick(DEFAULT_SCAN_INTERVAL) + async_fire_time_changed(hass, dt_util.utcnow()) + await hass.async_block_till_done() + + state = hass.states.get("sensor.sma_device_name_battery_capacity_a") + assert state.state == STATE_UNAVAILABLE