mirror of
https://github.com/home-assistant/core.git
synced 2025-10-28 21:19:41 +00:00
405 lines
12 KiB
Python
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
|