diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 0132df486d3..f81e3a0be5c 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -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.""" diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index cf009c11caa..65cb6a83bb2 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -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" diff --git a/homeassistant/components/bond/strings.json b/homeassistant/components/bond/strings.json index 5ca2278a3e5..f8eff6ddd9e 100644 --- a/homeassistant/components/bond/strings.json +++ b/homeassistant/components/bond/strings.json @@ -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%]" } diff --git a/homeassistant/components/bond/translations/en.json b/homeassistant/components/bond/translations/en.json index 945b09b8186..d9ce8ab0fe4 100644 --- a/homeassistant/components/bond/translations/en.json +++ b/homeassistant/components/bond/translations/en.json @@ -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": { diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 55ef81778f0..225eec87d98 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -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]: diff --git a/requirements_all.txt b/requirements_all.txt index 638989b46c9..7079c94a00b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 254f8cdf86a..a434739be14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 54d127832b5..061dc23797e 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -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: diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 2a76e1fa6a0..39fd1a2db5d 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -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", {})