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."""
import unittest
from unittest.mock import patch
from homeassistant.components.sma.const import CONF_GROUP
from homeassistant.const import (
CONF_HOST,
@ -11,12 +8,16 @@ from homeassistant.const import (
CONF_SSL,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
MOCK_DEVICE = {
"manufacturer": "SMA",
"name": "SMA Device Name",
"type": "Sunny Boy 3.6",
"serial": 123456789,
"sw_version": "1.0.0",
}
MOCK_USER_INPUT = {
@ -32,7 +33,6 @@ MOCK_USER_REAUTH = {
}
MOCK_DHCP_DISCOVERY_INPUT = {
# CONF_HOST: "1.1.1.2",
CONF_SSL: True,
CONF_VERIFY_SSL: False,
CONF_GROUP: "user",
@ -49,9 +49,9 @@ MOCK_DHCP_DISCOVERY = {
}
def _patch_async_setup_entry(return_value=True) -> unittest.mock._patch:
"""Patch async_setup_entry."""
return patch(
"homeassistant.components.sma.async_setup_entry",
return_value=return_value,
)
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)
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."""
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.sensor import Sensors
import pytest
from homeassistant import config_entries
from homeassistant.components.sma.const import DOMAIN
from homeassistant.core import HomeAssistant
@ -19,31 +23,54 @@ from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Return the default mocked config entry."""
entry = MockConfigEntry(
return MockConfigEntry(
domain=DOMAIN,
title=MOCK_DEVICE["name"],
unique_id=str(MOCK_DEVICE["serial"]),
data=MOCK_USER_INPUT,
source=config_entries.SOURCE_IMPORT,
minor_version=2,
entry_id="sma_entry_123",
)
entry.add_to_hass(hass)
return entry
@pytest.fixture
async def init_integration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> MockConfigEntry:
"""Create a fake SMA Config Entry."""
mock_config_entry.add_to_hass(hass)
def mock_setup_entry() -> Generator[AsyncMock]:
"""Mock the setup entry."""
with patch(
"homeassistant.components.sma.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
with (
patch("pysma.SMA.read"),
patch(
"pysma.SMA.get_sensors", return_value=Sensors(sensor_map[GENERIC_SENSORS])
),
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
@pytest.fixture
def mock_sma_client() -> Generator[MagicMock]:
"""Mock the SMA client."""
with patch("homeassistant.components.sma.pysma.SMA", autospec=True) as client:
client.return_value.device_info.return_value = MOCK_DEVICE
client.new_session.return_value = True
client.return_value.get_sensors.return_value = Sensors(
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_polling': False,
'source': 'import',
'source': 'user',
'subentries': list([
]),
'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."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from pysma.exceptions import (
SmaAuthenticationException,
@ -21,7 +21,6 @@ from . import (
MOCK_DHCP_DISCOVERY_INPUT,
MOCK_USER_INPUT,
MOCK_USER_REAUTH,
_patch_async_setup_entry,
)
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."""
result = await hass.config_entries.flow.async_init(
@ -48,16 +49,11 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM
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["flow_id"],
MOCK_USER_INPUT,
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == MOCK_USER_INPUT["host"]
@ -76,18 +72,18 @@ async def test_form(hass: HomeAssistant) -> None:
],
)
async def test_form_exceptions(
hass: HomeAssistant, exception: Exception, error: str
hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
with (
patch(
"homeassistant.components.sma.pysma.SMA.new_session", side_effect=exception
),
_patch_async_setup_entry() as mock_setup_entry,
with patch(
"homeassistant.components.sma.pysma.SMA.new_session", side_effect=exception
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@ -96,39 +92,34 @@ async def test_form_exceptions(
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
assert len(mock_setup_entry.mock_calls) == 0
async def test_form_already_configured(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_sma_client: AsyncMock
) -> None:
"""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(
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["flow_id"],
MOCK_USER_INPUT,
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_USER_INPUT,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
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."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
@ -139,31 +130,22 @@ async def test_dhcp_discovery(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM
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["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT,
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT,
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == MOCK_DHCP_DISCOVERY["host"]
assert result["data"] == MOCK_DHCP_DISCOVERY
assert result["result"].unique_id == DHCP_DISCOVERY.hostname.replace("SMA", "")
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_already_configured(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test starting a flow by dhcp when already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_DHCP}, data=DHCP_DISCOVERY_DUPLICATE
)
@ -182,18 +164,23 @@ async def test_dhcp_already_configured(
],
)
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:
"""Test we handle cannot connect error."""
"""Test we handle cannot connect error in DHCP flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_DHCP},
data=DHCP_DISCOVERY,
)
with patch(
"homeassistant.components.sma.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["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT,
@ -202,17 +189,12 @@ async def test_dhcp_exceptions(
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
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(),
):
with patch("homeassistant.components.sma.pysma.SMA") as mock_sma:
mock_sma_instance = mock_sma.return_value
mock_sma_instance.new_session = AsyncMock(return_value=True)
mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
mock_sma_instance.close_session = AsyncMock(return_value=True)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_DHCP_DISCOVERY_INPUT,
@ -225,14 +207,16 @@ async def test_dhcp_exceptions(
async def test_full_flow_reauth(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
hass: HomeAssistant, mock_setup_entry: MockConfigEntry, mock_sma_client: AsyncMock
) -> None:
"""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(
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["step_id"] == "reauth_confirm"
@ -241,16 +225,11 @@ async def test_full_flow_reauth(
assert result["type"] is FlowResultType.FORM
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["flow_id"],
MOCK_USER_REAUTH,
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_REAUTH,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
@ -272,26 +251,28 @@ async def test_reauth_flow_exceptions(
exception: Exception,
error: str,
) -> None:
"""Test we handle cannot connect error."""
result = await mock_config_entry.start_reauth_flow(hass)
"""Test we handle errors during reauth flow properly."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
with (
patch("pysma.SMA.new_session", side_effect=exception),
):
result = await entry.start_reauth_flow(hass)
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["flow_id"],
MOCK_USER_REAUTH,
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
assert result["step_id"] == "reauth_confirm"
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
assert result["step_id"] == "reauth_confirm"
mock_sma_instance.new_session = AsyncMock(return_value=True)
mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
mock_sma_instance.close_session = AsyncMock(return_value=True)
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["flow_id"],
MOCK_USER_REAUTH,
@ -300,4 +281,3 @@ async def test_reauth_flow_exceptions(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert len(mock_setup_entry.mock_calls) == 1

View File

@ -1,27 +1,32 @@
"""Test the sma init file."""
from collections.abc import AsyncGenerator
from homeassistant.components.sma.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT
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
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."""
with _patch_async_setup_entry():
entry = MockConfigEntry(
domain=DOMAIN,
title=MOCK_DEVICE["name"],
unique_id=MOCK_DEVICE["serial"], # Not converted to str
data=MOCK_USER_INPUT,
source=SOURCE_IMPORT,
minor_version=1,
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
assert entry.version == 1
assert entry.minor_version == 2
assert entry.unique_id == str(MOCK_DEVICE["serial"])
entry = MockConfigEntry(
domain=DOMAIN,
title=MOCK_DEVICE["name"],
unique_id=MOCK_DEVICE["serial"], # Not converted to str
data=MOCK_USER_INPUT,
source=SOURCE_IMPORT,
minor_version=1,
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
assert entry.version == 1
assert entry.minor_version == 2
assert entry.unique_id == str(MOCK_DEVICE["serial"])

View File

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