Avoid having to ask for the bond token when possible during config (#46845)

This commit is contained in:
J. Nick Koston 2021-02-23 15:42:56 -06:00 committed by GitHub
parent 00dd557cce
commit d68a51ddce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 166 additions and 34 deletions

View file

@ -14,16 +14,17 @@ from homeassistant.const import (
HTTP_UNAUTHORIZED,
)
from .const import CONF_BOND_ID
from .const import DOMAIN # pylint:disable=unused-import
from .utils import BondHub
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA_USER = vol.Schema(
USER_SCHEMA = vol.Schema(
{vol.Required(CONF_HOST): str, vol.Required(CONF_ACCESS_TOKEN): str}
)
DATA_SCHEMA_DISCOVERY = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
DISCOVERY_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
TOKEN_SCHEMA = vol.Schema({})
async def _validate_input(data: Dict[str, Any]) -> Tuple[str, Optional[str]]:
@ -56,7 +57,30 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
_discovered: dict = None
def __init__(self):
"""Initialize config flow."""
self._discovered: dict = None
async def _async_try_automatic_configure(self):
"""Try to auto configure the device.
Failure is acceptable here since the device may have been
online longer then the allowed setup period, and we will
instead ask them to manually enter the token.
"""
bond = Bond(self._discovered[CONF_HOST], "")
try:
response = await bond.token()
except ClientConnectionError:
return
token = response.get("token")
if token is None:
return
self._discovered[CONF_ACCESS_TOKEN] = token
_, hub_name = await _validate_input(self._discovered)
self._discovered[CONF_NAME] = hub_name
async def async_step_zeroconf(
self, discovery_info: Optional[Dict[str, Any]] = None
@ -68,11 +92,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(bond_id)
self._abort_if_unique_id_configured({CONF_HOST: host})
self._discovered = {
CONF_HOST: host,
CONF_BOND_ID: bond_id,
}
self.context.update({"title_placeholders": self._discovered})
self._discovered = {CONF_HOST: host, CONF_NAME: bond_id}
await self._async_try_automatic_configure()
self.context.update(
{
"title_placeholders": {
CONF_HOST: self._discovered[CONF_HOST],
CONF_NAME: self._discovered[CONF_NAME],
}
}
)
return await self.async_step_confirm()
@ -82,16 +112,37 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle confirmation flow for discovered bond hub."""
errors = {}
if user_input is not None:
data = user_input.copy()
data[CONF_HOST] = self._discovered[CONF_HOST]
if CONF_ACCESS_TOKEN in self._discovered:
return self.async_create_entry(
title=self._discovered[CONF_NAME],
data={
CONF_ACCESS_TOKEN: self._discovered[CONF_ACCESS_TOKEN],
CONF_HOST: self._discovered[CONF_HOST],
},
)
data = {
CONF_ACCESS_TOKEN: user_input[CONF_ACCESS_TOKEN],
CONF_HOST: self._discovered[CONF_HOST],
}
try:
return await self._try_create_entry(data)
_, hub_name = await _validate_input(data)
except InputValidationError as error:
errors["base"] = error.base
else:
return self.async_create_entry(
title=hub_name,
data=data,
)
if CONF_ACCESS_TOKEN in self._discovered:
data_schema = TOKEN_SCHEMA
else:
data_schema = DISCOVERY_SCHEMA
return self.async_show_form(
step_id="confirm",
data_schema=DATA_SCHEMA_DISCOVERY,
data_schema=data_schema,
errors=errors,
description_placeholders=self._discovered,
)
@ -103,21 +154,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
try:
return await self._try_create_entry(user_input)
bond_id, hub_name = await _validate_input(user_input)
except InputValidationError as error:
errors["base"] = error.base
else:
await self.async_set_unique_id(bond_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=hub_name, data=user_input)
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors
step_id="user", data_schema=USER_SCHEMA, errors=errors
)
async def _try_create_entry(self, data: Dict[str, Any]) -> Dict[str, Any]:
bond_id, name = await _validate_input(data)
await self.async_set_unique_id(bond_id)
self._abort_if_unique_id_configured()
hub_name = name or bond_id
return self.async_create_entry(title=hub_name, data=data)
class InputValidationError(exceptions.HomeAssistantError):
"""Error to indicate we cannot proceed due to invalid input."""

View file

@ -3,7 +3,7 @@
"name": "Bond",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bond",
"requirements": ["bond-api==0.1.10"],
"requirements": ["bond-api==0.1.11"],
"zeroconf": ["_bond._tcp.local."],
"codeowners": ["@prystupa"],
"quality_scale": "platinum"

View file

@ -1,9 +1,9 @@
{
"config": {
"flow_title": "Bond: {bond_id} ({host})",
"flow_title": "Bond: {name} ({host})",
"step": {
"confirm": {
"description": "Do you want to set up {bond_id}?",
"description": "Do you want to set up {name}?",
"data": {
"access_token": "[%key:common::config_flow::data::access_token%]"
}

View file

@ -9,13 +9,13 @@
"old_firmware": "Unsupported old firmware on the Bond device - please upgrade before continuing",
"unknown": "Unexpected error"
},
"flow_title": "Bond: {bond_id} ({host})",
"flow_title": "Bond: {name} ({host})",
"step": {
"confirm": {
"data": {
"access_token": "Access Token"
},
"description": "Do you want to set up {bond_id}?"
"description": "Do you want to set up {name}?"
},
"user": {
"data": {

View file

@ -150,11 +150,11 @@ class BondHub:
return self._version.get("make", BRIDGE_MAKE)
@property
def name(self) -> Optional[str]:
def name(self) -> str:
"""Get the name of this bridge."""
if not self.is_bridge and self._devices:
return self._devices[0].name
return self._bridge.get("name")
return self._bridge["name"]
@property
def location(self) -> Optional[str]:

View file

@ -371,7 +371,7 @@ blockchain==1.4.4
# bme680==1.0.5
# homeassistant.components.bond
bond-api==0.1.10
bond-api==0.1.11
# homeassistant.components.amazon_polly
# homeassistant.components.route53

View file

@ -205,7 +205,7 @@ blebox_uniapi==1.3.2
blinkpy==0.17.0
# homeassistant.components.bond
bond-api==0.1.10
bond-api==0.1.11
# homeassistant.components.braviatv
bravia-tv==1.0.8

View file

@ -30,12 +30,13 @@ async def setup_bond_entity(
patch_device_ids=False,
patch_platforms=False,
patch_bridge=False,
patch_token=False,
):
"""Set up Bond entity."""
config_entry.add_to_hass(hass)
with patch_start_bpup(), patch_bond_bridge(
enabled=patch_bridge
with patch_start_bpup(), patch_bond_bridge(enabled=patch_bridge), patch_bond_token(
enabled=patch_token
), patch_bond_version(enabled=patch_version), patch_bond_device_ids(
enabled=patch_device_ids
), patch_setup_entry(
@ -60,6 +61,7 @@ async def setup_platform(
props: Dict[str, Any] = None,
state: Dict[str, Any] = None,
bridge: Dict[str, Any] = None,
token: Dict[str, Any] = None,
):
"""Set up the specified Bond platform."""
mock_entry = MockConfigEntry(
@ -71,7 +73,7 @@ async def setup_platform(
with patch("homeassistant.components.bond.PLATFORMS", [platform]):
with patch_bond_version(return_value=bond_version), patch_bond_bridge(
return_value=bridge
), patch_bond_device_ids(
), patch_bond_token(return_value=token), patch_bond_device_ids(
return_value=[bond_device_id]
), patch_start_bpup(), patch_bond_device(
return_value=discovered_device
@ -124,6 +126,23 @@ def patch_bond_bridge(
)
def patch_bond_token(
enabled: bool = True, return_value: Optional[dict] = None, side_effect=None
):
"""Patch Bond API token endpoint."""
if not enabled:
return nullcontext()
if return_value is None:
return_value = {"locked": 1}
return patch(
"homeassistant.components.bond.Bond.token",
return_value=return_value,
side_effect=side_effect,
)
def patch_bond_device_ids(enabled: bool = True, return_value=None, side_effect=None):
"""Patch Bond API devices endpoint."""
if not enabled:

View file

@ -13,6 +13,7 @@ from .common import (
patch_bond_device,
patch_bond_device_ids,
patch_bond_device_properties,
patch_bond_token,
patch_bond_version,
)
@ -221,6 +222,70 @@ async def test_zeroconf_form(hass: core.HomeAssistant):
assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_form_token_unavailable(hass: core.HomeAssistant):
"""Test we get the discovery form and we handle the token being unavailable."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch_bond_version(), patch_bond_token():
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data={"name": "test-bond-id.some-other-tail-info", "host": "test-host"},
)
await hass.async_block_till_done()
assert result["type"] == "form"
assert result["errors"] == {}
with patch_bond_version(), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_ACCESS_TOKEN: "test-token"},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["title"] == "bond-name"
assert result2["data"] == {
CONF_HOST: "test-host",
CONF_ACCESS_TOKEN: "test-token",
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_form_with_token_available(hass: core.HomeAssistant):
"""Test we get the discovery form when we can get the token."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch_bond_version(return_value={"bondid": "test-bond-id"}), patch_bond_token(
return_value={"token": "discovered-token"}
), patch_bond_bridge(
return_value={"name": "discovered-name"}
), patch_bond_device_ids():
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data={"name": "test-bond-id.some-other-tail-info", "host": "test-host"},
)
await hass.async_block_till_done()
assert result["type"] == "form"
assert result["errors"] == {}
with _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["title"] == "discovered-name"
assert result2["data"] == {
CONF_HOST: "test-host",
CONF_ACCESS_TOKEN: "discovered-token",
}
assert len(mock_setup.mock_calls) == 1
assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_already_configured(hass: core.HomeAssistant):
"""Test starting a flow from discovery when already configured."""
await setup.async_setup_component(hass, "persistent_notification", {})