Add SharkIQ EU region support (#89349)

* SharkIQ Dep & Codeowner Update

* Update code owners

* Add EU Region Support

* Update Config Flow Tests

* Standardize Region Comparison Strings

* Add Translation Support to Region Selector

* Fix Validation Tests
This commit is contained in:
Mark Adkins 2023-03-28 02:48:32 -04:00 committed by GitHub
parent db6f0827aa
commit 38f3b9f165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 18 deletions

View file

@ -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:

View file

@ -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."""

View file

@ -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]

View file

@ -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"
}
}
}
}

View file

@ -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"

View file

@ -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(