Avoid having to ask for the bond token when possible during config (#46845)
This commit is contained in:
parent
00dd557cce
commit
d68a51ddce
9 changed files with 166 additions and 34 deletions
|
@ -14,16 +14,17 @@ from homeassistant.const import (
|
||||||
HTTP_UNAUTHORIZED,
|
HTTP_UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import CONF_BOND_ID
|
|
||||||
from .const import DOMAIN # pylint:disable=unused-import
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
from .utils import BondHub
|
from .utils import BondHub
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DATA_SCHEMA_USER = vol.Schema(
|
|
||||||
|
USER_SCHEMA = vol.Schema(
|
||||||
{vol.Required(CONF_HOST): str, vol.Required(CONF_ACCESS_TOKEN): str}
|
{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]]:
|
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
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
|
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(
|
async def async_step_zeroconf(
|
||||||
self, discovery_info: Optional[Dict[str, Any]] = None
|
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)
|
await self.async_set_unique_id(bond_id)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: host})
|
self._abort_if_unique_id_configured({CONF_HOST: host})
|
||||||
|
|
||||||
self._discovered = {
|
self._discovered = {CONF_HOST: host, CONF_NAME: bond_id}
|
||||||
CONF_HOST: host,
|
await self._async_try_automatic_configure()
|
||||||
CONF_BOND_ID: bond_id,
|
|
||||||
}
|
self.context.update(
|
||||||
self.context.update({"title_placeholders": self._discovered})
|
{
|
||||||
|
"title_placeholders": {
|
||||||
|
CONF_HOST: self._discovered[CONF_HOST],
|
||||||
|
CONF_NAME: self._discovered[CONF_NAME],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return await self.async_step_confirm()
|
return await self.async_step_confirm()
|
||||||
|
|
||||||
|
@ -82,16 +112,37 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle confirmation flow for discovered bond hub."""
|
"""Handle confirmation flow for discovered bond hub."""
|
||||||
errors = {}
|
errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
data = user_input.copy()
|
if CONF_ACCESS_TOKEN in self._discovered:
|
||||||
data[CONF_HOST] = self._discovered[CONF_HOST]
|
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:
|
try:
|
||||||
return await self._try_create_entry(data)
|
_, hub_name = await _validate_input(data)
|
||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
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(
|
return self.async_show_form(
|
||||||
step_id="confirm",
|
step_id="confirm",
|
||||||
data_schema=DATA_SCHEMA_DISCOVERY,
|
data_schema=data_schema,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
description_placeholders=self._discovered,
|
description_placeholders=self._discovered,
|
||||||
)
|
)
|
||||||
|
@ -103,21 +154,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors = {}
|
errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
return await self._try_create_entry(user_input)
|
bond_id, hub_name = await _validate_input(user_input)
|
||||||
except InputValidationError as error:
|
except InputValidationError as error:
|
||||||
errors["base"] = error.base
|
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(
|
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):
|
class InputValidationError(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate we cannot proceed due to invalid input."""
|
"""Error to indicate we cannot proceed due to invalid input."""
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Bond",
|
"name": "Bond",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/bond",
|
"documentation": "https://www.home-assistant.io/integrations/bond",
|
||||||
"requirements": ["bond-api==0.1.10"],
|
"requirements": ["bond-api==0.1.11"],
|
||||||
"zeroconf": ["_bond._tcp.local."],
|
"zeroconf": ["_bond._tcp.local."],
|
||||||
"codeowners": ["@prystupa"],
|
"codeowners": ["@prystupa"],
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"flow_title": "Bond: {bond_id} ({host})",
|
"flow_title": "Bond: {name} ({host})",
|
||||||
"step": {
|
"step": {
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"description": "Do you want to set up {bond_id}?",
|
"description": "Do you want to set up {name}?",
|
||||||
"data": {
|
"data": {
|
||||||
"access_token": "[%key:common::config_flow::data::access_token%]"
|
"access_token": "[%key:common::config_flow::data::access_token%]"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
"old_firmware": "Unsupported old firmware on the Bond device - please upgrade before continuing",
|
"old_firmware": "Unsupported old firmware on the Bond device - please upgrade before continuing",
|
||||||
"unknown": "Unexpected error"
|
"unknown": "Unexpected error"
|
||||||
},
|
},
|
||||||
"flow_title": "Bond: {bond_id} ({host})",
|
"flow_title": "Bond: {name} ({host})",
|
||||||
"step": {
|
"step": {
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"data": {
|
"data": {
|
||||||
"access_token": "Access Token"
|
"access_token": "Access Token"
|
||||||
},
|
},
|
||||||
"description": "Do you want to set up {bond_id}?"
|
"description": "Do you want to set up {name}?"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
|
|
|
@ -150,11 +150,11 @@ class BondHub:
|
||||||
return self._version.get("make", BRIDGE_MAKE)
|
return self._version.get("make", BRIDGE_MAKE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> Optional[str]:
|
def name(self) -> str:
|
||||||
"""Get the name of this bridge."""
|
"""Get the name of this bridge."""
|
||||||
if not self.is_bridge and self._devices:
|
if not self.is_bridge and self._devices:
|
||||||
return self._devices[0].name
|
return self._devices[0].name
|
||||||
return self._bridge.get("name")
|
return self._bridge["name"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self) -> Optional[str]:
|
def location(self) -> Optional[str]:
|
||||||
|
|
|
@ -371,7 +371,7 @@ blockchain==1.4.4
|
||||||
# bme680==1.0.5
|
# bme680==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-api==0.1.10
|
bond-api==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.amazon_polly
|
# homeassistant.components.amazon_polly
|
||||||
# homeassistant.components.route53
|
# homeassistant.components.route53
|
||||||
|
|
|
@ -205,7 +205,7 @@ blebox_uniapi==1.3.2
|
||||||
blinkpy==0.17.0
|
blinkpy==0.17.0
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-api==0.1.10
|
bond-api==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.braviatv
|
# homeassistant.components.braviatv
|
||||||
bravia-tv==1.0.8
|
bravia-tv==1.0.8
|
||||||
|
|
|
@ -30,12 +30,13 @@ async def setup_bond_entity(
|
||||||
patch_device_ids=False,
|
patch_device_ids=False,
|
||||||
patch_platforms=False,
|
patch_platforms=False,
|
||||||
patch_bridge=False,
|
patch_bridge=False,
|
||||||
|
patch_token=False,
|
||||||
):
|
):
|
||||||
"""Set up Bond entity."""
|
"""Set up Bond entity."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
with patch_start_bpup(), patch_bond_bridge(
|
with patch_start_bpup(), patch_bond_bridge(enabled=patch_bridge), patch_bond_token(
|
||||||
enabled=patch_bridge
|
enabled=patch_token
|
||||||
), patch_bond_version(enabled=patch_version), patch_bond_device_ids(
|
), patch_bond_version(enabled=patch_version), patch_bond_device_ids(
|
||||||
enabled=patch_device_ids
|
enabled=patch_device_ids
|
||||||
), patch_setup_entry(
|
), patch_setup_entry(
|
||||||
|
@ -60,6 +61,7 @@ async def setup_platform(
|
||||||
props: Dict[str, Any] = None,
|
props: Dict[str, Any] = None,
|
||||||
state: Dict[str, Any] = None,
|
state: Dict[str, Any] = None,
|
||||||
bridge: Dict[str, Any] = None,
|
bridge: Dict[str, Any] = None,
|
||||||
|
token: Dict[str, Any] = None,
|
||||||
):
|
):
|
||||||
"""Set up the specified Bond platform."""
|
"""Set up the specified Bond platform."""
|
||||||
mock_entry = MockConfigEntry(
|
mock_entry = MockConfigEntry(
|
||||||
|
@ -71,7 +73,7 @@ async def setup_platform(
|
||||||
with patch("homeassistant.components.bond.PLATFORMS", [platform]):
|
with patch("homeassistant.components.bond.PLATFORMS", [platform]):
|
||||||
with patch_bond_version(return_value=bond_version), patch_bond_bridge(
|
with patch_bond_version(return_value=bond_version), patch_bond_bridge(
|
||||||
return_value=bridge
|
return_value=bridge
|
||||||
), patch_bond_device_ids(
|
), patch_bond_token(return_value=token), patch_bond_device_ids(
|
||||||
return_value=[bond_device_id]
|
return_value=[bond_device_id]
|
||||||
), patch_start_bpup(), patch_bond_device(
|
), patch_start_bpup(), patch_bond_device(
|
||||||
return_value=discovered_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):
|
def patch_bond_device_ids(enabled: bool = True, return_value=None, side_effect=None):
|
||||||
"""Patch Bond API devices endpoint."""
|
"""Patch Bond API devices endpoint."""
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
|
|
@ -13,6 +13,7 @@ from .common import (
|
||||||
patch_bond_device,
|
patch_bond_device,
|
||||||
patch_bond_device_ids,
|
patch_bond_device_ids,
|
||||||
patch_bond_device_properties,
|
patch_bond_device_properties,
|
||||||
|
patch_bond_token,
|
||||||
patch_bond_version,
|
patch_bond_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -221,6 +222,70 @@ async def test_zeroconf_form(hass: core.HomeAssistant):
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
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):
|
async def test_zeroconf_already_configured(hass: core.HomeAssistant):
|
||||||
"""Test starting a flow from discovery when already configured."""
|
"""Test starting a flow from discovery when already configured."""
|
||||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
Loading…
Add table
Reference in a new issue