From 7c585bd380407cb325cb8a43832576e12604773f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 31 Aug 2022 10:52:41 +0200 Subject: [PATCH] Fix sync context in icloud (#77582) Co-authored-by: Martin Hjelmare --- homeassistant/components/icloud/account.py | 8 +++- .../components/icloud/config_flow.py | 8 ++-- tests/components/icloud/conftest.py | 7 --- tests/components/icloud/const.py | 30 +++++++++++++ tests/components/icloud/test_config_flow.py | 32 ++++++------- tests/components/icloud/test_init.py | 45 +++++++++++++++++++ 6 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 tests/components/icloud/const.py create mode 100644 tests/components/icloud/test_init.py diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index fe542f6bf0f..c51f6a3ac26 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -132,7 +132,7 @@ class IcloudAccount: self._config_entry.data[CONF_USERNAME], ) - self._config_entry.async_start_reauth(self.hass) + self._require_reauth() return try: @@ -164,7 +164,7 @@ class IcloudAccount: return if self.api.requires_2fa: - self._config_entry.async_start_reauth(self.hass) + self._require_reauth() return api_devices = {} @@ -230,6 +230,10 @@ class IcloudAccount: utcnow() + timedelta(minutes=self._fetch_interval), ) + def _require_reauth(self): + """Require the user to log in again.""" + self.hass.add_job(self._config_entry.async_start_reauth, self.hass) + def _determine_interval(self) -> int: """Calculate new interval between two API fetch (in minutes).""" intervals = {"default": self._max_interval} diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index e82fb230eb1..6cdde2249c8 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -55,7 +55,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._trusted_device = None self._verification_code = None - self._existing_entry = None + self._existing_entry_data = None self._description_placeholders = None def _show_setup_form(self, user_input=None, errors=None, step_id="user"): @@ -99,8 +99,8 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # If an existing entry was found, meaning this is a password update attempt, # use those to get config values that aren't changing - if self._existing_entry: - extra_inputs = self._existing_entry + if self._existing_entry_data: + extra_inputs = self._existing_entry_data self._username = extra_inputs[CONF_USERNAME] self._with_family = extra_inputs.get(CONF_WITH_FAMILY, DEFAULT_WITH_FAMILY) @@ -183,7 +183,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Store existing entry data so it can be used later and set unique ID # so existing config entry can be updated await self.async_set_unique_id(self.context["unique_id"]) - self._existing_entry = {**entry_data} + self._existing_entry_data = {**entry_data} self._description_placeholders = {"username": entry_data[CONF_USERNAME]} return await self.async_step_reauth_confirm() diff --git a/tests/components/icloud/conftest.py b/tests/components/icloud/conftest.py index c8195471878..2437d05f575 100644 --- a/tests/components/icloud/conftest.py +++ b/tests/components/icloud/conftest.py @@ -4,13 +4,6 @@ from unittest.mock import patch import pytest -@pytest.fixture(name="icloud_bypass_setup", autouse=True) -def icloud_bypass_setup_fixture(): - """Mock component setup.""" - with patch("homeassistant.components.icloud.async_setup_entry", return_value=True): - yield - - @pytest.fixture(autouse=True) def icloud_not_create_dir(): """Mock component setup.""" diff --git a/tests/components/icloud/const.py b/tests/components/icloud/const.py new file mode 100644 index 00000000000..459f18e17cc --- /dev/null +++ b/tests/components/icloud/const.py @@ -0,0 +1,30 @@ +"""Constants for the iCloud tests.""" +from homeassistant.components.icloud.const import ( + CONF_GPS_ACCURACY_THRESHOLD, + CONF_MAX_INTERVAL, + CONF_WITH_FAMILY, + DEFAULT_GPS_ACCURACY_THRESHOLD, + DEFAULT_MAX_INTERVAL, + DEFAULT_WITH_FAMILY, +) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +USERNAME = "username@me.com" +USERNAME_2 = "second_username@icloud.com" +PASSWORD = "password" +PASSWORD_2 = "second_password" +WITH_FAMILY = True +MAX_INTERVAL = 15 +GPS_ACCURACY_THRESHOLD = 250 + +MOCK_CONFIG = { + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_WITH_FAMILY: DEFAULT_WITH_FAMILY, + CONF_MAX_INTERVAL: DEFAULT_MAX_INTERVAL, + CONF_GPS_ACCURACY_THRESHOLD: DEFAULT_GPS_ACCURACY_THRESHOLD, +} + +TRUSTED_DEVICES = [ + {"deviceType": "SMS", "areaCode": "", "phoneNumber": "*******58", "deviceId": "1"} +] diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index 598e44248fa..ef866dd4aeb 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -22,27 +22,23 @@ from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from .const import ( + MOCK_CONFIG, + PASSWORD, + PASSWORD_2, + TRUSTED_DEVICES, + USERNAME, + WITH_FAMILY, +) + from tests.common import MockConfigEntry -USERNAME = "username@me.com" -USERNAME_2 = "second_username@icloud.com" -PASSWORD = "password" -PASSWORD_2 = "second_password" -WITH_FAMILY = True -MAX_INTERVAL = 15 -GPS_ACCURACY_THRESHOLD = 250 -MOCK_CONFIG = { - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - CONF_WITH_FAMILY: DEFAULT_WITH_FAMILY, - CONF_MAX_INTERVAL: DEFAULT_MAX_INTERVAL, - CONF_GPS_ACCURACY_THRESHOLD: DEFAULT_GPS_ACCURACY_THRESHOLD, -} - -TRUSTED_DEVICES = [ - {"deviceType": "SMS", "areaCode": "", "phoneNumber": "*******58", "deviceId": "1"} -] +@pytest.fixture(name="icloud_bypass_setup", autouse=True) +def icloud_bypass_setup_fixture(): + """Mock component setup.""" + with patch("homeassistant.components.icloud.async_setup_entry", return_value=True): + yield @pytest.fixture(name="service") diff --git a/tests/components/icloud/test_init.py b/tests/components/icloud/test_init.py new file mode 100644 index 00000000000..60ab00a6262 --- /dev/null +++ b/tests/components/icloud/test_init.py @@ -0,0 +1,45 @@ +"""Tests for the iCloud config flow.""" +from unittest.mock import Mock, patch + +import pytest + +from homeassistant.components.icloud.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from .const import MOCK_CONFIG, USERNAME + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="service_2fa") +def mock_controller_2fa_service(): + """Mock a successful 2fa service.""" + with patch( + "homeassistant.components.icloud.account.PyiCloudService" + ) as service_mock: + service_mock.return_value.requires_2fa = True + service_mock.return_value.requires_2sa = True + service_mock.return_value.validate_2fa_code = Mock(return_value=True) + service_mock.return_value.is_trusted_session = False + yield service_mock + + +@pytest.mark.usefixtures("service_2fa") +async def test_setup_2fa(hass: HomeAssistant) -> None: + """Test that invalid login triggers reauth flow.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data=MOCK_CONFIG, entry_id="test", unique_id=USERNAME + ) + config_entry.add_to_hass(hass) + + assert config_entry.state is ConfigEntryState.NOT_LOADED + assert not hass.config_entries.flow.async_progress() + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + in_progress_flows = hass.config_entries.flow.async_progress() + assert len(in_progress_flows) == 1 + assert in_progress_flows[0]["context"]["unique_id"] == config_entry.unique_id