diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index 0c4f7bb0bfc04b8faf0cb79327f85091fe2daeb8..738dd595a5a7b1da360e9964f639a66fa720ab56 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -13,11 +13,11 @@ from sharkiq import ( from homeassistant import exceptions from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import API_TIMEOUT, DOMAIN, LOGGER, PLATFORMS +from .const import API_TIMEOUT, DOMAIN, LOGGER, PLATFORMS, SHARKIQ_REGION_EUROPE from .update_coordinator import SharkIqUpdateCoordinator @@ -47,6 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b username=config_entry.data[CONF_USERNAME], password=config_entry.data[CONF_PASSWORD], websession=async_get_clientsession(hass), + europe=(config_entry.data[CONF_REGION] == SHARKIQ_REGION_EUROPE), ) try: diff --git a/homeassistant/components/sharkiq/config_flow.py b/homeassistant/components/sharkiq/config_flow.py index b0aae5259dd9d5fa5e3a85cbdc67be84aac76170..57de36ce41591751693743dd5bb9b50806c50997 100644 --- a/homeassistant/components/sharkiq/config_flow.py +++ b/homeassistant/components/sharkiq/config_flow.py @@ -11,14 +11,31 @@ from sharkiq import SharkIqAuthError, get_ayla_api import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import DOMAIN, LOGGER +from .const import ( + DOMAIN, + LOGGER, + SHARKIQ_REGION_DEFAULT, + SHARKIQ_REGION_EUROPE, + SHARKIQ_REGION_OPTIONS, +) SHARKIQ_SCHEMA = vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required( + CONF_REGION, default=SHARKIQ_REGION_DEFAULT + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SHARKIQ_REGION_OPTIONS, translation_key="region" + ), + ), + } ) @@ -30,16 +47,29 @@ async def _validate_input( username=data[CONF_USERNAME], password=data[CONF_PASSWORD], websession=async_get_clientsession(hass), + europe=(data[CONF_REGION] == SHARKIQ_REGION_EUROPE), ) try: async with async_timeout.timeout(10): LOGGER.debug("Initialize connection to Ayla networks API") await ayla_api.async_sign_in() - except (asyncio.TimeoutError, aiohttp.ClientError) as errors: - raise CannotConnect from errors + except (asyncio.TimeoutError, aiohttp.ClientError, TypeError) as error: + LOGGER.error(error) + raise CannotConnect( + "Unable to connect to SharkIQ services. Check your region settings." + ) from error except SharkIqAuthError as error: - raise InvalidAuth from error + LOGGER.error(error) + raise InvalidAuth( + "Username or password incorrect. Please check your credentials." + ) from error + except Exception as error: + LOGGER.exception("Unexpected exception") + LOGGER.error(error) + raise UnknownAuth( + "An unknown error occurred. Check your region settings and open an issue on Github if the issue persists." + ) from error # Return info that you want to store in the config entry. return {"title": data[CONF_USERNAME]} @@ -64,8 +94,7 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" - except Exception: # pylint: disable=broad-except - LOGGER.exception("Unexpected exception") + except UnknownAuth: # pylint: disable=broad-except errors["base"] = "unknown" return info, errors @@ -114,3 +143,7 @@ class CannotConnect(exceptions.HomeAssistantError): class InvalidAuth(exceptions.HomeAssistantError): """Error to indicate there is invalid auth.""" + + +class UnknownAuth(exceptions.HomeAssistantError): + """Error to indicate there is an uncaught auth error.""" diff --git a/homeassistant/components/sharkiq/const.py b/homeassistant/components/sharkiq/const.py index fb683bb525a8674a196aa4500963e6d9382f6016..b12a86dc2407fbb2f5ca51f62989a2ad54a10c31 100644 --- a/homeassistant/components/sharkiq/const.py +++ b/homeassistant/components/sharkiq/const.py @@ -11,3 +11,8 @@ PLATFORMS = [Platform.VACUUM] DOMAIN = "sharkiq" SHARK = "Shark" UPDATE_INTERVAL = timedelta(seconds=30) + +SHARKIQ_REGION_EUROPE = "europe" +SHARKIQ_REGION_ELSEWHERE = "elsewhere" +SHARKIQ_REGION_DEFAULT = SHARKIQ_REGION_ELSEWHERE +SHARKIQ_REGION_OPTIONS = [SHARKIQ_REGION_EUROPE, SHARKIQ_REGION_ELSEWHERE] diff --git a/homeassistant/components/sharkiq/strings.json b/homeassistant/components/sharkiq/strings.json index bc920ac7c7ebdf2af82cb225884346457775ee4a..23f949be4cc667b18f5288ad6ebe4c4c76e10d23 100644 --- a/homeassistant/components/sharkiq/strings.json +++ b/homeassistant/components/sharkiq/strings.json @@ -1,16 +1,23 @@ { "config": { + "flow_title": "Add Shark IQ Account", "step": { "user": { + "description": "Sign into your Shark Clean account to control your devices.", "data": { "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "password": "[%key:common::config_flow::data::password%]", + "region": "Region" + }, + "data_description": { + "region": "Shark IQ uses different services in the EU. Select your region to connect to the correct service for your account." } }, "reauth": { "data": { "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "password": "[%key:common::config_flow::data::password%]", + "region": "Region" } } }, @@ -25,5 +32,13 @@ "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } + }, + "selector": { + "region": { + "options": { + "europe": "Europe", + "elsewhere": "Everywhere Else" + } + } } } diff --git a/tests/components/sharkiq/const.py b/tests/components/sharkiq/const.py index 305d12ddfa787fab69114ec97c91c04ba1ac33a1..8ec7d424ffa164df3ed7a8c404ce0de7f9095d67 100644 --- a/tests/components/sharkiq/const.py +++ b/tests/components/sharkiq/const.py @@ -1,6 +1,6 @@ """Constants used in shark iq tests.""" -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME # Dummy device dict of the form returned by AylaApi.list_devices() SHARK_DEVICE_DICT = { @@ -69,6 +69,11 @@ SHARK_PROPERTIES_DICT = { TEST_USERNAME = "test-username" TEST_PASSWORD = "test-password" +TEST_REGION = "elsewhere" UNIQUE_ID = "foo@bar.com" -CONFIG = {CONF_USERNAME: TEST_USERNAME, CONF_PASSWORD: TEST_PASSWORD} +CONFIG = { + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: TEST_PASSWORD, + CONF_REGION: TEST_REGION, +} ENTRY_ID = "0123456789abcdef0123456789abcdef" diff --git a/tests/components/sharkiq/test_config_flow.py b/tests/components/sharkiq/test_config_flow.py index f611d8e6d8e1f04aacb020c296378ca756e1d142..c7a0603f8651c4b89d0e8a550ea36e35ba6023a5 100644 --- a/tests/components/sharkiq/test_config_flow.py +++ b/tests/components/sharkiq/test_config_flow.py @@ -3,13 +3,13 @@ from unittest.mock import patch import aiohttp import pytest -from sharkiq import AylaApi, SharkIqAuthError +from sharkiq import AylaApi, SharkIqAuthError, SharkIqError from homeassistant import config_entries from homeassistant.components.sharkiq.const import DOMAIN from homeassistant.core import HomeAssistant -from .const import CONFIG, TEST_PASSWORD, TEST_USERNAME, UNIQUE_ID +from .const import CONFIG, TEST_PASSWORD, TEST_REGION, TEST_USERNAME, UNIQUE_ID from tests.common import MockConfigEntry @@ -37,6 +37,7 @@ async def test_form(hass: HomeAssistant) -> None: assert result2["data"] == { "username": TEST_USERNAME, "password": TEST_PASSWORD, + "region": TEST_REGION, } await hass.async_block_till_done() mock_setup_entry.assert_called_once() @@ -47,7 +48,8 @@ async def test_form(hass: HomeAssistant) -> None: [ (SharkIqAuthError, "invalid_auth"), (aiohttp.ClientError, "cannot_connect"), - (TypeError, "unknown"), + (TypeError, "cannot_connect"), + (SharkIqError, "unknown"), ], ) async def test_form_error(hass: HomeAssistant, exc: Exception, base_error: str) -> None: @@ -87,7 +89,8 @@ async def test_reauth_success(hass: HomeAssistant) -> None: [ (SharkIqAuthError, "form", "errors", "invalid_auth"), (aiohttp.ClientError, "abort", "reason", "cannot_connect"), - (TypeError, "abort", "reason", "unknown"), + (TypeError, "abort", "reason", "cannot_connect"), + (SharkIqError, "abort", "reason", "unknown"), ], ) async def test_reauth(