Files
core/tests/components/solaredge/test_config_flow.py
2025-10-19 21:33:23 +02:00

405 lines
12 KiB
Python

"""Tests for the SolarEdge config flow."""
from unittest.mock import AsyncMock, Mock
from aiohttp import ClientError, ClientResponseError
import pytest
from homeassistant.components.recorder import Recorder
from homeassistant.components.solaredge.const import (
CONF_SECTION_API_AUTH,
CONF_SECTION_WEB_AUTH,
CONF_SITE_ID,
DEFAULT_NAME,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .conftest import API_KEY, PASSWORD, SITE_ID, USERNAME
from tests.common import MockConfigEntry
NAME = "solaredge site 1 2 3"
@pytest.fixture(autouse=True)
def solaredge_api_fixture(solaredge_api: Mock) -> None:
"""Mock the solaredge API."""
@pytest.fixture(autouse=True)
def solaredge_web_api_fixture(solaredge_web_api: AsyncMock) -> None:
"""Mock the solaredge web API."""
async def test_user_api_key(
recorder_mock: Recorder,
hass: HomeAssistant,
solaredge_api: Mock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test user config with API key."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result.get("type") is FlowResultType.FORM
assert result.get("step_id") == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: NAME,
CONF_SITE_ID: SITE_ID,
CONF_SECTION_API_AUTH: {CONF_API_KEY: API_KEY},
},
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("title") == "solaredge_site_1_2_3"
data = result.get("data")
assert data
assert data[CONF_SITE_ID] == SITE_ID
assert data[CONF_API_KEY] == API_KEY
assert CONF_USERNAME not in data
assert CONF_PASSWORD not in data
assert len(mock_setup_entry.mock_calls) == 1
async def test_user_web_login(
recorder_mock: Recorder,
hass: HomeAssistant,
solaredge_web_api: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test user config with web login."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: NAME,
CONF_SITE_ID: SITE_ID,
CONF_SECTION_WEB_AUTH: {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
},
},
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("title") == "solaredge_site_1_2_3"
data = result.get("data")
assert data
assert data[CONF_SITE_ID] == SITE_ID
assert data[CONF_USERNAME] == USERNAME
assert data[CONF_PASSWORD] == PASSWORD
assert CONF_API_KEY not in data
assert len(mock_setup_entry.mock_calls) == 1
solaredge_web_api.async_get_equipment.assert_awaited_once()
async def test_user_both_auth(
recorder_mock: Recorder,
hass: HomeAssistant,
solaredge_api: Mock,
solaredge_web_api: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test user config with both API key and web login."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_NAME: NAME,
CONF_SITE_ID: SITE_ID,
CONF_SECTION_API_AUTH: {CONF_API_KEY: API_KEY},
CONF_SECTION_WEB_AUTH: {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
},
},
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
data = result.get("data")
assert data
assert data[CONF_SITE_ID] == SITE_ID
assert data[CONF_API_KEY] == API_KEY
assert data[CONF_USERNAME] == USERNAME
assert data[CONF_PASSWORD] == PASSWORD
assert len(mock_setup_entry.mock_calls) == 1
async def test_abort_if_already_setup(
recorder_mock: Recorder, hass: HomeAssistant
) -> None:
"""Test we abort if the site_id is already setup."""
MockConfigEntry(
domain=DOMAIN,
data={CONF_NAME: DEFAULT_NAME, CONF_SITE_ID: SITE_ID, CONF_API_KEY: API_KEY},
).add_to_hass(hass)
# Should fail, same SITE_ID
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_NAME: "test",
CONF_SITE_ID: SITE_ID,
CONF_SECTION_API_AUTH: {CONF_API_KEY: "test"},
},
)
assert result.get("type") is FlowResultType.FORM
assert result.get("errors") == {CONF_SITE_ID: "already_configured"}
async def test_ignored_entry_does_not_cause_error(
recorder_mock: Recorder, hass: HomeAssistant
) -> None:
"""Test an ignored entry does not cause and error and we can still create an new entry."""
MockConfigEntry(
domain="solaredge",
data={CONF_NAME: DEFAULT_NAME, CONF_API_KEY: API_KEY},
source=SOURCE_IGNORE,
).add_to_hass(hass)
# Should not fail, same SITE_ID
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_NAME: "test",
CONF_SITE_ID: SITE_ID,
CONF_SECTION_API_AUTH: {CONF_API_KEY: "test"},
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test"
data = result["data"]
assert data
assert data[CONF_SITE_ID] == SITE_ID
assert data[CONF_API_KEY] == "test"
async def test_no_auth_provided(recorder_mock: Recorder, hass: HomeAssistant) -> None:
"""Test error when no authentication method is provided."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_NAME: NAME, CONF_SITE_ID: SITE_ID},
)
assert result.get("type") is FlowResultType.FORM
assert result.get("errors") == {"base": "auth_missing"}
@pytest.mark.parametrize(
("get_details_setup", "expected_error"),
[
(AsyncMock(return_value={"details": {"status": "NOK"}}), "site_not_active"),
(AsyncMock(return_value={}), "invalid_api_key"),
(AsyncMock(side_effect=TimeoutError()), "cannot_connect"),
(AsyncMock(side_effect=ClientError()), "cannot_connect"),
],
)
async def test_api_key_errors(
recorder_mock: Recorder,
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
solaredge_api: Mock,
get_details_setup: AsyncMock,
expected_error: str,
) -> None:
"""Test API key validation errors."""
solaredge_api.get_details = get_details_setup
user_input = {
CONF_NAME: NAME,
CONF_SITE_ID: SITE_ID,
CONF_SECTION_API_AUTH: {CONF_API_KEY: API_KEY},
}
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=user_input,
)
assert result.get("type") is FlowResultType.FORM
assert result.get("errors") == {CONF_SITE_ID: expected_error}
# Make sure the config flow is able to recover from above error
solaredge_api.get_details = AsyncMock(
return_value={"details": {"status": "active"}}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("data") == {CONF_SITE_ID: SITE_ID, CONF_API_KEY: API_KEY}
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize(
("api_exception", "expected_error"),
[
(ClientResponseError(None, None, status=401), "invalid_auth"),
(ClientResponseError(None, None, status=403), "invalid_auth"),
(ClientResponseError(None, None, status=400), "cannot_connect"),
(ClientResponseError(None, None, status=500), "cannot_connect"),
(TimeoutError(), "cannot_connect"),
(ClientError(), "cannot_connect"),
],
)
async def test_web_login_errors(
recorder_mock: Recorder,
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
solaredge_web_api: AsyncMock,
api_exception: Exception,
expected_error: str,
) -> None:
"""Test web login validation errors."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
solaredge_web_api.async_get_equipment.side_effect = api_exception
user_input = {
CONF_NAME: NAME,
CONF_SITE_ID: SITE_ID,
CONF_SECTION_WEB_AUTH: {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
},
}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input
)
assert result.get("type") is FlowResultType.FORM
assert result.get("errors") == {"base": expected_error}
# Make sure the config flow is able to recover from above error
solaredge_web_api.async_get_equipment.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input
)
assert result.get("type") is FlowResultType.CREATE_ENTRY
assert result.get("data") == {
CONF_SITE_ID: SITE_ID,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_reconfigure_flow_api_key(
recorder_mock: Recorder,
hass: HomeAssistant,
solaredge_api: Mock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reconfigure flow with API key."""
entry = MockConfigEntry(
domain=DOMAIN,
title=NAME,
data={CONF_SITE_ID: SITE_ID, CONF_API_KEY: "old_api_key"},
)
entry.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
assert result.get("type") is FlowResultType.FORM
assert result.get("step_id") == "reconfigure"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_SECTION_API_AUTH: {CONF_API_KEY: API_KEY},
},
)
assert result.get("type") is FlowResultType.ABORT
assert result.get("reason") == "reconfigure_successful"
await hass.async_block_till_done()
assert entry.title == NAME
assert entry.data[CONF_SITE_ID] == SITE_ID
assert entry.data[CONF_API_KEY] == API_KEY
assert mock_setup_entry.call_count == 1
async def test_reconfigure_flow_web_login_and_errors(
recorder_mock: Recorder,
hass: HomeAssistant,
solaredge_web_api: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reconfigure flow with web login and error handling."""
entry = MockConfigEntry(
domain=DOMAIN,
title=NAME,
data={CONF_SITE_ID: SITE_ID, CONF_API_KEY: "old_api_key"},
)
entry.add_to_hass(hass)
result = await entry.start_reconfigure_flow(hass)
assert result.get("type") is FlowResultType.FORM
assert result.get("step_id") == "reconfigure"
# Test error
solaredge_web_api.async_get_equipment.side_effect = ClientResponseError(
None, None, status=401
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_SECTION_WEB_AUTH: {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
},
},
)
assert result.get("type") is FlowResultType.FORM
assert result.get("errors") == {"base": "invalid_auth"}
# Test recovery
solaredge_web_api.async_get_equipment.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_SECTION_WEB_AUTH: {
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
},
},
)
assert result.get("type") is FlowResultType.ABORT
assert result.get("reason") == "reconfigure_successful"
await hass.async_block_till_done()
assert entry.title == NAME
assert entry.data == {
CONF_SITE_ID: SITE_ID,
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
}
assert mock_setup_entry.call_count == 1