mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Fix can't add multiple iCloud accounts (remove account name) (#30898)
* Fix can't add multiple iCloud accounts (remove account name) * Update tests with flow.async_init()
This commit is contained in:
parent
6c84c126ea
commit
7c155731fc
@ -23,7 +23,6 @@ from homeassistant.util.dt import utcnow
|
||||
from homeassistant.util.location import distance
|
||||
|
||||
from .const import (
|
||||
CONF_ACCOUNT_NAME,
|
||||
CONF_GPS_ACCURACY_THRESHOLD,
|
||||
CONF_MAX_INTERVAL,
|
||||
DEFAULT_GPS_ACCURACY_THRESHOLD,
|
||||
@ -100,7 +99,6 @@ ACCOUNT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_ACCOUNT_NAME): cv.string,
|
||||
vol.Optional(CONF_MAX_INTERVAL, default=DEFAULT_MAX_INTERVAL): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_GPS_ACCURACY_THRESHOLD, default=DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
@ -140,20 +138,13 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
||||
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
account_name = entry.data.get(CONF_ACCOUNT_NAME)
|
||||
max_interval = entry.data[CONF_MAX_INTERVAL]
|
||||
gps_accuracy_threshold = entry.data[CONF_GPS_ACCURACY_THRESHOLD]
|
||||
|
||||
icloud_dir = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
|
||||
account = IcloudAccount(
|
||||
hass,
|
||||
username,
|
||||
password,
|
||||
icloud_dir,
|
||||
account_name,
|
||||
max_interval,
|
||||
gps_accuracy_threshold,
|
||||
hass, username, password, icloud_dir, max_interval, gps_accuracy_threshold,
|
||||
)
|
||||
await hass.async_add_executor_job(account.setup)
|
||||
hass.data[DOMAIN][username] = account
|
||||
@ -254,7 +245,6 @@ class IcloudAccount:
|
||||
username: str,
|
||||
password: str,
|
||||
icloud_dir: Store,
|
||||
account_name: str,
|
||||
max_interval: int,
|
||||
gps_accuracy_threshold: int,
|
||||
):
|
||||
@ -262,7 +252,6 @@ class IcloudAccount:
|
||||
self.hass = hass
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._name = account_name or slugify(username.partition("@")[0])
|
||||
self._fetch_interval = max_interval
|
||||
self._max_interval = max_interval
|
||||
self._gps_accuracy_threshold = gps_accuracy_threshold
|
||||
@ -434,11 +423,6 @@ class IcloudAccount:
|
||||
raise Exception(f"No device with name {name}")
|
||||
return result
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the account name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""Return the account username."""
|
||||
@ -471,7 +455,6 @@ class IcloudDevice:
|
||||
def __init__(self, account: IcloudAccount, device: AppleDevice, status):
|
||||
"""Initialize the iCloud device."""
|
||||
self._account = account
|
||||
account_name = account.name
|
||||
|
||||
self._device = device
|
||||
self._status = status
|
||||
@ -494,7 +477,6 @@ class IcloudDevice:
|
||||
|
||||
self._attrs = {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
CONF_ACCOUNT_NAME: account_name,
|
||||
ATTR_ACCOUNT_FETCH_INTERVAL: self._account.fetch_interval,
|
||||
ATTR_DEVICE_NAME: self._device_model,
|
||||
ATTR_DEVICE_STATUS: None,
|
||||
|
@ -8,10 +8,8 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import (
|
||||
CONF_ACCOUNT_NAME,
|
||||
CONF_GPS_ACCURACY_THRESHOLD,
|
||||
CONF_MAX_INTERVAL,
|
||||
DEFAULT_GPS_ACCURACY_THRESHOLD,
|
||||
@ -45,14 +43,10 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._trusted_device = None
|
||||
self._verification_code = None
|
||||
|
||||
def _configuration_exists(self, username: str, account_name: str) -> bool:
|
||||
"""Return True if username or account_name exists in configuration."""
|
||||
def _configuration_exists(self, username: str) -> bool:
|
||||
"""Return True if username exists in configuration."""
|
||||
for entry in self._async_current_entries():
|
||||
if (
|
||||
entry.data[CONF_USERNAME] == username
|
||||
or entry.data.get(CONF_ACCOUNT_NAME) == account_name
|
||||
or slugify(entry.data[CONF_USERNAME].partition("@")[0]) == account_name
|
||||
):
|
||||
if entry.data[CONF_USERNAME] == username:
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -91,13 +85,12 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._username = user_input[CONF_USERNAME]
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
self._account_name = user_input.get(CONF_ACCOUNT_NAME)
|
||||
self._max_interval = user_input.get(CONF_MAX_INTERVAL, DEFAULT_MAX_INTERVAL)
|
||||
self._gps_accuracy_threshold = user_input.get(
|
||||
CONF_GPS_ACCURACY_THRESHOLD, DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
)
|
||||
|
||||
if self._configuration_exists(self._username, self._account_name):
|
||||
if self._configuration_exists(self._username):
|
||||
errors[CONF_USERNAME] = "username_exists"
|
||||
return await self._show_setup_form(user_input, errors)
|
||||
|
||||
@ -119,7 +112,6 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
data={
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_ACCOUNT_NAME: self._account_name,
|
||||
CONF_MAX_INTERVAL: self._max_interval,
|
||||
CONF_GPS_ACCURACY_THRESHOLD: self._gps_accuracy_threshold,
|
||||
},
|
||||
@ -127,9 +119,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def async_step_import(self, user_input):
|
||||
"""Import a config entry."""
|
||||
if self._configuration_exists(
|
||||
user_input[CONF_USERNAME], user_input.get(CONF_ACCOUNT_NAME)
|
||||
):
|
||||
if self._configuration_exists(user_input[CONF_USERNAME]):
|
||||
return self.async_abort(reason="username_exists")
|
||||
|
||||
return await self.async_step_user(user_input)
|
||||
@ -214,7 +204,6 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
{
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_ACCOUNT_NAME: self._account_name,
|
||||
CONF_MAX_INTERVAL: self._max_interval,
|
||||
CONF_GPS_ACCURACY_THRESHOLD: self._gps_accuracy_threshold,
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
DOMAIN = "icloud"
|
||||
SERVICE_UPDATE = f"{DOMAIN}_update"
|
||||
|
||||
CONF_ACCOUNT_NAME = "account_name"
|
||||
CONF_MAX_INTERVAL = "max_interval"
|
||||
CONF_GPS_ACCURACY_THRESHOLD = "gps_accuracy_threshold"
|
||||
|
||||
|
@ -11,22 +11,21 @@ from homeassistant.components.icloud.config_flow import (
|
||||
CONF_VERIFICATION_CODE,
|
||||
)
|
||||
from homeassistant.components.icloud.const import (
|
||||
CONF_ACCOUNT_NAME,
|
||||
CONF_GPS_ACCURACY_THRESHOLD,
|
||||
CONF_MAX_INTERVAL,
|
||||
DEFAULT_GPS_ACCURACY_THRESHOLD,
|
||||
DEFAULT_MAX_INTERVAL,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
USERNAME = "username@me.com"
|
||||
USERNAME_2 = "second_username@icloud.com"
|
||||
PASSWORD = "password"
|
||||
ACCOUNT_NAME = "Account name 1 2 3"
|
||||
ACCOUNT_NAME_FROM_USERNAME = None
|
||||
MAX_INTERVAL = 15
|
||||
GPS_ACCURACY_THRESHOLD = 250
|
||||
|
||||
@ -92,15 +91,17 @@ def init_config_flow(hass: HomeAssistantType):
|
||||
|
||||
async def test_user(hass: HomeAssistantType, service: MagicMock):
|
||||
"""Test user config."""
|
||||
flow = init_config_flow(hass)
|
||||
|
||||
result = await flow.async_step_user()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}, data=None
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
# test with all provided
|
||||
result = await flow.async_step_user(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
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_FORM
|
||||
assert result["step_id"] == CONF_TRUSTED_DEVICE
|
||||
@ -110,41 +111,41 @@ async def test_user_with_cookie(
|
||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
||||
):
|
||||
"""Test user config with presence of a cookie."""
|
||||
flow = init_config_flow(hass)
|
||||
|
||||
# test with all provided
|
||||
result = await flow.async_step_user(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
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_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"][CONF_USERNAME] == USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME_FROM_USERNAME
|
||||
assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL
|
||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
|
||||
|
||||
async def test_import(hass: HomeAssistantType, service: MagicMock):
|
||||
"""Test import step."""
|
||||
flow = init_config_flow(hass)
|
||||
|
||||
# import with username and password
|
||||
result = await flow.async_step_import(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "trusted_device"
|
||||
|
||||
# import with all
|
||||
result = await flow.async_step_import(
|
||||
{
|
||||
CONF_USERNAME: USERNAME,
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_USERNAME: USERNAME_2,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
CONF_ACCOUNT_NAME: ACCOUNT_NAME,
|
||||
CONF_MAX_INTERVAL: MAX_INTERVAL,
|
||||
CONF_GPS_ACCURACY_THRESHOLD: GPS_ACCURACY_THRESHOLD,
|
||||
}
|
||||
},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "trusted_device"
|
||||
@ -154,67 +155,80 @@ async def test_import_with_cookie(
|
||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
||||
):
|
||||
"""Test import step with presence of a cookie."""
|
||||
flow = init_config_flow(hass)
|
||||
|
||||
# import with username and password
|
||||
result = await flow.async_step_import(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"][CONF_USERNAME] == USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME_FROM_USERNAME
|
||||
assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL
|
||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
|
||||
# import with all
|
||||
result = await flow.async_step_import(
|
||||
{
|
||||
CONF_USERNAME: USERNAME,
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_USERNAME: USERNAME_2,
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
CONF_ACCOUNT_NAME: ACCOUNT_NAME,
|
||||
CONF_MAX_INTERVAL: MAX_INTERVAL,
|
||||
CONF_GPS_ACCURACY_THRESHOLD: GPS_ACCURACY_THRESHOLD,
|
||||
}
|
||||
},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"][CONF_USERNAME] == USERNAME
|
||||
assert result["title"] == USERNAME_2
|
||||
assert result["data"][CONF_USERNAME] == USERNAME_2
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_ACCOUNT_NAME] == ACCOUNT_NAME
|
||||
assert result["data"][CONF_MAX_INTERVAL] == MAX_INTERVAL
|
||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == GPS_ACCURACY_THRESHOLD
|
||||
|
||||
|
||||
async def test_two_accounts_setup(
|
||||
hass: HomeAssistantType, service_with_cookie: MagicMock
|
||||
):
|
||||
"""Test to setup two accounts."""
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
).add_to_hass(hass)
|
||||
|
||||
# import with username and password
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_USERNAME: USERNAME_2, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == USERNAME_2
|
||||
assert result["data"][CONF_USERNAME] == USERNAME_2
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL
|
||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(hass: HomeAssistantType):
|
||||
"""Test we abort if the account is already setup."""
|
||||
flow = init_config_flow(hass)
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
).add_to_hass(hass)
|
||||
|
||||
# Should fail, same USERNAME (import)
|
||||
result = await flow.async_step_import(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "username_exists"
|
||||
|
||||
# Should fail, same ACCOUNT_NAME (import)
|
||||
result = await flow.async_step_import(
|
||||
{
|
||||
CONF_USERNAME: "other_username@icloud.com",
|
||||
CONF_PASSWORD: PASSWORD,
|
||||
CONF_ACCOUNT_NAME: ACCOUNT_NAME_FROM_USERNAME,
|
||||
}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "username_exists"
|
||||
|
||||
# Should fail, same USERNAME (flow)
|
||||
result = await flow.async_step_user(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
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_FORM
|
||||
assert result["errors"] == {CONF_USERNAME: "username_exists"}
|
||||
@ -222,14 +236,14 @@ async def test_abort_if_already_setup(hass: HomeAssistantType):
|
||||
|
||||
async def test_login_failed(hass: HomeAssistantType):
|
||||
"""Test when we have errors during login."""
|
||||
flow = init_config_flow(hass)
|
||||
|
||||
with patch(
|
||||
"pyicloud.base.PyiCloudService.authenticate",
|
||||
side_effect=PyiCloudFailedLoginException(),
|
||||
):
|
||||
result = await flow.async_step_user(
|
||||
{CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
|
||||
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_FORM
|
||||
assert result["errors"] == {CONF_USERNAME: "login"}
|
||||
@ -290,7 +304,6 @@ async def test_verification_code_success(
|
||||
assert result["title"] == USERNAME
|
||||
assert result["data"][CONF_USERNAME] == USERNAME
|
||||
assert result["data"][CONF_PASSWORD] == PASSWORD
|
||||
assert result["data"][CONF_ACCOUNT_NAME] is None
|
||||
assert result["data"][CONF_MAX_INTERVAL] == DEFAULT_MAX_INTERVAL
|
||||
assert result["data"][CONF_GPS_ACCURACY_THRESHOLD] == DEFAULT_GPS_ACCURACY_THRESHOLD
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user