1
mirror of https://github.com/home-assistant/core synced 2024-07-27 18:58:57 +02:00

Surepetcare config flow (#56127)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Daniel Hjelseth Høyer 2021-09-13 21:57:06 +02:00 committed by GitHub
parent d661a76462
commit f9de8fb49a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 303 additions and 26 deletions

View File

@ -9,7 +9,14 @@ from surepy.enums import LockState
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_TOKEN,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -61,14 +68,28 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Sure Petcare integration."""
conf = config[DOMAIN]
if DOMAIN not in config:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=config[DOMAIN],
)
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Sure Petcare from a config entry."""
hass.data.setdefault(DOMAIN, {})
try:
surepy = Surepy(
conf[CONF_USERNAME],
conf[CONF_PASSWORD],
auth_token=None,
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
auth_token=entry.data[CONF_TOKEN],
api_timeout=SURE_API_TIMEOUT,
session=async_get_clientsession(hass),
)
@ -94,14 +115,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
update_interval=SCAN_INTERVAL,
)
hass.data[DOMAIN] = coordinator
await coordinator.async_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
await coordinator.async_config_entry_first_refresh()
# load platforms
for platform in PLATFORMS:
hass.async_create_task(
hass.helpers.discovery.async_load_platform(platform, DOMAIN, {}, config)
)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
lock_states = {
LockState.UNLOCKED.name.lower(): surepy.sac.unlock,
@ -138,3 +155,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -23,16 +23,12 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None
) -> None:
async def async_setup_entry(hass, entry, async_add_entities) -> None:
"""Set up Sure PetCare Flaps binary sensors based on a config entry."""
if discovery_info is None:
return
entities: list[SurepyEntity | Pet | Hub | DeviceConnectivity] = []
coordinator: DataUpdateCoordinator = hass.data[DOMAIN]
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
for surepy_entity in coordinator.data.values():

View File

@ -0,0 +1,85 @@
"""Config flow for Sure Petcare integration."""
from __future__ import annotations
import logging
from typing import Any
from surepy import Surepy
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN, SURE_API_TIMEOUT
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
surepy = Surepy(
data[CONF_USERNAME],
data[CONF_PASSWORD],
auth_token=None,
api_timeout=SURE_API_TIMEOUT,
session=async_get_clientsession(hass),
)
token = await surepy.sac.get_token()
return {CONF_TOKEN: token}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Sure Petcare."""
VERSION = 1
async def async_step_import(self, import_info):
"""Set the config entry up from yaml."""
return await self.async_step_user(import_info)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
errors = {}
try:
info = 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:
await self.async_set_unique_id(user_input[CONF_USERNAME].lower())
self._abort_if_unique_id_configured()
user_input[CONF_TOKEN] = info[CONF_TOKEN]
return self.async_create_entry(
title="Sure Petcare",
data=user_input,
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

View File

@ -2,7 +2,13 @@
"domain": "surepetcare",
"name": "Sure Petcare",
"documentation": "https://www.home-assistant.io/integrations/surepetcare",
"codeowners": ["@benleb", "@danielhiversen"],
"requirements": ["surepy==0.7.1"],
"iot_class": "cloud_polling"
}
"codeowners": [
"@benleb",
"@danielhiversen"
],
"requirements": [
"surepy==0.7.1"
],
"iot_class": "cloud_polling",
"config_flow": true
}

View File

@ -19,14 +19,12 @@ from .const import DOMAIN, SURE_BATT_VOLTAGE_DIFF, SURE_BATT_VOLTAGE_LOW
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Sure PetCare Flaps sensors."""
if discovery_info is None:
return
entities: list[SurepyEntity] = []
coordinator: DataUpdateCoordinator = hass.data[DOMAIN]
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
for surepy_entity in coordinator.data.values():

View File

@ -0,0 +1,20 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@ -265,6 +265,7 @@ FLOWS = [
"srp_energy",
"starline",
"subaru",
"surepetcare",
"switcher_kis",
"syncthing",
"syncthru",

View File

@ -19,4 +19,5 @@ async def surepetcare():
client = mock_client_class.return_value
client.resources = {}
client.call = _mock_call
client.get_token.return_value = "token"
yield client

View File

@ -0,0 +1,144 @@
"""Test the Sure Petcare config flow."""
from unittest.mock import NonCallableMagicMock, patch
from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError
from homeassistant import config_entries, setup
from homeassistant.components.surepetcare.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant, surepetcare: NonCallableMagicMock) -> None:
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] is None
with patch(
"homeassistant.components.surepetcare.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "Sure Petcare"
assert result2["data"] == {
"username": "test-username",
"password": "test-password",
"token": "token",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareAuthenticationError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"surepy.client.SureAPIClient.get_token",
side_effect=SurePetcareError,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_unknown_error(hass: HomeAssistant) -> None:
"""Test we handle unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"surepy.client.SureAPIClient.get_token",
side_effect=Exception,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "unknown"}
async def test_flow_entry_already_exists(
hass, surepetcare: NonCallableMagicMock
) -> None:
"""Test user input for config_entry that already exists."""
first_entry = MockConfigEntry(
domain="surepetcare",
data={
"username": "test-username",
"password": "test-password",
},
unique_id="test-username",
)
first_entry.add_to_hass(hass)
with patch(
"homeassistant.components.surepetcare.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={
"username": "test-username",
"password": "test-password",
},
)
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"