mirror of https://github.com/home-assistant/core
469 lines
14 KiB
Python
469 lines
14 KiB
Python
"""Test the Risco config flow."""
|
|
|
|
from unittest.mock import PropertyMock, patch
|
|
|
|
import pytest
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import config_entries
|
|
from homeassistant.components.risco.config_flow import (
|
|
CannotConnectError,
|
|
UnauthorizedError,
|
|
)
|
|
from homeassistant.components.risco.const import CONF_COMMUNICATION_DELAY, DOMAIN
|
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.data_entry_flow import FlowResultType
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
TEST_SITE_NAME = "test-site-name"
|
|
TEST_CLOUD_DATA = {
|
|
"username": "test-username",
|
|
"password": "test-password",
|
|
"pin": "1234",
|
|
}
|
|
|
|
TEST_LOCAL_DATA = {
|
|
"host": "test-host",
|
|
"port": 5004,
|
|
"pin": "1234",
|
|
}
|
|
|
|
TEST_RISCO_TO_HA = {
|
|
"arm": "armed_away",
|
|
"partial_arm": "armed_home",
|
|
"A": "armed_home",
|
|
"B": "armed_home",
|
|
"C": "armed_night",
|
|
"D": "armed_night",
|
|
}
|
|
|
|
TEST_HA_TO_RISCO = {
|
|
"armed_away": "arm",
|
|
"armed_home": "partial_arm",
|
|
"armed_night": "C",
|
|
}
|
|
|
|
TEST_OPTIONS = {
|
|
"code_arm_required": True,
|
|
"code_disarm_required": True,
|
|
}
|
|
|
|
TEST_ADVANCED_OPTIONS = {
|
|
"scan_interval": 10,
|
|
"concurrency": 3,
|
|
}
|
|
|
|
|
|
async def test_cloud_form(hass: HomeAssistant) -> None:
|
|
"""Test we get the cloud form."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.MENU
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "cloud"}
|
|
)
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["errors"] == {}
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.site_name",
|
|
new_callable=PropertyMock(return_value=TEST_SITE_NAME),
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.close"
|
|
) as mock_close,
|
|
patch(
|
|
"homeassistant.components.risco.async_setup_entry",
|
|
return_value=True,
|
|
) as mock_setup_entry,
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_CLOUD_DATA
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == TEST_SITE_NAME
|
|
assert result3["data"] == TEST_CLOUD_DATA
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
mock_close.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("exception", "error"),
|
|
[
|
|
(UnauthorizedError, "invalid_auth"),
|
|
(CannotConnectError, "cannot_connect"),
|
|
(Exception, "unknown"),
|
|
],
|
|
)
|
|
async def test_cloud_error(hass: HomeAssistant, login_with_error, error) -> None:
|
|
"""Test we handle config flow errors."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "cloud"}
|
|
)
|
|
|
|
with patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.close"
|
|
) as mock_close:
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_CLOUD_DATA
|
|
)
|
|
|
|
mock_close.assert_awaited_once()
|
|
assert result3["type"] is FlowResultType.FORM
|
|
assert result3["errors"] == {"base": error}
|
|
|
|
|
|
async def test_form_cloud_already_exists(hass: HomeAssistant) -> None:
|
|
"""Test that a flow with an existing username aborts."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_CLOUD_DATA["username"],
|
|
data=TEST_CLOUD_DATA,
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "cloud"}
|
|
)
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_CLOUD_DATA
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "already_configured"
|
|
|
|
|
|
async def test_form_reauth(
|
|
hass: HomeAssistant, cloud_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test reauthenticate."""
|
|
|
|
result = await cloud_config_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {}
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.site_name",
|
|
new_callable=PropertyMock(return_value=TEST_SITE_NAME),
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.close",
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.async_setup_entry",
|
|
return_value=True,
|
|
) as mock_setup_entry,
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {**TEST_CLOUD_DATA, CONF_PASSWORD: "new_password"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
assert cloud_config_entry.data[CONF_PASSWORD] == "new_password"
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_form_reauth_with_new_username(
|
|
hass: HomeAssistant, cloud_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test reauthenticate with new username."""
|
|
|
|
result = await cloud_config_entry.start_reauth_flow(hass)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["errors"] == {}
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.login",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.site_name",
|
|
new_callable=PropertyMock(return_value=TEST_SITE_NAME),
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoCloud.close",
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.async_setup_entry",
|
|
return_value=True,
|
|
) as mock_setup_entry,
|
|
):
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {**TEST_CLOUD_DATA, CONF_USERNAME: "new_user"}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result2["type"] is FlowResultType.ABORT
|
|
assert result2["reason"] == "reauth_successful"
|
|
assert cloud_config_entry.data[CONF_USERNAME] == "new_user"
|
|
assert cloud_config_entry.unique_id == "new_user"
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
async def test_local_form(hass: HomeAssistant) -> None:
|
|
"""Test we get the local form."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
assert result["type"] is FlowResultType.MENU
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "local"}
|
|
)
|
|
|
|
assert result2["type"] is FlowResultType.FORM
|
|
assert result2["errors"] == {}
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.connect",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.id",
|
|
new_callable=PropertyMock(return_value=TEST_SITE_NAME),
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.disconnect"
|
|
) as mock_close,
|
|
patch(
|
|
"homeassistant.components.risco.async_setup_entry",
|
|
return_value=True,
|
|
) as mock_setup_entry,
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_LOCAL_DATA
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
expected_data = {
|
|
**TEST_LOCAL_DATA,
|
|
"type": "local",
|
|
CONF_COMMUNICATION_DELAY: 0,
|
|
}
|
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
|
assert result3["title"] == TEST_SITE_NAME
|
|
assert result3["data"] == expected_data
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
mock_close.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("exception", "error"),
|
|
[
|
|
(UnauthorizedError, "invalid_auth"),
|
|
(CannotConnectError, "cannot_connect"),
|
|
(Exception, "unknown"),
|
|
],
|
|
)
|
|
async def test_local_error(hass: HomeAssistant, connect_with_error, error) -> None:
|
|
"""Test we handle config flow errors."""
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "local"}
|
|
)
|
|
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_LOCAL_DATA
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.FORM
|
|
assert result3["errors"] == {"base": error}
|
|
|
|
|
|
async def test_form_local_already_exists(hass: HomeAssistant) -> None:
|
|
"""Test that a flow with an existing host aborts."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_SITE_NAME,
|
|
data=TEST_LOCAL_DATA,
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
|
)
|
|
|
|
result2 = await hass.config_entries.flow.async_configure(
|
|
result["flow_id"], {"next_step_id": "local"}
|
|
)
|
|
|
|
with (
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.connect",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.id",
|
|
new_callable=PropertyMock(return_value=TEST_SITE_NAME),
|
|
),
|
|
patch(
|
|
"homeassistant.components.risco.config_flow.RiscoLocal.disconnect",
|
|
),
|
|
):
|
|
result3 = await hass.config_entries.flow.async_configure(
|
|
result2["flow_id"], TEST_LOCAL_DATA
|
|
)
|
|
|
|
assert result3["type"] is FlowResultType.ABORT
|
|
assert result3["reason"] == "already_configured"
|
|
|
|
|
|
async def test_options_flow(hass: HomeAssistant) -> None:
|
|
"""Test options flow."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_CLOUD_DATA["username"],
|
|
data=TEST_CLOUD_DATA,
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "init"
|
|
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_OPTIONS,
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "risco_to_ha"
|
|
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_RISCO_TO_HA,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "ha_to_risco"
|
|
|
|
with patch("homeassistant.components.risco.async_setup_entry", return_value=True):
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_HA_TO_RISCO,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert entry.options == {
|
|
**TEST_OPTIONS,
|
|
"risco_states_to_ha": TEST_RISCO_TO_HA,
|
|
"ha_states_to_risco": TEST_HA_TO_RISCO,
|
|
}
|
|
|
|
|
|
async def test_advanced_options_flow(hass: HomeAssistant) -> None:
|
|
"""Test options flow."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_CLOUD_DATA["username"],
|
|
data=TEST_CLOUD_DATA,
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.options.async_init(
|
|
entry.entry_id, context={"show_advanced_options": True}
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "init"
|
|
assert "concurrency" in result["data_schema"].schema
|
|
assert "scan_interval" in result["data_schema"].schema
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"], user_input={**TEST_OPTIONS, **TEST_ADVANCED_OPTIONS}
|
|
)
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "risco_to_ha"
|
|
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_RISCO_TO_HA,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.FORM
|
|
assert result["step_id"] == "ha_to_risco"
|
|
|
|
with patch("homeassistant.components.risco.async_setup_entry", return_value=True):
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_HA_TO_RISCO,
|
|
)
|
|
|
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
|
assert entry.options == {
|
|
**TEST_OPTIONS,
|
|
**TEST_ADVANCED_OPTIONS,
|
|
"risco_states_to_ha": TEST_RISCO_TO_HA,
|
|
"ha_states_to_risco": TEST_HA_TO_RISCO,
|
|
}
|
|
|
|
|
|
async def test_ha_to_risco_schema(hass: HomeAssistant) -> None:
|
|
"""Test that the schema for the ha-to-risco mapping step is generated properly."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
unique_id=TEST_CLOUD_DATA["username"],
|
|
data=TEST_CLOUD_DATA,
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
result = await hass.config_entries.options.async_init(entry.entry_id)
|
|
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_OPTIONS,
|
|
)
|
|
result = await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input=TEST_RISCO_TO_HA,
|
|
)
|
|
|
|
# Test an HA state that isn't used
|
|
with pytest.raises(vol.error.Invalid):
|
|
await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input={**TEST_HA_TO_RISCO, "armed_custom_bypass": "D"},
|
|
)
|
|
|
|
# Test a combo that can't be selected
|
|
with pytest.raises(vol.error.Invalid):
|
|
await hass.config_entries.options.async_configure(
|
|
result["flow_id"],
|
|
user_input={**TEST_HA_TO_RISCO, "armed_night": "A"},
|
|
)
|