SMA add snapshots & tests (#144555)

* Refactor the sensor test to use snapshots

* Review feedback

* Remove leftover
This commit is contained in:
Erwin Douna 2025-05-09 22:55:08 +02:00 committed by GitHub
parent 970edbed40
commit 2bce697aa7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 5737 additions and 168 deletions

View File

@ -1,8 +1,5 @@
"""Tests for the sma integration.""" """Tests for the sma integration."""
import unittest
from unittest.mock import patch
from homeassistant.components.sma.const import CONF_GROUP from homeassistant.components.sma.const import CONF_GROUP
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
@ -11,12 +8,16 @@ from homeassistant.const import (
CONF_SSL, CONF_SSL,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
MOCK_DEVICE = { MOCK_DEVICE = {
"manufacturer": "SMA", "manufacturer": "SMA",
"name": "SMA Device Name", "name": "SMA Device Name",
"type": "Sunny Boy 3.6", "type": "Sunny Boy 3.6",
"serial": 123456789, "serial": 123456789,
"sw_version": "1.0.0",
} }
MOCK_USER_INPUT = { MOCK_USER_INPUT = {
@ -32,7 +33,6 @@ MOCK_USER_REAUTH = {
} }
MOCK_DHCP_DISCOVERY_INPUT = { MOCK_DHCP_DISCOVERY_INPUT = {
# CONF_HOST: "1.1.1.2",
CONF_SSL: True, CONF_SSL: True,
CONF_VERIFY_SSL: False, CONF_VERIFY_SSL: False,
CONF_GROUP: "user", CONF_GROUP: "user",
@ -49,9 +49,9 @@ MOCK_DHCP_DISCOVERY = {
} }
def _patch_async_setup_entry(return_value=True) -> unittest.mock._patch: async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Patch async_setup_entry.""" """Fixture for setting up the component."""
return patch( config_entry.add_to_hass(hass)
"homeassistant.components.sma.async_setup_entry",
return_value=return_value, await hass.config_entries.async_setup(config_entry.entry_id)
) await hass.async_block_till_done()

View File

@ -1,13 +1,17 @@
"""Fixtures for sma tests.""" """Fixtures for sma tests."""
from unittest.mock import patch from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch
from pysma.const import GENERIC_SENSORS from pysma.const import (
ENERGY_METER_VIA_INVERTER,
GENERIC_SENSORS,
OPTIMIZERS_VIA_INVERTER,
)
from pysma.definitions import sensor_map from pysma.definitions import sensor_map
from pysma.sensor import Sensors from pysma.sensor import Sensors
import pytest import pytest
from homeassistant import config_entries
from homeassistant.components.sma.const import DOMAIN from homeassistant.components.sma.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -19,31 +23,54 @@ from tests.common import MockConfigEntry
@pytest.fixture @pytest.fixture
def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Return the default mocked config entry.""" """Return the default mocked config entry."""
entry = MockConfigEntry(
return MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title=MOCK_DEVICE["name"], title=MOCK_DEVICE["name"],
unique_id=str(MOCK_DEVICE["serial"]), unique_id=str(MOCK_DEVICE["serial"]),
data=MOCK_USER_INPUT, data=MOCK_USER_INPUT,
source=config_entries.SOURCE_IMPORT,
minor_version=2, minor_version=2,
entry_id="sma_entry_123",
) )
entry.add_to_hass(hass)
return entry
@pytest.fixture @pytest.fixture
async def init_integration( def mock_setup_entry() -> Generator[AsyncMock]:
hass: HomeAssistant, mock_config_entry: MockConfigEntry """Mock the setup entry."""
) -> MockConfigEntry: with patch(
"""Create a fake SMA Config Entry.""" "homeassistant.components.sma.async_setup_entry", return_value=True
mock_config_entry.add_to_hass(hass) ) as mock_setup_entry:
yield mock_setup_entry
with (
patch("pysma.SMA.read"), @pytest.fixture
patch( def mock_sma_client() -> Generator[MagicMock]:
"pysma.SMA.get_sensors", return_value=Sensors(sensor_map[GENERIC_SENSORS]) """Mock the SMA client."""
), with patch("homeassistant.components.sma.pysma.SMA", autospec=True) as client:
): client.return_value.device_info.return_value = MOCK_DEVICE
await hass.config_entries.async_setup(mock_config_entry.entry_id) client.new_session.return_value = True
await hass.async_block_till_done() client.return_value.get_sensors.return_value = Sensors(
return mock_config_entry sensor_map[GENERIC_SENSORS]
+ sensor_map[OPTIMIZERS_VIA_INVERTER]
+ sensor_map[ENERGY_METER_VIA_INVERTER]
)
default_sensor_values = {
"6100_00499100": 5000,
"6100_00499500": 230,
"6100_00499200": 20,
"6100_00499300": 50,
"6100_00499400": 100,
"6100_00499600": 10,
"6100_00499700": 1000,
}
def mock_read(sensors):
for sensor in sensors:
if sensor.key in default_sensor_values:
sensor.value = default_sensor_values[sensor.key]
return True
client.return_value.read.side_effect = mock_read
yield client

View File

@ -20,7 +20,7 @@
}), }),
'pref_disable_new_entities': False, 'pref_disable_new_entities': False,
'pref_disable_polling': False, 'pref_disable_polling': False,
'source': 'import', 'source': 'user',
'subentries': list([ 'subentries': list([
]), ]),
'title': 'SMA Device Name', 'title': 'SMA Device Name',

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
"""Test the sma config flow.""" """Test the sma config flow."""
from unittest.mock import patch from unittest.mock import AsyncMock, patch
from pysma.exceptions import ( from pysma.exceptions import (
SmaAuthenticationException, SmaAuthenticationException,
@ -21,7 +21,6 @@ from . import (
MOCK_DHCP_DISCOVERY_INPUT, MOCK_DHCP_DISCOVERY_INPUT,
MOCK_USER_INPUT, MOCK_USER_INPUT,
MOCK_USER_REAUTH, MOCK_USER_REAUTH,
_patch_async_setup_entry,
) )
from tests.conftest import MockConfigEntry from tests.conftest import MockConfigEntry
@ -39,7 +38,9 @@ DHCP_DISCOVERY_DUPLICATE = DhcpServiceInfo(
) )
async def test_form(hass: HomeAssistant) -> None: async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_sma_client: AsyncMock
) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -48,11 +49,6 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {} assert result["errors"] == {}
with (
patch("pysma.SMA.new_session", return_value=True),
patch("pysma.SMA.device_info", return_value=MOCK_DEVICE),
_patch_async_setup_entry() as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_USER_INPUT, MOCK_USER_INPUT,
@ -76,18 +72,18 @@ async def test_form(hass: HomeAssistant) -> None:
], ],
) )
async def test_form_exceptions( async def test_form_exceptions(
hass: HomeAssistant, exception: Exception, error: str hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None: ) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
with ( with patch(
patch(
"homeassistant.components.sma.pysma.SMA.new_session", side_effect=exception "homeassistant.components.sma.pysma.SMA.new_session", side_effect=exception
),
_patch_async_setup_entry() as mock_setup_entry,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -96,39 +92,34 @@ async def test_form_exceptions(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error} assert result["errors"] == {"base": error}
assert len(mock_setup_entry.mock_calls) == 0
async def test_form_already_configured( async def test_form_already_configured(
hass: HomeAssistant, mock_config_entry: MockConfigEntry hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_sma_client: AsyncMock
) -> None: ) -> None:
"""Test starting a flow by user when already configured.""" """Test starting a flow by user when already configured."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
with (
patch("homeassistant.components.sma.pysma.SMA.new_session", return_value=True),
patch(
"homeassistant.components.sma.pysma.SMA.device_info",
return_value=MOCK_DEVICE,
),
patch(
"homeassistant.components.sma.pysma.SMA.close_session", return_value=True
),
_patch_async_setup_entry() as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_USER_INPUT, user_input=MOCK_USER_INPUT,
) )
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
assert len(mock_setup_entry.mock_calls) == 0
async def test_dhcp_discovery(hass: HomeAssistant) -> None: async def test_dhcp_discovery(
hass: HomeAssistant, mock_setup_entry: MockConfigEntry, mock_sma_client: AsyncMock
) -> None:
"""Test we can setup from dhcp discovery.""" """Test we can setup from dhcp discovery."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -139,14 +130,6 @@ async def test_dhcp_discovery(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "discovery_confirm" assert result["step_id"] == "discovery_confirm"
with (
patch("homeassistant.components.sma.pysma.SMA.new_session", return_value=True),
patch(
"homeassistant.components.sma.pysma.SMA.device_info",
return_value=MOCK_DEVICE,
),
_patch_async_setup_entry() as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT, MOCK_DHCP_DISCOVERY_INPUT,
@ -157,13 +140,12 @@ async def test_dhcp_discovery(hass: HomeAssistant) -> None:
assert result["data"] == MOCK_DHCP_DISCOVERY assert result["data"] == MOCK_DHCP_DISCOVERY
assert result["result"].unique_id == DHCP_DISCOVERY.hostname.replace("SMA", "") assert result["result"].unique_id == DHCP_DISCOVERY.hostname.replace("SMA", "")
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_already_configured( async def test_dhcp_already_configured(
hass: HomeAssistant, mock_config_entry: MockConfigEntry hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None: ) -> None:
"""Test starting a flow by dhcp when already configured.""" """Test starting a flow by dhcp when already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY_DUPLICATE DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY_DUPLICATE
) )
@ -182,18 +164,23 @@ async def test_dhcp_already_configured(
], ],
) )
async def test_dhcp_exceptions( async def test_dhcp_exceptions(
hass: HomeAssistant, exception: Exception, error: str hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
mock_sma_client: AsyncMock,
exception: Exception,
error: str,
) -> None: ) -> None:
"""Test we handle cannot connect error.""" """Test we handle cannot connect error in DHCP flow."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_DHCP}, context={"source": SOURCE_DHCP},
data=DHCP_DISCOVERY, data=DHCP_DISCOVERY,
) )
with patch( with patch("homeassistant.components.sma.pysma.SMA") as mock_sma:
"homeassistant.components.sma.pysma.SMA.new_session", side_effect=exception mock_sma_instance = mock_sma.return_value
): mock_sma_instance.new_session = AsyncMock(side_effect=exception)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT, MOCK_DHCP_DISCOVERY_INPUT,
@ -202,17 +189,12 @@ async def test_dhcp_exceptions(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error} assert result["errors"] == {"base": error}
with ( with patch("homeassistant.components.sma.pysma.SMA") as mock_sma:
patch("homeassistant.components.sma.pysma.SMA.new_session", return_value=True), mock_sma_instance = mock_sma.return_value
patch( mock_sma_instance.new_session = AsyncMock(return_value=True)
"homeassistant.components.sma.pysma.SMA.device_info", mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
return_value=MOCK_DEVICE, mock_sma_instance.close_session = AsyncMock(return_value=True)
),
patch(
"homeassistant.components.sma.pysma.SMA.close_session", return_value=True
),
_patch_async_setup_entry(),
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT, MOCK_DHCP_DISCOVERY_INPUT,
@ -225,14 +207,16 @@ async def test_dhcp_exceptions(
async def test_full_flow_reauth( async def test_full_flow_reauth(
hass: HomeAssistant, mock_config_entry: MockConfigEntry hass: HomeAssistant, mock_setup_entry: MockConfigEntry, mock_sma_client: AsyncMock
) -> None: ) -> None:
"""Test the full flow of the config flow.""" """Test the full flow of the config flow."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
result = await mock_config_entry.start_reauth_flow(hass) result = await entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
@ -241,11 +225,6 @@ async def test_full_flow_reauth(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
with (
patch("pysma.SMA.new_session", return_value=True),
patch("pysma.SMA.device_info", return_value=MOCK_DEVICE),
_patch_async_setup_entry() as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_USER_REAUTH, MOCK_USER_REAUTH,
@ -272,12 +251,15 @@ async def test_reauth_flow_exceptions(
exception: Exception, exception: Exception,
error: str, error: str,
) -> None: ) -> None:
"""Test we handle cannot connect error.""" """Test we handle errors during reauth flow properly."""
result = await mock_config_entry.start_reauth_flow(hass) entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
with ( result = await entry.start_reauth_flow(hass)
patch("pysma.SMA.new_session", side_effect=exception),
): with patch("homeassistant.components.sma.pysma.SMA") as mock_sma:
mock_sma_instance = mock_sma.return_value
mock_sma_instance.new_session = AsyncMock(side_effect=exception)
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_USER_REAUTH, MOCK_USER_REAUTH,
@ -287,11 +269,10 @@ async def test_reauth_flow_exceptions(
assert result["errors"] == {"base": error} assert result["errors"] == {"base": error}
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
with ( mock_sma_instance.new_session = AsyncMock(return_value=True)
patch("pysma.SMA.new_session", return_value=True), mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
patch("pysma.SMA.device_info", return_value=MOCK_DEVICE), mock_sma_instance.close_session = AsyncMock(return_value=True)
_patch_async_setup_entry() as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
MOCK_USER_REAUTH, MOCK_USER_REAUTH,
@ -300,4 +281,3 @@ async def test_reauth_flow_exceptions(
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful" assert result["reason"] == "reauth_successful"
assert len(mock_setup_entry.mock_calls) == 1

View File

@ -1,17 +1,22 @@
"""Test the sma init file.""" """Test the sma init file."""
from collections.abc import AsyncGenerator
from homeassistant.components.sma.const import DOMAIN from homeassistant.components.sma.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import MOCK_DEVICE, MOCK_USER_INPUT, _patch_async_setup_entry from . import MOCK_DEVICE, MOCK_USER_INPUT
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_migrate_entry_minor_version_1_2(hass: HomeAssistant) -> None: async def test_migrate_entry_minor_version_1_2(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_sma_client: AsyncGenerator,
) -> None:
"""Test migrating a 1.1 config entry to 1.2.""" """Test migrating a 1.1 config entry to 1.2."""
with _patch_async_setup_entry():
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title=MOCK_DEVICE["name"], title=MOCK_DEVICE["name"],

View File

@ -1,31 +1,34 @@
"""Test the sma sensor platform.""" """Test the SMA sensor platform."""
from pysma.const import ( from collections.abc import Generator
ENERGY_METER_VIA_INVERTER, from unittest.mock import patch
GENERIC_SENSORS,
OPTIMIZERS_VIA_INVERTER,
)
from pysma.definitions import sensor_map
from homeassistant.components.sma.sensor import SENSOR_ENTITIES import pytest
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, UnitOfPower from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_sensors(hass: HomeAssistant, init_integration) -> None: @pytest.mark.usefixtures("entity_registry_enabled_by_default")
"""Test states of the sensors.""" async def test_all_entities(
state = hass.states.get("sensor.sma_device_grid_power") hass: HomeAssistant,
assert state snapshot: SnapshotAssertion,
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT mock_sma_client: Generator,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
async def test_sensor_entities(hass: HomeAssistant, init_integration) -> None: ) -> None:
"""Test SENSOR_ENTITIES contains a SensorEntityDescription for each pysma sensor.""" """Test all entities."""
pysma_sensor_definitions = ( with patch(
sensor_map[GENERIC_SENSORS] "homeassistant.components.sma.PLATFORMS",
+ sensor_map[OPTIMIZERS_VIA_INVERTER] [Platform.SENSOR],
+ sensor_map[ENERGY_METER_VIA_INVERTER] ):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(
hass, entity_registry, snapshot, mock_config_entry.entry_id
) )
for sensor in pysma_sensor_definitions:
assert sensor.name in SENSOR_ENTITIES