Verisure config flow cleanups (#75144)

This commit is contained in:
Franck Nijhof 2022-07-14 11:37:59 +02:00 committed by GitHub
parent 169264db66
commit 3bccac9949
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 263 additions and 235 deletions

View file

@ -34,8 +34,8 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
email: str email: str
entry: ConfigEntry entry: ConfigEntry
installations: dict[str, str]
password: str password: str
verisure: Verisure
@staticmethod @staticmethod
@callback @callback
@ -50,11 +50,13 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {} errors: dict[str, str] = {}
if user_input is not None: if user_input is not None:
verisure = Verisure( self.email = user_input[CONF_EMAIL]
self.password = user_input[CONF_PASSWORD]
self.verisure = Verisure(
username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD] username=user_input[CONF_EMAIL], password=user_input[CONF_PASSWORD]
) )
try: try:
await self.hass.async_add_executor_job(verisure.login) await self.hass.async_add_executor_job(self.verisure.login)
except VerisureLoginError as ex: except VerisureLoginError as ex:
LOGGER.debug("Could not log in to Verisure, %s", ex) LOGGER.debug("Could not log in to Verisure, %s", ex)
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
@ -62,13 +64,6 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
LOGGER.debug("Unexpected response from Verisure, %s", ex) LOGGER.debug("Unexpected response from Verisure, %s", ex)
errors["base"] = "unknown" errors["base"] = "unknown"
else: else:
self.email = user_input[CONF_EMAIL]
self.password = user_input[CONF_PASSWORD]
self.installations = {
inst["giid"]: f"{inst['alias']} ({inst['street']})"
for inst in verisure.installations
}
return await self.async_step_installation() return await self.async_step_installation()
return self.async_show_form( return self.async_show_form(
@ -86,22 +81,26 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> FlowResult: ) -> FlowResult:
"""Select Verisure installation to add.""" """Select Verisure installation to add."""
if len(self.installations) == 1: installations = {
user_input = {CONF_GIID: list(self.installations)[0]} inst["giid"]: f"{inst['alias']} ({inst['street']})"
for inst in self.verisure.installations or []
}
if user_input is None: if user_input is None:
return self.async_show_form( if len(installations) != 1:
step_id="installation", return self.async_show_form(
data_schema=vol.Schema( step_id="installation",
{vol.Required(CONF_GIID): vol.In(self.installations)} data_schema=vol.Schema(
), {vol.Required(CONF_GIID): vol.In(installations)}
) ),
)
user_input = {CONF_GIID: list(installations)[0]}
await self.async_set_unique_id(user_input[CONF_GIID]) await self.async_set_unique_id(user_input[CONF_GIID])
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
return self.async_create_entry( return self.async_create_entry(
title=self.installations[user_input[CONF_GIID]], title=installations[user_input[CONF_GIID]],
data={ data={
CONF_EMAIL: self.email, CONF_EMAIL: self.email,
CONF_PASSWORD: self.password, CONF_PASSWORD: self.password,

View file

@ -0,0 +1,50 @@
"""Fixtures for Verisure integration tests."""
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from homeassistant.components.verisure.const import CONF_GIID, DOMAIN
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="12345",
data={
CONF_EMAIL: "verisure_my_pages@example.com",
CONF_GIID: "12345",
CONF_PASSWORD: "SuperS3cr3t!",
},
)
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.verisure.async_setup_entry", return_value=True
) as mock_setup:
yield mock_setup
@pytest.fixture
def mock_verisure_config_flow() -> Generator[None, MagicMock, None]:
"""Return a mocked Tailscale client."""
with patch(
"homeassistant.components.verisure.config_flow.Verisure", autospec=True
) as verisure_mock:
verisure = verisure_mock.return_value
verisure.login.return_value = True
verisure.installations = [
{"giid": "12345", "alias": "ascending", "street": "12345th street"},
{"giid": "54321", "alias": "descending", "street": "54321th street"},
]
yield verisure

View file

@ -1,7 +1,7 @@
"""Test the Verisure config flow.""" """Test the Verisure config flow."""
from __future__ import annotations from __future__ import annotations
from unittest.mock import PropertyMock, patch from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
from verisure import Error as VerisureError, LoginError as VerisureLoginError from verisure import Error as VerisureError, LoginError as VerisureLoginError
@ -21,151 +21,151 @@ from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
TEST_INSTALLATIONS = [
{"giid": "12345", "alias": "ascending", "street": "12345th street"},
{"giid": "54321", "alias": "descending", "street": "54321th street"},
]
TEST_INSTALLATION = [TEST_INSTALLATIONS[0]]
async def test_full_user_flow_single_installation(
async def test_full_user_flow_single_installation(hass: HomeAssistant) -> None: hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_verisure_config_flow: MagicMock,
) -> None:
"""Test a full user initiated configuration flow with a single installation.""" """Test a full user initiated configuration flow with a single installation."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
assert result["step_id"] == "user" assert result.get("step_id") == "user"
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["errors"] == {} assert result.get("errors") == {}
assert "flow_id" in result
with patch( mock_verisure_config_flow.installations = [
"homeassistant.components.verisure.config_flow.Verisure", mock_verisure_config_flow.installations[0]
) as mock_verisure, patch( ]
"homeassistant.components.verisure.async_setup_entry",
return_value=True,
) as mock_setup_entry:
type(mock_verisure.return_value).installations = PropertyMock(
return_value=TEST_INSTALLATION
)
mock_verisure.login.return_value = True
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"email": "verisure_my_pages@example.com", "email": "verisure_my_pages@example.com",
"password": "SuperS3cr3t!", "password": "SuperS3cr3t!",
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY assert result2.get("type") == FlowResultType.CREATE_ENTRY
assert result2["title"] == "ascending (12345th street)" assert result2.get("title") == "ascending (12345th street)"
assert result2["data"] == { assert result2.get("data") == {
CONF_GIID: "12345", CONF_GIID: "12345",
CONF_EMAIL: "verisure_my_pages@example.com", CONF_EMAIL: "verisure_my_pages@example.com",
CONF_PASSWORD: "SuperS3cr3t!", CONF_PASSWORD: "SuperS3cr3t!",
} }
assert len(mock_verisure.mock_calls) == 2 assert len(mock_verisure_config_flow.login.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_full_user_flow_multiple_installations(hass: HomeAssistant) -> None: async def test_full_user_flow_multiple_installations(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_verisure_config_flow: MagicMock,
) -> None:
"""Test a full user initiated configuration flow with multiple installations.""" """Test a full user initiated configuration flow with multiple installations."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
assert result["step_id"] == "user" assert result.get("step_id") == "user"
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["errors"] == {} assert result.get("errors") == {}
assert "flow_id" in result
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.verisure.config_flow.Verisure", result["flow_id"],
) as mock_verisure: {
type(mock_verisure.return_value).installations = PropertyMock( "email": "verisure_my_pages@example.com",
return_value=TEST_INSTALLATIONS "password": "SuperS3cr3t!",
) },
mock_verisure.login.return_value = True )
await hass.async_block_till_done()
result2 = await hass.config_entries.flow.async_configure( assert result2.get("step_id") == "installation"
result["flow_id"], assert result2.get("type") == FlowResultType.FORM
{ assert result2.get("errors") is None
"email": "verisure_my_pages@example.com", assert "flow_id" in result2
"password": "SuperS3cr3t!",
},
)
await hass.async_block_till_done()
assert result2["step_id"] == "installation" result3 = await hass.config_entries.flow.async_configure(
assert result2["type"] == FlowResultType.FORM result2["flow_id"], {"giid": "54321"}
assert result2["errors"] is None )
await hass.async_block_till_done()
with patch( assert result3.get("type") == FlowResultType.CREATE_ENTRY
"homeassistant.components.verisure.async_setup_entry", assert result3.get("title") == "descending (54321th street)"
return_value=True, assert result3.get("data") == {
) as mock_setup_entry:
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"], {"giid": "54321"}
)
await hass.async_block_till_done()
assert result3["type"] == FlowResultType.CREATE_ENTRY
assert result3["title"] == "descending (54321th street)"
assert result3["data"] == {
CONF_GIID: "54321", CONF_GIID: "54321",
CONF_EMAIL: "verisure_my_pages@example.com", CONF_EMAIL: "verisure_my_pages@example.com",
CONF_PASSWORD: "SuperS3cr3t!", CONF_PASSWORD: "SuperS3cr3t!",
} }
assert len(mock_verisure.mock_calls) == 2 assert len(mock_verisure_config_flow.login.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_invalid_login(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"side_effect,error",
[
(VerisureLoginError, "invalid_auth"),
(VerisureError, "unknown"),
],
)
async def test_verisure_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_verisure_config_flow: MagicMock,
side_effect: Exception,
error: str,
) -> None:
"""Test a flow with an invalid Verisure My Pages login.""" """Test a flow with an invalid Verisure My Pages login."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( assert "flow_id" in result
"homeassistant.components.verisure.config_flow.Verisure.login",
side_effect=VerisureLoginError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "SuperS3cr3t!",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM mock_verisure_config_flow.login.side_effect = side_effect
assert result2["step_id"] == "user" result2 = await hass.config_entries.flow.async_configure(
assert result2["errors"] == {"base": "invalid_auth"} result["flow_id"],
{
"email": "verisure_my_pages@example.com",
async def test_unknown_error(hass: HomeAssistant) -> None: "password": "SuperS3cr3t!",
"""Test a flow with an invalid Verisure My Pages login.""" },
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
await hass.async_block_till_done()
with patch( assert result2.get("type") == FlowResultType.FORM
"homeassistant.components.verisure.config_flow.Verisure.login", assert result2.get("step_id") == "user"
side_effect=VerisureError, assert result2.get("errors") == {"base": error}
): assert "flow_id" in result2
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "SuperS3cr3t!",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM mock_verisure_config_flow.login.side_effect = None
assert result2["step_id"] == "user" mock_verisure_config_flow.installations = [
assert result2["errors"] == {"base": "unknown"} mock_verisure_config_flow.installations[0]
]
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "SuperS3cr3t!",
},
)
await hass.async_block_till_done()
assert result3.get("type") == FlowResultType.CREATE_ENTRY
assert result3.get("title") == "ascending (12345th street)"
assert result3.get("data") == {
CONF_GIID: "12345",
CONF_EMAIL: "verisure_my_pages@example.com",
CONF_PASSWORD: "SuperS3cr3t!",
}
assert len(mock_verisure_config_flow.login.mock_calls) == 2
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp(hass: HomeAssistant) -> None: async def test_dhcp(hass: HomeAssistant) -> None:
@ -178,144 +178,121 @@ async def test_dhcp(hass: HomeAssistant) -> None:
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
) )
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["step_id"] == "user" assert result.get("step_id") == "user"
async def test_reauth_flow(hass: HomeAssistant) -> None: async def test_reauth_flow(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_verisure_config_flow: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test a reauthentication flow.""" """Test a reauthentication flow."""
entry = MockConfigEntry( mock_config_entry.add_to_hass(hass)
domain=DOMAIN,
unique_id="12345",
data={
CONF_EMAIL: "verisure_my_pages@example.com",
CONF_GIID: "12345",
CONF_PASSWORD: "SuperS3cr3t!",
},
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={ context={
"source": config_entries.SOURCE_REAUTH, "source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id, "unique_id": mock_config_entry.unique_id,
"entry_id": entry.entry_id, "entry_id": mock_config_entry.entry_id,
}, },
data=entry.data, data=mock_config_entry.data,
) )
assert result["step_id"] == "reauth_confirm" assert result.get("step_id") == "reauth_confirm"
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["errors"] == {} assert result.get("errors") == {}
assert "flow_id" in result
with patch( result2 = await hass.config_entries.flow.async_configure(
"homeassistant.components.verisure.config_flow.Verisure.login", result["flow_id"],
return_value=True, {
) as mock_verisure, patch( "email": "verisure_my_pages@example.com",
"homeassistant.components.verisure.async_setup_entry", "password": "correct horse battery staple",
return_value=True, },
) as mock_setup_entry: )
result2 = await hass.config_entries.flow.async_configure( await hass.async_block_till_done()
result["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "correct horse battery staple",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.ABORT assert result2.get("type") == FlowResultType.ABORT
assert result2["reason"] == "reauth_successful" assert result2.get("reason") == "reauth_successful"
assert entry.data == { assert mock_config_entry.data == {
CONF_GIID: "12345", CONF_GIID: "12345",
CONF_EMAIL: "verisure_my_pages@example.com", CONF_EMAIL: "verisure_my_pages@example.com",
CONF_PASSWORD: "correct horse battery staple", CONF_PASSWORD: "correct horse battery staple",
} }
assert len(mock_verisure.mock_calls) == 1 assert len(mock_verisure_config_flow.login.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_reauth_flow_invalid_login(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
"side_effect,error",
[
(VerisureLoginError, "invalid_auth"),
(VerisureError, "unknown"),
],
)
async def test_reauth_flow_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_verisure_config_flow: MagicMock,
mock_config_entry: MockConfigEntry,
side_effect: Exception,
error: str,
) -> None:
"""Test a reauthentication flow.""" """Test a reauthentication flow."""
entry = MockConfigEntry( mock_config_entry.add_to_hass(hass)
domain=DOMAIN,
unique_id="12345",
data={
CONF_EMAIL: "verisure_my_pages@example.com",
CONF_GIID: "12345",
CONF_PASSWORD: "SuperS3cr3t!",
},
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={ context={
"source": config_entries.SOURCE_REAUTH, "source": config_entries.SOURCE_REAUTH,
"unique_id": entry.unique_id, "unique_id": mock_config_entry.unique_id,
"entry_id": entry.entry_id, "entry_id": mock_config_entry.entry_id,
}, },
data=entry.data, data=mock_config_entry.data,
) )
with patch( assert "flow_id" in result
"homeassistant.components.verisure.config_flow.Verisure.login",
side_effect=VerisureLoginError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "WrOngP4ssw0rd!",
},
)
await hass.async_block_till_done()
assert result2["step_id"] == "reauth_confirm" mock_verisure_config_flow.login.side_effect = side_effect
assert result2["type"] == FlowResultType.FORM result2 = await hass.config_entries.flow.async_configure(
assert result2["errors"] == {"base": "invalid_auth"} result["flow_id"],
{
"email": "verisure_my_pages@example.com",
async def test_reauth_flow_unknown_error(hass: HomeAssistant) -> None: "password": "WrOngP4ssw0rd!",
"""Test a reauthentication flow, with an unknown error happening."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="12345",
data={
CONF_EMAIL: "verisure_my_pages@example.com",
CONF_GIID: "12345",
CONF_PASSWORD: "SuperS3cr3t!",
}, },
) )
entry.add_to_hass(hass) await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init( assert result2.get("step_id") == "reauth_confirm"
DOMAIN, assert result2.get("type") == FlowResultType.FORM
context={ assert result2.get("errors") == {"base": error}
"source": config_entries.SOURCE_REAUTH, assert "flow_id" in result2
"unique_id": entry.unique_id,
"entry_id": entry.entry_id, mock_verisure_config_flow.login.side_effect = None
mock_verisure_config_flow.installations = [
mock_verisure_config_flow.installations[0]
]
await hass.config_entries.flow.async_configure(
result2["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "correct horse battery staple",
}, },
data=entry.data,
) )
await hass.async_block_till_done()
with patch( assert mock_config_entry.data == {
"homeassistant.components.verisure.config_flow.Verisure.login", CONF_GIID: "12345",
side_effect=VerisureError, CONF_EMAIL: "verisure_my_pages@example.com",
): CONF_PASSWORD: "correct horse battery staple",
result2 = await hass.config_entries.flow.async_configure( }
result["flow_id"],
{
"email": "verisure_my_pages@example.com",
"password": "WrOngP4ssw0rd!",
},
)
await hass.async_block_till_done()
assert result2["step_id"] == "reauth_confirm" assert len(mock_verisure_config_flow.login.mock_calls) == 2
assert result2["type"] == FlowResultType.FORM assert len(mock_setup_entry.mock_calls) == 1
assert result2["errors"] == {"base": "unknown"}
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -362,16 +339,17 @@ async def test_options_flow(
result = await hass.config_entries.options.async_init(entry.entry_id) result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["step_id"] == "init" assert result.get("step_id") == "init"
assert "flow_id" in result
result = await hass.config_entries.options.async_configure( result = await hass.config_entries.options.async_configure(
result["flow_id"], result["flow_id"],
user_input=input, user_input=input,
) )
assert result["type"] == FlowResultType.CREATE_ENTRY assert result.get("type") == FlowResultType.CREATE_ENTRY
assert result["data"] == output assert result.get("data") == output
async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None: async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None:
@ -392,9 +370,10 @@ async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None:
result = await hass.config_entries.options.async_init(entry.entry_id) result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["step_id"] == "init" assert result.get("step_id") == "init"
assert result["errors"] == {} assert result.get("errors") == {}
assert "flow_id" in result
result = await hass.config_entries.options.async_configure( result = await hass.config_entries.options.async_configure(
result["flow_id"], result["flow_id"],
@ -404,6 +383,6 @@ async def test_options_flow_code_format_mismatch(hass: HomeAssistant) -> None:
}, },
) )
assert result["type"] == FlowResultType.FORM assert result.get("type") == FlowResultType.FORM
assert result["step_id"] == "init" assert result.get("step_id") == "init"
assert result["errors"] == {"base": "code_format_mismatch"} assert result.get("errors") == {"base": "code_format_mismatch"}