Add devices check to iCloud config flow (#31950)
* Add devices check to iCloud config flow * Some test rename * Bump pyicloud to catch KeyError
This commit is contained in:
parent
8c52e2c923
commit
2e802c88f8
8 changed files with 64 additions and 15 deletions
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Account already configured"
|
"already_configured": "Account already configured",
|
||||||
|
"no_device": "None of your devices have \"Find my iPhone\" activated"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"login": "Login error: please check your email & password",
|
"login": "Login error: please check your email & password",
|
||||||
|
|
|
@ -5,7 +5,11 @@ import operator
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from pyicloud import PyiCloudService
|
from pyicloud import PyiCloudService
|
||||||
from pyicloud.exceptions import PyiCloudFailedLoginException, PyiCloudNoDevicesException
|
from pyicloud.exceptions import (
|
||||||
|
PyiCloudFailedLoginException,
|
||||||
|
PyiCloudNoDevicesException,
|
||||||
|
PyiCloudServiceNotActivatedException,
|
||||||
|
)
|
||||||
from pyicloud.services.findmyiphone import AppleDevice
|
from pyicloud.services.findmyiphone import AppleDevice
|
||||||
|
|
||||||
from homeassistant.components.zone import async_active_zone
|
from homeassistant.components.zone import async_active_zone
|
||||||
|
@ -109,7 +113,7 @@ class IcloudAccount:
|
||||||
api_devices = self.api.devices
|
api_devices = self.api.devices
|
||||||
# Gets device owners infos
|
# Gets device owners infos
|
||||||
user_info = api_devices.response["userInfo"]
|
user_info = api_devices.response["userInfo"]
|
||||||
except (KeyError, PyiCloudNoDevicesException):
|
except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException):
|
||||||
_LOGGER.error("No iCloud device found")
|
_LOGGER.error("No iCloud device found")
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,12 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pyicloud import PyiCloudService
|
from pyicloud import PyiCloudService
|
||||||
from pyicloud.exceptions import PyiCloudException, PyiCloudFailedLoginException
|
from pyicloud.exceptions import (
|
||||||
|
PyiCloudException,
|
||||||
|
PyiCloudFailedLoginException,
|
||||||
|
PyiCloudNoDevicesException,
|
||||||
|
PyiCloudServiceNotActivatedException,
|
||||||
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
@ -101,6 +106,17 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
if self.api.requires_2sa:
|
if self.api.requires_2sa:
|
||||||
return await self.async_step_trusted_device()
|
return await self.async_step_trusted_device()
|
||||||
|
|
||||||
|
try:
|
||||||
|
devices = await self.hass.async_add_executor_job(
|
||||||
|
getattr, self.api, "devices"
|
||||||
|
)
|
||||||
|
if not devices:
|
||||||
|
raise PyiCloudNoDevicesException()
|
||||||
|
except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException):
|
||||||
|
_LOGGER.error("No device found in the iCloud account: %s", self._username)
|
||||||
|
self.api = None
|
||||||
|
return self.async_abort(reason="no_device")
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self._username,
|
title=self._username,
|
||||||
data={
|
data={
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Apple iCloud",
|
"name": "Apple iCloud",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/icloud",
|
"documentation": "https://www.home-assistant.io/integrations/icloud",
|
||||||
"requirements": ["pyicloud==0.9.3"],
|
"requirements": ["pyicloud==0.9.4"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@Quentame"]
|
"codeowners": ["@Quentame"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
"validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again"
|
"validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Account already configured"
|
"already_configured": "Account already configured",
|
||||||
|
"no_device": "None of your devices have \"Find my iPhone\" activated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1312,7 +1312,7 @@ pyhomeworks==0.0.6
|
||||||
pyialarm==0.3
|
pyialarm==0.3
|
||||||
|
|
||||||
# homeassistant.components.icloud
|
# homeassistant.components.icloud
|
||||||
pyicloud==0.9.3
|
pyicloud==0.9.4
|
||||||
|
|
||||||
# homeassistant.components.intesishome
|
# homeassistant.components.intesishome
|
||||||
pyintesishome==1.6
|
pyintesishome==1.6
|
||||||
|
|
|
@ -480,7 +480,7 @@ pyheos==0.6.0
|
||||||
pyhomematic==0.1.65
|
pyhomematic==0.1.65
|
||||||
|
|
||||||
# homeassistant.components.icloud
|
# homeassistant.components.icloud
|
||||||
pyicloud==0.9.3
|
pyicloud==0.9.4
|
||||||
|
|
||||||
# homeassistant.components.ipma
|
# homeassistant.components.ipma
|
||||||
pyipma==2.0.5
|
pyipma==2.0.5
|
||||||
|
|
|
@ -46,8 +46,8 @@ def mock_controller_service():
|
||||||
yield service_mock
|
yield service_mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="service_with_cookie")
|
@pytest.fixture(name="service_authenticated")
|
||||||
def mock_controller_service_with_cookie():
|
def mock_controller_service_authenticated():
|
||||||
"""Mock a successful service while already authenticate."""
|
"""Mock a successful service while already authenticate."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.icloud.config_flow.PyiCloudService"
|
"homeassistant.components.icloud.config_flow.PyiCloudService"
|
||||||
|
@ -59,6 +59,20 @@ def mock_controller_service_with_cookie():
|
||||||
yield service_mock
|
yield service_mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="service_authenticated_no_device")
|
||||||
|
def mock_controller_service_authenticated_no_device():
|
||||||
|
"""Mock a successful service while already authenticate, but without device."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.icloud.config_flow.PyiCloudService"
|
||||||
|
) as service_mock:
|
||||||
|
service_mock.return_value.requires_2sa = False
|
||||||
|
service_mock.return_value.trusted_devices = TRUSTED_DEVICES
|
||||||
|
service_mock.return_value.send_verification_code = Mock(return_value=True)
|
||||||
|
service_mock.return_value.validate_verification_code = Mock(return_value=True)
|
||||||
|
service_mock.return_value.devices = {}
|
||||||
|
yield service_mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="service_send_verification_code_failed")
|
@pytest.fixture(name="service_send_verification_code_failed")
|
||||||
def mock_controller_service_send_verification_code_failed():
|
def mock_controller_service_send_verification_code_failed():
|
||||||
"""Mock a failed service during sending verification code step."""
|
"""Mock a failed service during sending verification code step."""
|
||||||
|
@ -103,7 +117,7 @@ async def test_user(hass: HomeAssistantType, service: MagicMock):
|
||||||
|
|
||||||
|
|
||||||
async def test_user_with_cookie(
|
async def test_user_with_cookie(
|
||||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
hass: HomeAssistantType, service_authenticated: MagicMock
|
||||||
):
|
):
|
||||||
"""Test user config with presence of a cookie."""
|
"""Test user config with presence of a cookie."""
|
||||||
# test with all provided
|
# test with all provided
|
||||||
|
@ -148,7 +162,7 @@ async def test_import(hass: HomeAssistantType, service: MagicMock):
|
||||||
|
|
||||||
|
|
||||||
async def test_import_with_cookie(
|
async def test_import_with_cookie(
|
||||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
hass: HomeAssistantType, service_authenticated: MagicMock
|
||||||
):
|
):
|
||||||
"""Test import step with presence of a cookie."""
|
"""Test import step with presence of a cookie."""
|
||||||
# import with username and password
|
# import with username and password
|
||||||
|
@ -186,7 +200,7 @@ async def test_import_with_cookie(
|
||||||
|
|
||||||
|
|
||||||
async def test_two_accounts_setup(
|
async def test_two_accounts_setup(
|
||||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
hass: HomeAssistantType, service_authenticated: MagicMock
|
||||||
):
|
):
|
||||||
"""Test to setup two accounts."""
|
"""Test to setup two accounts."""
|
||||||
MockConfigEntry(
|
MockConfigEntry(
|
||||||
|
@ -210,7 +224,7 @@ async def test_two_accounts_setup(
|
||||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||||
|
|
||||||
|
|
||||||
async def test_abort_if_already_setup(hass: HomeAssistantType):
|
async def test_already_setup(hass: HomeAssistantType):
|
||||||
"""Test we abort if the account is already setup."""
|
"""Test we abort if the account is already setup."""
|
||||||
MockConfigEntry(
|
MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
@ -240,7 +254,7 @@ async def test_abort_if_already_setup(hass: HomeAssistantType):
|
||||||
async def test_login_failed(hass: HomeAssistantType):
|
async def test_login_failed(hass: HomeAssistantType):
|
||||||
"""Test when we have errors during login."""
|
"""Test when we have errors during login."""
|
||||||
with patch(
|
with patch(
|
||||||
"pyicloud.base.PyiCloudService.authenticate",
|
"homeassistant.components.icloud.config_flow.PyiCloudService.authenticate",
|
||||||
side_effect=PyiCloudFailedLoginException(),
|
side_effect=PyiCloudFailedLoginException(),
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
@ -252,6 +266,19 @@ async def test_login_failed(hass: HomeAssistantType):
|
||||||
assert result["errors"] == {CONF_USERNAME: "login"}
|
assert result["errors"] == {CONF_USERNAME: "login"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_device(
|
||||||
|
hass: HomeAssistantType, service_authenticated_no_device: MagicMock
|
||||||
|
):
|
||||||
|
"""Test when we have no devices."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "no_device"
|
||||||
|
|
||||||
|
|
||||||
async def test_trusted_device(hass: HomeAssistantType, service: MagicMock):
|
async def test_trusted_device(hass: HomeAssistantType, service: MagicMock):
|
||||||
"""Test trusted_device step."""
|
"""Test trusted_device step."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue