Elmax integration (#59321)
* Add elmax integration. * Run hassfest and generate requirements_all * Remove secondary platforms from elmax integration as per first component integration. * Move ElmaxCoordinator and ElmaxEntity into external file Linting review * Remove useless variables * Fix wrong indentation. * Remove unecessary platforms. * Remove unnecessary attributes from manifest. * Rely on property getters/setters rathern than private attribute from parent. Update internal entity state just after transitory state update. * Update homeassistant/components/elmax/const.py Reference Platform constant Co-authored-by: Marvin Wichmann <marvin@fam-wichmann.de> * Update username/password values Rely on already-present templating constants Co-authored-by: Marvin Wichmann <marvin@fam-wichmann.de> * Add missing constant import. * Remove unnecessary test_unhandled_error() callback implementation. * Add common.py to coverage ignore list. * Improve coverage of config_flow. * Rename the integration. Co-authored-by: Franck Nijhof <frenck@frenck.nl> * Fix reauth bug and improve testing. * Refactor lambdas into generators. Co-authored-by: Marvin Wichmann <marvin@fam-wichmann.de> Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
7c7df5bb51
commit
b0affe7bfb
19 changed files with 1206 additions and 0 deletions
268
tests/components/elmax/test_config_flow.py
Normal file
268
tests/components/elmax/test_config_flow.py
Normal file
|
@ -0,0 +1,268 @@
|
|||
"""Tests for the Abode config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from elmax_api.exceptions import ElmaxBadLoginError, ElmaxBadPinError, ElmaxNetworkError
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.elmax.const import (
|
||||
CONF_ELMAX_PANEL_ID,
|
||||
CONF_ELMAX_PANEL_NAME,
|
||||
CONF_ELMAX_PANEL_PIN,
|
||||
CONF_ELMAX_PASSWORD,
|
||||
CONF_ELMAX_USERNAME,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_REAUTH
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from tests.components.elmax import (
|
||||
MOCK_PANEL_ID,
|
||||
MOCK_PANEL_NAME,
|
||||
MOCK_PANEL_PIN,
|
||||
MOCK_PASSWORD,
|
||||
MOCK_USERNAME,
|
||||
)
|
||||
|
||||
CONF_POLLING = "polling"
|
||||
|
||||
|
||||
def _has_error(errors):
|
||||
return errors is not None and len(errors.keys()) > 0
|
||||
|
||||
|
||||
async def _bootstrap(
|
||||
hass,
|
||||
source=config_entries.SOURCE_USER,
|
||||
username=MOCK_USERNAME,
|
||||
password=MOCK_PASSWORD,
|
||||
panel_name=MOCK_PANEL_NAME,
|
||||
panel_pin=MOCK_PANEL_PIN,
|
||||
) -> FlowResult:
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": source}
|
||||
)
|
||||
if result["type"] != data_entry_flow.RESULT_TYPE_FORM or _has_error(
|
||||
result["errors"]
|
||||
):
|
||||
return result
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_ELMAX_USERNAME: username,
|
||||
CONF_ELMAX_PASSWORD: password,
|
||||
},
|
||||
)
|
||||
if result2["type"] != data_entry_flow.RESULT_TYPE_FORM or _has_error(
|
||||
result2["errors"]
|
||||
):
|
||||
return result2
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_ELMAX_PANEL_NAME: panel_name,
|
||||
CONF_ELMAX_PANEL_PIN: panel_pin,
|
||||
},
|
||||
)
|
||||
return result3
|
||||
|
||||
|
||||
async def _reauth(hass):
|
||||
|
||||
# Trigger reauth
|
||||
result2 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_REAUTH},
|
||||
data={
|
||||
CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID,
|
||||
CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN,
|
||||
CONF_ELMAX_USERNAME: MOCK_USERNAME,
|
||||
CONF_ELMAX_PASSWORD: MOCK_PASSWORD,
|
||||
},
|
||||
)
|
||||
if result2["type"] != data_entry_flow.RESULT_TYPE_FORM or _has_error(
|
||||
result2["errors"]
|
||||
):
|
||||
return result2
|
||||
|
||||
# Perform reauth confirm step
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID,
|
||||
CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN,
|
||||
CONF_ELMAX_USERNAME: MOCK_USERNAME,
|
||||
CONF_ELMAX_PASSWORD: MOCK_PASSWORD,
|
||||
},
|
||||
)
|
||||
return result3
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_one_config_allowed(hass):
|
||||
"""Test that only one Elmax configuration is allowed for each panel."""
|
||||
# Setup once.
|
||||
attempt1 = await _bootstrap(hass)
|
||||
assert attempt1["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
# Attempt to add another instance of the integration for the very same panel, it must fail.
|
||||
attempt2 = await _bootstrap(hass)
|
||||
assert attempt2["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert attempt2["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_invalid_credentials(hass):
|
||||
"""Test that invalid credentials throws an error."""
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.login",
|
||||
side_effect=ElmaxBadLoginError(),
|
||||
):
|
||||
result = await _bootstrap(
|
||||
hass, username="wrong_user_name@email.com", password="incorrect_password"
|
||||
)
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "bad_auth"}
|
||||
|
||||
|
||||
async def test_connection_error(hass):
|
||||
"""Test other than invalid credentials throws an error."""
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.login",
|
||||
side_effect=ElmaxNetworkError(),
|
||||
):
|
||||
result = await _bootstrap(
|
||||
hass, username="wrong_user_name@email.com", password="incorrect_password"
|
||||
)
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "network_error"}
|
||||
|
||||
|
||||
async def test_unhandled_error(hass):
|
||||
"""Test unhandled exceptions."""
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.get_panel_status",
|
||||
side_effect=Exception(),
|
||||
):
|
||||
result = await _bootstrap(hass)
|
||||
assert result["step_id"] == "panels"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "unknown_error"}
|
||||
|
||||
|
||||
async def test_invalid_pin(hass):
|
||||
"""Test error is thrown when a wrong pin is used to pair a panel."""
|
||||
# Simulate bad pin response.
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.get_panel_status",
|
||||
side_effect=ElmaxBadPinError(),
|
||||
):
|
||||
result = await _bootstrap(hass)
|
||||
assert result["step_id"] == "panels"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "invalid_pin"}
|
||||
|
||||
|
||||
async def test_no_online_panel(hass):
|
||||
"""Test no-online panel is available."""
|
||||
# Simulate low-level api returns no panels.
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.list_control_panels",
|
||||
return_value=[],
|
||||
):
|
||||
result = await _bootstrap(hass)
|
||||
assert result["step_id"] == "user"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "no_panel_online"}
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test that the user step works."""
|
||||
result = await _bootstrap(hass)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID,
|
||||
CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN,
|
||||
CONF_ELMAX_USERNAME: MOCK_USERNAME,
|
||||
CONF_ELMAX_PASSWORD: MOCK_PASSWORD,
|
||||
}
|
||||
|
||||
|
||||
async def test_show_reauth(hass):
|
||||
"""Test that the reauth form shows."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_REAUTH},
|
||||
data={
|
||||
CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID,
|
||||
CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN,
|
||||
CONF_ELMAX_USERNAME: MOCK_USERNAME,
|
||||
CONF_ELMAX_PASSWORD: MOCK_PASSWORD,
|
||||
},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
|
||||
|
||||
async def test_reauth_flow(hass):
|
||||
"""Test that the reauth flow works."""
|
||||
# Simulate a first setup
|
||||
await _bootstrap(hass)
|
||||
# Trigger reauth
|
||||
result = await _reauth(hass)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "reauth_successful"
|
||||
|
||||
|
||||
async def test_reauth_panel_disappeared(hass):
|
||||
"""Test that the case where panel is no longer associated with the user."""
|
||||
# Simulate a first setup
|
||||
await _bootstrap(hass)
|
||||
# Trigger reauth
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.list_control_panels",
|
||||
return_value=[],
|
||||
):
|
||||
result = await _reauth(hass)
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "reauth_panel_disappeared"}
|
||||
|
||||
|
||||
async def test_reauth_invalid_pin(hass):
|
||||
"""Test that the case where panel is no longer associated with the user."""
|
||||
# Simulate a first setup
|
||||
await _bootstrap(hass)
|
||||
# Trigger reauth
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.get_panel_status",
|
||||
side_effect=ElmaxBadPinError(),
|
||||
):
|
||||
result = await _reauth(hass)
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "invalid_pin"}
|
||||
|
||||
|
||||
async def test_reauth_bad_login(hass):
|
||||
"""Test bad login attempt at reauth time."""
|
||||
# Simulate a first setup
|
||||
await _bootstrap(hass)
|
||||
# Trigger reauth
|
||||
with patch(
|
||||
"elmax_api.http.Elmax.login",
|
||||
side_effect=ElmaxBadLoginError(),
|
||||
):
|
||||
result = await _reauth(hass)
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "bad_auth"}
|
Loading…
Add table
Add a link
Reference in a new issue