Simplify user flow
This commit is contained in:
parent
9a088fa8f1
commit
0ef9d1c6bb
5 changed files with 68 additions and 191 deletions
|
@ -29,7 +29,9 @@
|
|||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"no_adapters": "No unconfigured Bluetooth adapters found. There are {ignored_adapters} ignored adapters."
|
||||
"no_adapters": "No unconfigured Bluetooth adapters found. There are {ignored_adapters} ignored adapters.",
|
||||
"no_additional_devices_found": "No additional matching Bluetooth devices found",
|
||||
"no_devices_found": "No matching Bluetooth devices found"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
|
|
@ -5,13 +5,13 @@ import logging
|
|||
from typing import Any
|
||||
|
||||
from bleak import BleakError
|
||||
from py_dormakaba_dkey import DKEYLock, device_filter, errors as dkey_errors
|
||||
from py_dormakaba_dkey import DKEYLock, errors as dkey_errors
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothServiceInfoBleak,
|
||||
async_discovered_service_info,
|
||||
async_rediscover_address,
|
||||
)
|
||||
from homeassistant.const import CONF_ADDRESS
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
@ -43,48 +43,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the user step to pick discovered device."""
|
||||
errors: dict[str, str] = {}
|
||||
"""Handle the user step.
|
||||
|
||||
if user_input is not None:
|
||||
address = user_input[CONF_ADDRESS]
|
||||
await self.async_set_unique_id(address, raise_on_progress=False)
|
||||
# Guard against the user selecting a device which has been configured by
|
||||
# another flow.
|
||||
self._abort_if_unique_id_configured()
|
||||
self._discovery_info = self._discovered_devices[address]
|
||||
return await self.async_step_associate()
|
||||
|
||||
current_addresses = self._async_current_ids()
|
||||
for discovery in async_discovered_service_info(self.hass):
|
||||
if (
|
||||
discovery.address in current_addresses
|
||||
or discovery.address in self._discovered_devices
|
||||
or not device_filter(discovery.advertisement)
|
||||
):
|
||||
continue
|
||||
self._discovered_devices[discovery.address] = discovery
|
||||
|
||||
if not self._discovered_devices:
|
||||
return self.async_abort(reason="no_devices_found")
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ADDRESS): vol.In(
|
||||
{
|
||||
service_info.address: (
|
||||
f"{service_info.name} ({service_info.address})"
|
||||
)
|
||||
for service_info in self._discovered_devices.values()
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
The user will be shown a list of matching discovery flows automatically,
|
||||
so we just abort a user flow.
|
||||
"""
|
||||
if self._async_in_progress(include_uninitialized=True):
|
||||
return self.async_abort(reason="no_additional_devices_found")
|
||||
return self.async_abort(reason="no_devices_found")
|
||||
|
||||
async def async_step_bluetooth(
|
||||
self, discovery_info: BluetoothServiceInfoBleak
|
||||
|
@ -155,3 +121,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_show_form(
|
||||
step_id="associate", data_schema=STEP_ASSOCIATE_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_unignore(self, user_input: dict[str, Any]) -> FlowResult:
|
||||
"""Unignore an ignored bluetooth discovery flow."""
|
||||
async_rediscover_address(self.hass, user_input["unique_id"])
|
||||
return self.async_abort(reason="already_in_progress")
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
"config": {
|
||||
"flow_title": "[%key:component::bluetooth::config::flow_title%]",
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "[%key:component::bluetooth::config::step::user::description%]",
|
||||
"data": {
|
||||
"address": "[%key:component::bluetooth::config::step::user::data::address%]"
|
||||
}
|
||||
},
|
||||
"bluetooth_confirm": {
|
||||
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
|
||||
},
|
||||
|
@ -25,7 +19,8 @@
|
|||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||
"no_additional_devices_found": "[%key:component::bluetooth::config::abort::no_devices_found%]",
|
||||
"no_devices_found": "[%key:component::bluetooth::config::abort::no_devices_found%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,3 @@ DKEY_DISCOVERY_INFO = BluetoothServiceInfoBleak(
|
|||
time=0,
|
||||
connectable=True,
|
||||
)
|
||||
|
||||
|
||||
NOT_DKEY_DISCOVERY_INFO = BluetoothServiceInfoBleak(
|
||||
name="Not",
|
||||
address="AA:BB:CC:DD:EE:F2",
|
||||
rssi=-60,
|
||||
manufacturer_data={
|
||||
33: b"\x00\x00\xd1\xf0b;\xd8\x1dE\xd6\xba\xeeL\xdd]\xf5\xb2\xe9",
|
||||
21: b"\x061\x00Z\x8f\x93\xb2\xec\x85\x06\x00i\x00\x02\x02Q\xed\x1d\xf0",
|
||||
},
|
||||
service_uuids=[],
|
||||
service_data={},
|
||||
source="local",
|
||||
device=BLEDevice(address="AA:BB:CC:DD:EE:F2", name="Aug"),
|
||||
advertisement=generate_advertisement_data(),
|
||||
time=0,
|
||||
connectable=True,
|
||||
)
|
||||
|
|
|
@ -10,134 +10,37 @@ from homeassistant import config_entries
|
|||
from homeassistant.components.dormakaba_dkey.const import DOMAIN
|
||||
from homeassistant.const import CONF_ADDRESS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import DKEY_DISCOVERY_INFO, NOT_DKEY_DISCOVERY_INFO
|
||||
from . import DKEY_DISCOVERY_INFO
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_user_step_success(hass: HomeAssistant) -> None:
|
||||
"""Test user step success path."""
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_discovered_service_info",
|
||||
return_value=[NOT_DKEY_DISCOVERY_INFO, DKEY_DISCOVERY_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_ADDRESS: DKEY_DISCOVERY_INFO.address,
|
||||
},
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "associate"
|
||||
assert result["errors"] is None
|
||||
|
||||
await _test_common_success(hass, result)
|
||||
|
||||
|
||||
async def test_user_step_no_devices_found(hass: HomeAssistant) -> None:
|
||||
"""Test user step with no devices found."""
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_discovered_service_info",
|
||||
return_value=[NOT_DKEY_DISCOVERY_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
async def test_user_step_no_new_devices_found(hass: HomeAssistant) -> None:
|
||||
"""Test user step with only existing devices found."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_ADDRESS: DKEY_DISCOVERY_INFO.address,
|
||||
},
|
||||
unique_id=DKEY_DISCOVERY_INFO.address,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_discovered_service_info",
|
||||
return_value=[DKEY_DISCOVERY_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
|
||||
|
||||
async def test_user_step_device_added_between_steps_1(hass: HomeAssistant) -> None:
|
||||
"""Test the device gets added via another flow between steps."""
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_discovered_service_info",
|
||||
return_value=[DKEY_DISCOVERY_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=DKEY_DISCOVERY_INFO.address,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"address": DKEY_DISCOVERY_INFO.address},
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_async_step_user_takes_precedence_over_discovery(hass):
|
||||
"""Test manual setup takes precedence over discovery."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
async def test_user_step(hass: HomeAssistant) -> None:
|
||||
"""Test user step when there is a discovery flow."""
|
||||
await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_BLUETOOTH},
|
||||
data=DKEY_DISCOVERY_INFO,
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "bluetooth_confirm"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_discovered_service_info",
|
||||
return_value=[DKEY_DISCOVERY_INFO],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_ADDRESS: DKEY_DISCOVERY_INFO.address,
|
||||
},
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "associate"
|
||||
assert result["errors"] is None
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "no_additional_devices_found"
|
||||
return
|
||||
|
||||
await _test_common_success(hass, result)
|
||||
|
||||
# Verify the discovery flow was aborted
|
||||
assert not hass.config_entries.flow.async_progress(DOMAIN)
|
||||
async def test_user_step_no_discovery_flow(hass: HomeAssistant) -> None:
|
||||
"""Test user step with no discovery flows."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "no_devices_found"
|
||||
return
|
||||
|
||||
|
||||
async def test_bluetooth_step_success(hass: HomeAssistant) -> None:
|
||||
|
@ -156,12 +59,6 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None:
|
|||
assert result["step_id"] == "associate"
|
||||
assert result["errors"] is None
|
||||
|
||||
await _test_common_success(hass, result)
|
||||
|
||||
|
||||
async def _test_common_success(hass: HomeAssistant, result: FlowResult) -> None:
|
||||
"""Test bluetooth and user flow success paths."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.DKEYLock.associate",
|
||||
return_value=AssociationData(b"1234", b"AABBCCDD"),
|
||||
|
@ -294,3 +191,33 @@ async def test_bluetooth_step_cannot_associate(hass: HomeAssistant, exc, error)
|
|||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "associate"
|
||||
assert result["errors"] == {"base": error}
|
||||
|
||||
|
||||
async def test_unignore_flow(hass: HomeAssistant) -> None:
|
||||
"""Test a config flow started by unignoring a device."""
|
||||
# Create ignored entry
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IGNORE},
|
||||
data={
|
||||
"unique_id": DKEY_DISCOVERY_INFO.address,
|
||||
"title": DKEY_DISCOVERY_INFO.name,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result["context"]["unique_id"] == DKEY_DISCOVERY_INFO.address
|
||||
|
||||
# Unignore and expect rediscover call to bluetooth
|
||||
with patch(
|
||||
"homeassistant.components.dormakaba_dkey.config_flow.async_rediscover_address",
|
||||
) as rediscover_mock:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_UNIGNORE},
|
||||
data={"unique_id": DKEY_DISCOVERY_INFO.address},
|
||||
)
|
||||
rediscover_mock.assert_called_once_with(hass, DKEY_DISCOVERY_INFO.address)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_in_progress"
|
||||
|
|
Loading…
Add table
Reference in a new issue