Surepetcare reauthorize (#56402)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Daniel Hjelseth Høyer 2021-09-30 17:11:45 +02:00 committed by GitHub
parent 08719af794
commit d5bda3ac14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 210 additions and 15 deletions

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service import ServiceCall from homeassistant.helpers.service import ServiceCall
@ -99,12 +100,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry, entry,
hass, hass,
) )
except SurePetcareAuthenticationError: except SurePetcareAuthenticationError as error:
_LOGGER.error("Unable to connect to surepetcare.io: Wrong credentials!") _LOGGER.error("Unable to connect to surepetcare.io: Wrong credentials!")
return False raise ConfigEntryAuthFailed from error
except SurePetcareError as error: except SurePetcareError as error:
_LOGGER.error("Unable to connect to surepetcare.io: Wrong %s!", error) raise ConfigEntryNotReady from error
return False
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
@ -188,6 +188,8 @@ class SurePetcareDataCoordinator(DataUpdateCoordinator):
"""Get the latest data from Sure Petcare.""" """Get the latest data from Sure Petcare."""
try: try:
return await self.surepy.get_entities(refresh=True) return await self.surepy.get_entities(refresh=True)
except SurePetcareAuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid username/password") from err
except SurePetcareError as err: except SurePetcareError as err:
raise UpdateFailed(f"Unable to fetch data: {err}") from err raise UpdateFailed(f"Unable to fetch data: {err}") from err

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from surepy import Surepy import surepy
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
import voluptuous as vol import voluptuous as vol
@ -18,7 +18,7 @@ from .const import DOMAIN, SURE_API_TIMEOUT
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema( USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_USERNAME): str, vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str, vol.Required(CONF_PASSWORD): str,
@ -28,7 +28,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
surepy = Surepy( surepy_client = surepy.Surepy(
data[CONF_USERNAME], data[CONF_USERNAME],
data[CONF_PASSWORD], data[CONF_PASSWORD],
auth_token=None, auth_token=None,
@ -36,7 +36,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
session=async_get_clientsession(hass), session=async_get_clientsession(hass),
) )
token = await surepy.sac.get_token() token = await surepy_client.sac.get_token()
return {CONF_TOKEN: token} return {CONF_TOKEN: token}
@ -46,6 +46,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
def __init__(self) -> None:
"""Initialize."""
self._username: str | None = None
async def async_step_import(self, import_info: dict[str, Any] | None) -> FlowResult: async def async_step_import(self, import_info: dict[str, Any] | None) -> FlowResult:
"""Set the config entry up from yaml.""" """Set the config entry up from yaml."""
return await self.async_step_user(import_info) return await self.async_step_user(import_info)
@ -55,9 +59,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) -> FlowResult: ) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(step_id="user", data_schema=USER_DATA_SCHEMA)
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
errors = {} errors = {}
@ -81,5 +83,40 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) )
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors step_id="user", data_schema=USER_DATA_SCHEMA, errors=errors
)
async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult:
"""Handle configuration by re-auth."""
self._username = config[CONF_USERNAME]
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Dialog that informs the user that reauth is required."""
errors = {}
if user_input is not None:
user_input[CONF_USERNAME] = self._username
try:
await validate_input(self.hass, user_input)
except SurePetcareAuthenticationError:
errors["base"] = "invalid_auth"
except SurePetcareError:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
existing_entry = await self.async_set_unique_id(
user_input[CONF_USERNAME].lower()
)
if existing_entry:
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}),
errors=errors,
) )

View File

@ -14,6 +14,11 @@ from homeassistant.data_entry_flow import (
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
INPUT_DATA = {
"username": "test-username",
"password": "test-password",
}
async def test_form(hass: HomeAssistant, surepetcare: NonCallableMagicMock) -> None: async def test_form(hass: HomeAssistant, surepetcare: NonCallableMagicMock) -> None:
"""Test we get the form.""" """Test we get the form."""
@ -54,7 +59,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"surepy.client.SureAPIClient.get_token", "homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareAuthenticationError, side_effect=SurePetcareAuthenticationError,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -76,7 +81,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"surepy.client.SureAPIClient.get_token", "homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareError, side_effect=SurePetcareError,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -98,7 +103,7 @@ async def test_form_unknown_error(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"surepy.client.SureAPIClient.get_token", "homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=Exception, side_effect=Exception,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -142,3 +147,154 @@ async def test_flow_entry_already_exists(
assert result["type"] == RESULT_TYPE_ABORT assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_reauthentication(hass):
"""Test surepetcare reauthentication."""
old_entry = MockConfigEntry(
domain="surepetcare",
data=INPUT_DATA,
unique_id="test-username",
)
old_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": old_entry.unique_id,
"entry_id": old_entry.entry_id,
},
data=old_entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
assert result["step_id"] == "reauth_confirm"
with patch(
"homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
return_value={"token": "token"},
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"password": "test-password"},
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
async def test_reauthentication_failure(hass):
"""Test surepetcare reauthentication failure."""
old_entry = MockConfigEntry(
domain="surepetcare",
data=INPUT_DATA,
unique_id="USERID",
)
old_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": old_entry.unique_id,
"entry_id": old_entry.entry_id,
},
data=old_entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
assert result["step_id"] == "reauth_confirm"
with patch(
"homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareAuthenticationError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"password": "test-password"},
)
await hass.async_block_till_done()
assert result2["step_id"] == "reauth_confirm"
assert result["type"] == "form"
assert result2["errors"]["base"] == "invalid_auth"
async def test_reauthentication_cannot_connect(hass):
"""Test surepetcare reauthentication failure."""
old_entry = MockConfigEntry(
domain="surepetcare",
data=INPUT_DATA,
unique_id="USERID",
)
old_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": old_entry.unique_id,
"entry_id": old_entry.entry_id,
},
data=old_entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
assert result["step_id"] == "reauth_confirm"
with patch(
"homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"password": "test-password"},
)
await hass.async_block_till_done()
assert result2["step_id"] == "reauth_confirm"
assert result["type"] == "form"
assert result2["errors"]["base"] == "cannot_connect"
async def test_reauthentication_unknown_failure(hass):
"""Test surepetcare reauthentication failure."""
old_entry = MockConfigEntry(
domain="surepetcare",
data=INPUT_DATA,
unique_id="USERID",
)
old_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": old_entry.unique_id,
"entry_id": old_entry.entry_id,
},
data=old_entry.data,
)
assert result["type"] == "form"
assert result["errors"] == {}
assert result["step_id"] == "reauth_confirm"
with patch(
"homeassistant.components.surepetcare.config_flow.surepy.client.SureAPIClient.get_token",
side_effect=Exception,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"password": "test-password"},
)
await hass.async_block_till_done()
assert result2["step_id"] == "reauth_confirm"
assert result["type"] == "form"
assert result2["errors"]["base"] == "unknown"