From 3bccac9949eadc4bcb088e0b12df2d7c88f2e7fd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 Jul 2022 11:37:59 +0200 Subject: [PATCH] Verisure config flow cleanups (#75144) --- .../components/verisure/config_flow.py | 37 +- tests/components/verisure/conftest.py | 50 +++ tests/components/verisure/test_config_flow.py | 411 +++++++++--------- 3 files changed, 263 insertions(+), 235 deletions(-) create mode 100644 tests/components/verisure/conftest.py diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index 41687dbc6a4..119a9250736 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -34,8 +34,8 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): email: str entry: ConfigEntry - installations: dict[str, str] password: str + verisure: Verisure @staticmethod @callback @@ -50,11 +50,13 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} 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] ) try: - await self.hass.async_add_executor_job(verisure.login) + await self.hass.async_add_executor_job(self.verisure.login) except VerisureLoginError as ex: LOGGER.debug("Could not log in to Verisure, %s", ex) errors["base"] = "invalid_auth" @@ -62,13 +64,6 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): LOGGER.debug("Unexpected response from Verisure, %s", ex) errors["base"] = "unknown" 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 self.async_show_form( @@ -86,22 +81,26 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Select Verisure installation to add.""" - if len(self.installations) == 1: - user_input = {CONF_GIID: list(self.installations)[0]} + installations = { + inst["giid"]: f"{inst['alias']} ({inst['street']})" + for inst in self.verisure.installations or [] + } if user_input is None: - return self.async_show_form( - step_id="installation", - data_schema=vol.Schema( - {vol.Required(CONF_GIID): vol.In(self.installations)} - ), - ) + if len(installations) != 1: + return self.async_show_form( + step_id="installation", + 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]) self._abort_if_unique_id_configured() return self.async_create_entry( - title=self.installations[user_input[CONF_GIID]], + title=installations[user_input[CONF_GIID]], data={ CONF_EMAIL: self.email, CONF_PASSWORD: self.password, diff --git a/tests/components/verisure/conftest.py b/tests/components/verisure/conftest.py new file mode 100644 index 00000000000..f91215866d8 --- /dev/null +++ b/tests/components/verisure/conftest.py @@ -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 diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 03fa0fa82cc..d957709c878 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Verisure config flow.""" from __future__ import annotations -from unittest.mock import PropertyMock, patch +from unittest.mock import AsyncMock, MagicMock, patch import pytest 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 -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(hass: HomeAssistant) -> None: +async def test_full_user_flow_single_installation( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_verisure_config_flow: MagicMock, +) -> None: """Test a full user initiated configuration flow with a single installation.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["step_id"] == "user" - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {} + assert result.get("step_id") == "user" + assert result.get("type") == FlowResultType.FORM + assert result.get("errors") == {} + assert "flow_id" in result - with patch( - "homeassistant.components.verisure.config_flow.Verisure", - ) 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 + mock_verisure_config_flow.installations = [ + mock_verisure_config_flow.installations[0] + ] - 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() + 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.CREATE_ENTRY - assert result2["title"] == "ascending (12345th street)" - assert result2["data"] == { + assert result2.get("type") == FlowResultType.CREATE_ENTRY + assert result2.get("title") == "ascending (12345th street)" + assert result2.get("data") == { CONF_GIID: "12345", CONF_EMAIL: "verisure_my_pages@example.com", 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 -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.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["step_id"] == "user" - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {} + assert result.get("step_id") == "user" + assert result.get("type") == FlowResultType.FORM + assert result.get("errors") == {} + assert "flow_id" in result - with patch( - "homeassistant.components.verisure.config_flow.Verisure", - ) as mock_verisure: - type(mock_verisure.return_value).installations = PropertyMock( - return_value=TEST_INSTALLATIONS - ) - mock_verisure.login.return_value = True + 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() - 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.get("step_id") == "installation" + assert result2.get("type") == FlowResultType.FORM + assert result2.get("errors") is None + assert "flow_id" in result2 - assert result2["step_id"] == "installation" - assert result2["type"] == FlowResultType.FORM - assert result2["errors"] is None + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {"giid": "54321"} + ) + await hass.async_block_till_done() - with patch( - "homeassistant.components.verisure.async_setup_entry", - return_value=True, - ) 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"] == { + assert result3.get("type") == FlowResultType.CREATE_ENTRY + assert result3.get("title") == "descending (54321th street)" + assert result3.get("data") == { CONF_GIID: "54321", CONF_EMAIL: "verisure_my_pages@example.com", 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 -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.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "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 "flow_id" in result - assert result2["type"] == FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_unknown_error(hass: HomeAssistant) -> None: - """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} + mock_verisure_config_flow.login.side_effect = side_effect + 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() - with patch( - "homeassistant.components.verisure.config_flow.Verisure.login", - side_effect=VerisureError, - ): - 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.get("type") == FlowResultType.FORM + assert result2.get("step_id") == "user" + assert result2.get("errors") == {"base": error} + assert "flow_id" in result2 - assert result2["type"] == FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "unknown"} + mock_verisure_config_flow.login.side_effect = None + mock_verisure_config_flow.installations = [ + 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: @@ -178,144 +178,121 @@ async def test_dhcp(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_DHCP}, ) - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" + assert result.get("type") == FlowResultType.FORM + 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.""" - 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) + mock_config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, - "unique_id": entry.unique_id, - "entry_id": entry.entry_id, + "unique_id": mock_config_entry.unique_id, + "entry_id": mock_config_entry.entry_id, }, - data=entry.data, + data=mock_config_entry.data, ) - assert result["step_id"] == "reauth_confirm" - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {} + assert result.get("step_id") == "reauth_confirm" + assert result.get("type") == FlowResultType.FORM + assert result.get("errors") == {} + assert "flow_id" in result - with patch( - "homeassistant.components.verisure.config_flow.Verisure.login", - return_value=True, - ) as mock_verisure, patch( - "homeassistant.components.verisure.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "email": "verisure_my_pages@example.com", - "password": "correct horse battery staple", - }, - ) - await hass.async_block_till_done() + result2 = await hass.config_entries.flow.async_configure( + 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["reason"] == "reauth_successful" - assert entry.data == { + assert result2.get("type") == FlowResultType.ABORT + assert result2.get("reason") == "reauth_successful" + assert mock_config_entry.data == { CONF_GIID: "12345", CONF_EMAIL: "verisure_my_pages@example.com", 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 -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.""" - 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) + mock_config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, - "unique_id": entry.unique_id, - "entry_id": entry.entry_id, + "unique_id": mock_config_entry.unique_id, + "entry_id": mock_config_entry.entry_id, }, - data=entry.data, + data=mock_config_entry.data, ) - with patch( - "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 "flow_id" in result - assert result2["step_id"] == "reauth_confirm" - assert result2["type"] == FlowResultType.FORM - assert result2["errors"] == {"base": "invalid_auth"} - - -async def test_reauth_flow_unknown_error(hass: HomeAssistant) -> None: - """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!", + mock_verisure_config_flow.login.side_effect = side_effect + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "email": "verisure_my_pages@example.com", + "password": "WrOngP4ssw0rd!", }, ) - entry.add_to_hass(hass) + await hass.async_block_till_done() - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "unique_id": entry.unique_id, - "entry_id": entry.entry_id, + assert result2.get("step_id") == "reauth_confirm" + assert result2.get("type") == FlowResultType.FORM + assert result2.get("errors") == {"base": error} + assert "flow_id" in result2 + + 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( - "homeassistant.components.verisure.config_flow.Verisure.login", - side_effect=VerisureError, - ): - 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 mock_config_entry.data == { + CONF_GIID: "12345", + CONF_EMAIL: "verisure_my_pages@example.com", + CONF_PASSWORD: "correct horse battery staple", + } - assert result2["step_id"] == "reauth_confirm" - assert result2["type"] == FlowResultType.FORM - assert result2["errors"] == {"base": "unknown"} + assert len(mock_verisure_config_flow.login.mock_calls) == 2 + assert len(mock_setup_entry.mock_calls) == 1 @pytest.mark.parametrize( @@ -362,16 +339,17 @@ async def test_options_flow( result = await hass.config_entries.options.async_init(entry.entry_id) - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "init" + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == "init" + assert "flow_id" in result result = await hass.config_entries.options.async_configure( result["flow_id"], user_input=input, ) - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["data"] == output + assert result.get("type") == FlowResultType.CREATE_ENTRY + assert result.get("data") == output 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) - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "init" - assert result["errors"] == {} + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == "init" + assert result.get("errors") == {} + assert "flow_id" in result result = await hass.config_entries.options.async_configure( 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["step_id"] == "init" - assert result["errors"] == {"base": "code_format_mismatch"} + assert result.get("type") == FlowResultType.FORM + assert result.get("step_id") == "init" + assert result.get("errors") == {"base": "code_format_mismatch"}