mirror of
https://github.com/home-assistant/core.git
synced 2026-07-03 12:46:09 +01:00
Filter already-configured hubs from nobo_hub config flow (#169593)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
e8e5eedd7e
commit
97844d7ee0
@@ -25,6 +25,8 @@ from .const import (
|
||||
DOMAIN,
|
||||
OVERRIDE_TYPE_CONSTANT,
|
||||
OVERRIDE_TYPE_NOW,
|
||||
SERIAL_LENGTH,
|
||||
SERIAL_PREFIX_LENGTH,
|
||||
)
|
||||
|
||||
DATA_NOBO_HUB_IMPL = "nobo_hub_flow_implementation"
|
||||
@@ -49,7 +51,20 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
if self._discovered_hubs is None:
|
||||
self._discovered_hubs = dict(await nobo.async_discover_hubs())
|
||||
# Wait 5s — real-world gaps up to ~4s have been observed.
|
||||
discovered = dict(await nobo.async_discover_hubs(autodiscover_wait=5.0))
|
||||
# Hide hubs that already have a config entry. Include matching on IP
|
||||
# as serial prefix is not unique.
|
||||
configured = {
|
||||
(entry.data[CONF_IP_ADDRESS], entry.unique_id[:SERIAL_PREFIX_LENGTH])
|
||||
for entry in self._async_current_entries(include_ignore=False)
|
||||
if entry.unique_id
|
||||
}
|
||||
self._discovered_hubs = {
|
||||
ip: prefix
|
||||
for ip, prefix in discovered.items()
|
||||
if (ip, prefix) not in configured
|
||||
}
|
||||
|
||||
if not self._discovered_hubs:
|
||||
# No hubs auto discovered
|
||||
@@ -227,7 +242,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def _test_connection(self, serial: str, ip_address: str) -> str:
|
||||
if not len(serial) == 12 or not serial.isdigit():
|
||||
if len(serial) != SERIAL_LENGTH or not serial.isdigit():
|
||||
raise NoboHubConnectError("invalid_serial")
|
||||
try:
|
||||
socket.inet_aton(ip_address)
|
||||
|
||||
@@ -9,6 +9,11 @@ CONF_OVERRIDE_TYPE = "override_type"
|
||||
OVERRIDE_TYPE_CONSTANT = "constant"
|
||||
OVERRIDE_TYPE_NOW = "now"
|
||||
|
||||
# Hub serial: 9-digit batch prefix + 3-digit per-hub suffix. Discovery
|
||||
# broadcasts only the prefix; the user supplies the suffix.
|
||||
SERIAL_PREFIX_LENGTH = 9
|
||||
SERIAL_LENGTH = SERIAL_PREFIX_LENGTH + 3
|
||||
|
||||
NOBO_MANUFACTURER = "Glen Dimplex Nordic AS"
|
||||
ATTR_HARDWARE_VERSION: Final = "hardware_version"
|
||||
ATTR_SOFTWARE_VERSION: Final = "software_version"
|
||||
|
||||
@@ -32,12 +32,13 @@ async def test_configure_with_discover(
|
||||
with patch(
|
||||
"homeassistant.components.nobo_hub.config_flow.nobo.async_discover_hubs",
|
||||
return_value=[("1.1.1.1", "123456789")],
|
||||
):
|
||||
) as mock_discover:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
mock_discover.assert_awaited_once_with(autodiscover_wait=5.0)
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
@@ -76,6 +77,106 @@ async def test_configure_with_discover(
|
||||
mock_setup_entry.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("discovered", "expected_devices", "selected_device"),
|
||||
[
|
||||
# Same IP+prefix hidden; sibling with same prefix at a different IP shown.
|
||||
(
|
||||
[("1.1.1.1", "111111111"), ("2.2.2.2", "111111111")],
|
||||
{"2.2.2.2", "manual"},
|
||||
"2.2.2.2",
|
||||
),
|
||||
# Same IP, different prefix → different hub (e.g. replacement), shown.
|
||||
([("1.1.1.1", "222222222")], {"1.1.1.1", "manual"}, "1.1.1.1"),
|
||||
],
|
||||
ids=["sibling_different_ip", "replaced_hub"],
|
||||
)
|
||||
async def test_configure_filters_configured_hubs(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
discovered: list[tuple[str, str]],
|
||||
expected_devices: set[str],
|
||||
selected_device: str,
|
||||
) -> None:
|
||||
"""Configured (IP, prefix) pairs are hidden; the user can pick a remaining one."""
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="111111111012",
|
||||
data={CONF_SERIAL: "111111111012", CONF_IP_ADDRESS: "1.1.1.1"},
|
||||
).add_to_hass(hass)
|
||||
|
||||
with patch("pynobo.nobo.async_discover_hubs", return_value=discovered):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert set(result["data_schema"].schema["device"].container) == expected_devices
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"device": selected_device},
|
||||
)
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "selected"
|
||||
|
||||
with (
|
||||
patch("pynobo.nobo.async_connect_hub", return_value=True),
|
||||
patch(
|
||||
"pynobo.nobo.hub_info",
|
||||
new_callable=PropertyMock,
|
||||
create=True,
|
||||
return_value={"name": "My Nobø Ecohub"},
|
||||
),
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{"serial_suffix": "999"},
|
||||
)
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_configure_skips_user_step_when_all_configured(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Flow falls through to manual when every discovered hub matches a configured pair."""
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id="111111111012",
|
||||
data={CONF_SERIAL: "111111111012", CONF_IP_ADDRESS: "1.1.1.1"},
|
||||
).add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"pynobo.nobo.async_discover_hubs",
|
||||
return_value=[("1.1.1.1", "111111111")],
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "manual"
|
||||
|
||||
with (
|
||||
patch("pynobo.nobo.async_connect_hub", return_value=True),
|
||||
patch(
|
||||
"pynobo.nobo.hub_info",
|
||||
new_callable=PropertyMock,
|
||||
create=True,
|
||||
return_value={"name": "My Nobø Ecohub"},
|
||||
),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"serial": "999999999999", "ip_address": "9.9.9.9"},
|
||||
)
|
||||
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_configure_manual(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
|
||||
Reference in New Issue
Block a user