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:
parent
d661a76462
commit
f9de8fb49a
@ -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
|
||||
|
@ -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():
|
||||
|
||||
|
85
homeassistant/components/surepetcare/config_flow.py
Normal file
85
homeassistant/components/surepetcare/config_flow.py
Normal 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
|
||||
)
|
@ -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
|
||||
}
|
@ -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():
|
||||
|
||||
|
20
homeassistant/components/surepetcare/strings.json
Normal file
20
homeassistant/components/surepetcare/strings.json
Normal 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%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -265,6 +265,7 @@ FLOWS = [
|
||||
"srp_energy",
|
||||
"starline",
|
||||
"subaru",
|
||||
"surepetcare",
|
||||
"switcher_kis",
|
||||
"syncthing",
|
||||
"syncthru",
|
||||
|
@ -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
|
||||
|
144
tests/components/surepetcare/test_config_flow.py
Normal file
144
tests/components/surepetcare/test_config_flow.py
Normal 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"
|
Loading…
Reference in New Issue
Block a user