Files
core/tests/components/sma/test_config_flow.py
2026-01-16 10:16:34 +01:00

422 lines
14 KiB
Python

"""Test the sma config flow."""
from unittest.mock import AsyncMock, MagicMock, patch
from pysma import SmaAuthenticationException, SmaConnectionException, SmaReadException
from pysma.helpers import DeviceInfo
import pytest
from homeassistant.components.sma.const import CONF_GROUP, DOMAIN
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_SSL, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from . import (
MOCK_DEVICE,
MOCK_DHCP_DISCOVERY,
MOCK_DHCP_DISCOVERY_INPUT,
MOCK_USER_INPUT,
MOCK_USER_REAUTH,
MOCK_USER_RECONFIGURE,
)
from tests.conftest import MockConfigEntry
DHCP_DISCOVERY = DhcpServiceInfo(
ip="1.1.1.1",
hostname="SMA123456",
macaddress="0015bb00abcd",
)
DHCP_DISCOVERY_DUPLICATE = DhcpServiceInfo(
ip="1.1.1.1",
hostname="SMA123456789",
macaddress="0015bb00abcd",
)
DHCP_DISCOVERY_DUPLICATE_001 = DhcpServiceInfo(
ip="1.1.1.1",
hostname="SMA123456789-001",
macaddress="0015bb00abcd",
)
async def test_form(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_sma_client: MagicMock
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
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"]
assert result["data"] == MOCK_USER_INPUT
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("exception", "error"),
[
(SmaConnectionException, "cannot_connect"),
(SmaAuthenticationException, "invalid_auth"),
(SmaReadException, "cannot_retrieve_device_info"),
(Exception, "unknown"),
],
)
async def test_form_exceptions(
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.config_flow.SMAWebConnect.new_session",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_INPUT,
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
async def test_form_already_configured(
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"
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"
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,
context={"source": SOURCE_DHCP},
data=DHCP_DISCOVERY,
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "discovery_confirm"
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", "")
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
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_dhcp_already_configured_duplicate(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test starting a flow by DHCP when already configured and MAC is added."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert CONF_MAC not in mock_config_entry.data
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_DHCP},
data=DHCP_DISCOVERY_DUPLICATE_001,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
await hass.async_block_till_done()
assert mock_config_entry.data.get(CONF_MAC) == format_mac(
DHCP_DISCOVERY_DUPLICATE_001.macaddress
)
@pytest.mark.parametrize(
("exception", "error"),
[
(SmaConnectionException, "cannot_connect"),
(SmaAuthenticationException, "invalid_auth"),
(SmaReadException, "cannot_retrieve_device_info"),
(Exception, "unknown"),
],
)
async def test_dhcp_exceptions(
hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
mock_sma_client: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""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.config_flow.SMAWebConnect") 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,
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
with patch("homeassistant.components.sma.config_flow.SMAWebConnect") 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,
)
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", "")
async def test_full_flow_reauth(
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 entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
# There is no user input
result = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
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"
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("exception", "error"),
[
(SmaConnectionException, "cannot_connect"),
(SmaAuthenticationException, "invalid_auth"),
(SmaReadException, "cannot_retrieve_device_info"),
(Exception, "unknown"),
],
)
async def test_reauth_flow_exceptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
exception: Exception,
error: str,
) -> None:
"""Test we handle errors during reauth flow properly."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
result = await entry.start_reauth_flow(hass)
with patch("homeassistant.components.sma.config_flow.SMAWebConnect") 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"
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_USER_REAUTH,
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
async def test_full_flow_reconfigure(
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 entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_USER_RECONFIGURE,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert entry.data[CONF_HOST] == "1.1.1.2"
assert entry.data[CONF_SSL] is True
assert entry.data[CONF_VERIFY_SSL] is False
assert entry.data[CONF_GROUP] == "user"
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("exception", "error"),
[
(SmaConnectionException, "cannot_connect"),
(SmaAuthenticationException, "invalid_auth"),
(SmaReadException, "cannot_retrieve_device_info"),
(Exception, "unknown"),
],
)
async def test_full_flow_reconfigure_exceptions(
hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
mock_sma_client: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test we handle cannot connect error and recover from it."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
mock_sma_client.new_session.side_effect = exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
MOCK_USER_RECONFIGURE,
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
mock_sma_client.new_session.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_USER_RECONFIGURE,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert entry.data[CONF_HOST] == "1.1.1.2"
assert entry.data[CONF_SSL] is True
assert entry.data[CONF_VERIFY_SSL] is False
assert entry.data[CONF_GROUP] == "user"
assert len(mock_setup_entry.mock_calls) == 1
async def test_reconfigure_mismatch_id(
hass: HomeAssistant,
mock_setup_entry: MockConfigEntry,
mock_sma_client: AsyncMock,
) -> None:
"""Test when a mismatch happens during reconfigure."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT, unique_id="123456789")
entry.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
# New device, on purpose to demonstrate we can't switch
different_device = DeviceInfo(
manufacturer="SMA",
name="Different SMA Device",
type="Sunny Boy 5.0",
serial=987654321,
sw_version="2.0.0",
)
mock_sma_client.device_info = AsyncMock(return_value=different_device)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_USER_RECONFIGURE,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unique_id_mismatch"