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:
parent
db6f0827aa
commit
38f3b9f165
6 changed files with 80 additions and 18 deletions
|
@ -13,11 +13,11 @@ from sharkiq import (
|
||||||
|
|
||||||
from homeassistant import exceptions
|
from homeassistant import exceptions
|
||||||
from homeassistant.config_entries import ConfigEntry
|
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.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
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
|
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],
|
username=config_entry.data[CONF_USERNAME],
|
||||||
password=config_entry.data[CONF_PASSWORD],
|
password=config_entry.data[CONF_PASSWORD],
|
||||||
websession=async_get_clientsession(hass),
|
websession=async_get_clientsession(hass),
|
||||||
|
europe=(config_entry.data[CONF_REGION] == SHARKIQ_REGION_EUROPE),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -11,14 +11,31 @@ from sharkiq import SharkIqAuthError, get_ayla_api
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, core, exceptions
|
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.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers import selector
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
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(
|
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],
|
username=data[CONF_USERNAME],
|
||||||
password=data[CONF_PASSWORD],
|
password=data[CONF_PASSWORD],
|
||||||
websession=async_get_clientsession(hass),
|
websession=async_get_clientsession(hass),
|
||||||
|
europe=(data[CONF_REGION] == SHARKIQ_REGION_EUROPE),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(10):
|
async with async_timeout.timeout(10):
|
||||||
LOGGER.debug("Initialize connection to Ayla networks API")
|
LOGGER.debug("Initialize connection to Ayla networks API")
|
||||||
await ayla_api.async_sign_in()
|
await ayla_api.async_sign_in()
|
||||||
except (asyncio.TimeoutError, aiohttp.ClientError) as errors:
|
except (asyncio.TimeoutError, aiohttp.ClientError, TypeError) as error:
|
||||||
raise CannotConnect from errors
|
LOGGER.error(error)
|
||||||
|
raise CannotConnect(
|
||||||
|
"Unable to connect to SharkIQ services. Check your region settings."
|
||||||
|
) from error
|
||||||
except SharkIqAuthError as 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 info that you want to store in the config entry.
|
||||||
return {"title": data[CONF_USERNAME]}
|
return {"title": data[CONF_USERNAME]}
|
||||||
|
@ -64,8 +94,7 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
except InvalidAuth:
|
except InvalidAuth:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except Exception: # pylint: disable=broad-except
|
except UnknownAuth: # pylint: disable=broad-except
|
||||||
LOGGER.exception("Unexpected exception")
|
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
return info, errors
|
return info, errors
|
||||||
|
|
||||||
|
@ -114,3 +143,7 @@ class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
|
||||||
class InvalidAuth(exceptions.HomeAssistantError):
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate there is invalid auth."""
|
"""Error to indicate there is invalid auth."""
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownAuth(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate there is an uncaught auth error."""
|
||||||
|
|
|
@ -11,3 +11,8 @@ PLATFORMS = [Platform.VACUUM]
|
||||||
DOMAIN = "sharkiq"
|
DOMAIN = "sharkiq"
|
||||||
SHARK = "Shark"
|
SHARK = "Shark"
|
||||||
UPDATE_INTERVAL = timedelta(seconds=30)
|
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]
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"flow_title": "Add Shark IQ Account",
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
"description": "Sign into your Shark Clean account to control your devices.",
|
||||||
"data": {
|
"data": {
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"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": {
|
"reauth": {
|
||||||
"data": {
|
"data": {
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"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%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"region": {
|
||||||
|
"options": {
|
||||||
|
"europe": "Europe",
|
||||||
|
"elsewhere": "Everywhere Else"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Constants used in shark iq tests."""
|
"""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()
|
# Dummy device dict of the form returned by AylaApi.list_devices()
|
||||||
SHARK_DEVICE_DICT = {
|
SHARK_DEVICE_DICT = {
|
||||||
|
@ -69,6 +69,11 @@ SHARK_PROPERTIES_DICT = {
|
||||||
|
|
||||||
TEST_USERNAME = "test-username"
|
TEST_USERNAME = "test-username"
|
||||||
TEST_PASSWORD = "test-password"
|
TEST_PASSWORD = "test-password"
|
||||||
|
TEST_REGION = "elsewhere"
|
||||||
UNIQUE_ID = "foo@bar.com"
|
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"
|
ENTRY_ID = "0123456789abcdef0123456789abcdef"
|
||||||
|
|
|
@ -3,13 +3,13 @@ from unittest.mock import patch
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import pytest
|
import pytest
|
||||||
from sharkiq import AylaApi, SharkIqAuthError
|
from sharkiq import AylaApi, SharkIqAuthError, SharkIqError
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.sharkiq.const import DOMAIN
|
from homeassistant.components.sharkiq.const import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
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
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||||
assert result2["data"] == {
|
assert result2["data"] == {
|
||||||
"username": TEST_USERNAME,
|
"username": TEST_USERNAME,
|
||||||
"password": TEST_PASSWORD,
|
"password": TEST_PASSWORD,
|
||||||
|
"region": TEST_REGION,
|
||||||
}
|
}
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_setup_entry.assert_called_once()
|
mock_setup_entry.assert_called_once()
|
||||||
|
@ -47,7 +48,8 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||||
[
|
[
|
||||||
(SharkIqAuthError, "invalid_auth"),
|
(SharkIqAuthError, "invalid_auth"),
|
||||||
(aiohttp.ClientError, "cannot_connect"),
|
(aiohttp.ClientError, "cannot_connect"),
|
||||||
(TypeError, "unknown"),
|
(TypeError, "cannot_connect"),
|
||||||
|
(SharkIqError, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_form_error(hass: HomeAssistant, exc: Exception, base_error: str) -> None:
|
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"),
|
(SharkIqAuthError, "form", "errors", "invalid_auth"),
|
||||||
(aiohttp.ClientError, "abort", "reason", "cannot_connect"),
|
(aiohttp.ClientError, "abort", "reason", "cannot_connect"),
|
||||||
(TypeError, "abort", "reason", "unknown"),
|
(TypeError, "abort", "reason", "cannot_connect"),
|
||||||
|
(SharkIqError, "abort", "reason", "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_reauth(
|
async def test_reauth(
|
||||||
|
|
Loading…
Add table
Reference in a new issue