mirror of
https://github.com/home-assistant/core.git
synced 2026-05-22 08:20:04 +01:00
Drop auto_discovered config in nobo_hub (#169558)
This commit is contained in:
committed by
GitHub
parent
b57e2814a7
commit
6c08950995
@@ -19,7 +19,6 @@ from homeassistant.util import dt as dt_util
|
||||
from .const import (
|
||||
ATTR_HARDWARE_VERSION,
|
||||
ATTR_SOFTWARE_VERSION,
|
||||
CONF_AUTO_DISCOVERED,
|
||||
CONF_OVERRIDE_TYPE,
|
||||
CONF_SERIAL,
|
||||
DOMAIN,
|
||||
@@ -36,7 +35,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: NoboHubConfigEntry) -> b
|
||||
|
||||
serial = entry.data[CONF_SERIAL]
|
||||
stored_ip = entry.data[CONF_IP_ADDRESS]
|
||||
auto_discovered = entry.data[CONF_AUTO_DISCOVERED]
|
||||
|
||||
async def _connect(ip: str) -> nobo:
|
||||
hub = nobo(
|
||||
@@ -52,20 +50,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: NoboHubConfigEntry) -> b
|
||||
try:
|
||||
hub = await _connect(stored_ip)
|
||||
except OSError as err:
|
||||
if not auto_discovered:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect_manual",
|
||||
translation_placeholders={"serial": serial, "ip": stored_ip},
|
||||
) from err
|
||||
# Stored IP may be stale for an auto-discovered entry - try UDP
|
||||
# rediscovery to pick up a new DHCP lease.
|
||||
# Stored IP may be stale - try UDP rediscovery to pick up a new
|
||||
# DHCP lease (or a hub that's been moved).
|
||||
discovered = await nobo.async_discover_hubs(serial=serial)
|
||||
if not discovered:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="hub_not_found",
|
||||
translation_placeholders={"serial": serial},
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={"serial": serial, "ip": stored_ip},
|
||||
) from err
|
||||
new_ip, _ = next(iter(discovered))
|
||||
try:
|
||||
@@ -73,8 +65,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: NoboHubConfigEntry) -> b
|
||||
except OSError as rediscover_err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect_rediscovered",
|
||||
translation_placeholders={"ip": new_ip},
|
||||
translation_key="cannot_connect",
|
||||
translation_placeholders={"serial": serial, "ip": new_ip},
|
||||
) from rediscover_err
|
||||
if new_ip != stored_ip:
|
||||
hass.config_entries.async_update_entry(
|
||||
@@ -129,4 +121,13 @@ async def async_migrate_entry(hass: HomeAssistant, entry: NoboHubConfigEntry) ->
|
||||
entry, options=new_options, version=1, minor_version=2
|
||||
)
|
||||
|
||||
if entry.version == 1 and entry.minor_version < 3:
|
||||
# auto_discovered no longer affects behaviour; rediscovery is now
|
||||
# the unconditional fallback on connection failure.
|
||||
new_data = dict(entry.data)
|
||||
new_data.pop("auto_discovered", None)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, version=1, minor_version=3
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -20,7 +20,6 @@ from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
||||
|
||||
from . import NoboHubConfigEntry
|
||||
from .const import (
|
||||
CONF_AUTO_DISCOVERED,
|
||||
CONF_OVERRIDE_TYPE,
|
||||
CONF_SERIAL,
|
||||
DOMAIN,
|
||||
@@ -36,7 +35,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Nobø Ecohub."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
MINOR_VERSION = 3
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
@@ -85,7 +84,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
serial_suffix = user_input["serial_suffix"]
|
||||
serial = f"{serial_prefix}{serial_suffix}"
|
||||
try:
|
||||
return await self._create_configuration(serial, self._hub, True)
|
||||
return await self._create_configuration(serial, self._hub)
|
||||
except NoboHubConnectError as error:
|
||||
errors["base"] = error.msg
|
||||
|
||||
@@ -114,7 +113,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
serial = user_input[CONF_SERIAL]
|
||||
ip_address = user_input[CONF_IP_ADDRESS]
|
||||
try:
|
||||
return await self._create_configuration(serial, ip_address, False)
|
||||
return await self._create_configuration(serial, ip_address)
|
||||
except NoboHubConnectError as error:
|
||||
errors["base"] = error.msg
|
||||
|
||||
@@ -133,7 +132,7 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
async def _create_configuration(
|
||||
self, serial: str, ip_address: str, auto_discovered: bool
|
||||
self, serial: str, ip_address: str
|
||||
) -> ConfigFlowResult:
|
||||
await self.async_set_unique_id(serial)
|
||||
self._abort_if_unique_id_configured()
|
||||
@@ -143,7 +142,6 @@ class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
data={
|
||||
CONF_SERIAL: serial,
|
||||
CONF_IP_ADDRESS: ip_address,
|
||||
CONF_AUTO_DISCOVERED: auto_discovered,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
DOMAIN = "nobo_hub"
|
||||
|
||||
CONF_AUTO_DISCOVERED = "auto_discovered"
|
||||
CONF_SERIAL = "serial"
|
||||
CONF_OVERRIDE_TYPE = "override_type"
|
||||
OVERRIDE_TYPE_CONSTANT = "constant"
|
||||
|
||||
@@ -58,14 +58,8 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"cannot_connect_manual": {
|
||||
"message": "Unable to connect to Nobø Ecohub with serial {serial} at {ip}. If the hub has moved to a new IP address, remove and re-add the integration."
|
||||
},
|
||||
"cannot_connect_rediscovered": {
|
||||
"message": "Unable to connect to Nobø Ecohub at rediscovered IP {ip}; will retry."
|
||||
},
|
||||
"hub_not_found": {
|
||||
"message": "Nobø Ecohub with serial {serial} not found on the network. The hub may be offline or on a different subnet; will retry."
|
||||
"cannot_connect": {
|
||||
"message": "Unable to connect to Nobø Ecohub with serial {serial} at {ip}; will retry. If the hub is on a different network from Home Assistant and has changed IP address, remove and re-add the integration."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
||||
@@ -8,11 +8,7 @@ from pynobo import nobo as pynobo_nobo
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.nobo_hub import PLATFORMS
|
||||
from homeassistant.components.nobo_hub.const import (
|
||||
CONF_AUTO_DISCOVERED,
|
||||
CONF_SERIAL,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.nobo_hub.const import CONF_SERIAL, DOMAIN
|
||||
from homeassistant.const import CONF_IP_ADDRESS, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -46,12 +42,6 @@ def ip_address() -> str:
|
||||
return STORED_IP
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auto_discovered() -> bool:
|
||||
"""Return whether the config entry was auto-discovered."""
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def connect_exc() -> BaseException | None:
|
||||
"""Exception to raise from hub.connect(), or None for success."""
|
||||
@@ -67,7 +57,6 @@ def config_entry_options() -> dict[str, Any]:
|
||||
@pytest.fixture
|
||||
def mock_config_entry(
|
||||
ip_address: str,
|
||||
auto_discovered: bool,
|
||||
config_entry_options: dict[str, Any],
|
||||
) -> MockConfigEntry:
|
||||
"""Return a mock Nobø Ecohub config entry."""
|
||||
@@ -78,7 +67,6 @@ def mock_config_entry(
|
||||
data={
|
||||
CONF_SERIAL: SERIAL,
|
||||
CONF_IP_ADDRESS: ip_address,
|
||||
CONF_AUTO_DISCOVERED: auto_discovered,
|
||||
},
|
||||
options=config_entry_options,
|
||||
)
|
||||
|
||||
@@ -57,7 +57,6 @@ async def test_configure_with_discover(
|
||||
assert result3["data"] == {
|
||||
"ip_address": "1.1.1.1",
|
||||
"serial": "123456789012",
|
||||
"auto_discovered": True,
|
||||
}
|
||||
mock_connect.assert_awaited_once_with("1.1.1.1", "123456789012")
|
||||
mock_setup_entry.assert_awaited_once()
|
||||
@@ -102,7 +101,6 @@ async def test_configure_manual(
|
||||
assert result2["data"] == {
|
||||
"serial": "123456789012",
|
||||
"ip_address": "1.1.1.1",
|
||||
"auto_discovered": False,
|
||||
}
|
||||
mock_connect.assert_awaited_once_with("1.1.1.1", "123456789012")
|
||||
mock_setup_entry.assert_awaited_once()
|
||||
@@ -154,7 +152,6 @@ async def test_configure_user_selected_manual(
|
||||
assert result2["data"] == {
|
||||
"serial": "123456789012",
|
||||
"ip_address": "1.1.1.1",
|
||||
"auto_discovered": False,
|
||||
}
|
||||
mock_connect.assert_awaited_once_with("1.1.1.1", "123456789012")
|
||||
mock_setup_entry.assert_awaited_once()
|
||||
|
||||
@@ -7,7 +7,6 @@ import pytest
|
||||
|
||||
from homeassistant.components.nobo_hub import async_setup_entry
|
||||
from homeassistant.components.nobo_hub.const import (
|
||||
CONF_AUTO_DISCOVERED,
|
||||
CONF_OVERRIDE_TYPE,
|
||||
CONF_SERIAL,
|
||||
DOMAIN,
|
||||
@@ -44,12 +43,12 @@ def _spec_hub(connect_exc: BaseException | None = None) -> MagicMock:
|
||||
return hub
|
||||
|
||||
|
||||
async def test_setup_manual_entry_uses_stored_ip(
|
||||
async def test_setup_uses_stored_ip(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_nobo_class: MagicMock,
|
||||
) -> None:
|
||||
"""Manual entry connects using the stored IP without rediscovery."""
|
||||
"""Setup connects using the stored IP without invoking rediscovery."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
@@ -60,49 +59,11 @@ async def test_setup_manual_entry_uses_stored_ip(
|
||||
mock_nobo_class.async_discover_hubs.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("auto_discovered", [True])
|
||||
async def test_setup_autodiscovered_entry_uses_stored_ip(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_nobo_class: MagicMock,
|
||||
) -> None:
|
||||
"""Auto-discovered entry with a working stored IP does not rediscover."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
mock_nobo_class.async_discover_hubs.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"connect_exc",
|
||||
[OSError("Unreachable"), TimeoutError("Handshake timed out")],
|
||||
)
|
||||
async def test_setup_manual_entry_connection_fails(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_nobo_class: MagicMock,
|
||||
) -> None:
|
||||
"""Manual entry raises ConfigEntryNotReady on socket errors or timeouts."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with pytest.raises(ConfigEntryNotReady) as exc_info:
|
||||
await async_setup_entry(hass, mock_config_entry)
|
||||
|
||||
assert exc_info.value.translation_key == "cannot_connect_manual"
|
||||
assert exc_info.value.translation_placeholders == {
|
||||
"serial": SERIAL,
|
||||
"ip": STORED_IP,
|
||||
}
|
||||
mock_nobo_class.async_discover_hubs.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("auto_discovered", [True])
|
||||
async def test_setup_autodiscovered_rediscovery_updates_ip(
|
||||
async def test_setup_rediscovery_updates_ip(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Auto-discovered entry recovers via rediscovery and persists the new IP."""
|
||||
"""A failed direct connect falls back to rediscovery and persists the new IP."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.nobo_hub.nobo", autospec=True) as mock_cls:
|
||||
mock_cls.side_effect = [
|
||||
@@ -121,30 +82,26 @@ async def test_setup_autodiscovered_rediscovery_updates_ip(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"discovered_hubs",
|
||||
"rediscovered_connect_fails",
|
||||
"expected_key",
|
||||
"expected_placeholders",
|
||||
"auto_discovered",
|
||||
),
|
||||
("discovered_hubs", "second_exc", "expected_placeholders"),
|
||||
[
|
||||
(set(), False, "hub_not_found", {"serial": SERIAL}, True),
|
||||
({(NEW_IP, SERIAL)}, True, "cannot_connect_rediscovered", {"ip": NEW_IP}, True),
|
||||
(set(), None, {"serial": SERIAL, "ip": STORED_IP}),
|
||||
(
|
||||
{(NEW_IP, SERIAL)},
|
||||
OSError("Unreachable"),
|
||||
{"serial": SERIAL, "ip": NEW_IP},
|
||||
),
|
||||
],
|
||||
ids=["rediscovery_empty", "rediscovered_ip_fails"],
|
||||
)
|
||||
async def test_setup_autodiscovered_rediscovery_failure(
|
||||
async def test_setup_rediscovery_failure(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
discovered_hubs: set[tuple[str, str]],
|
||||
rediscovered_connect_fails: bool,
|
||||
expected_key: str,
|
||||
second_exc: BaseException | None,
|
||||
expected_placeholders: dict[str, str],
|
||||
) -> None:
|
||||
"""Auto-discovered entry raises the right error when rediscovery can't recover."""
|
||||
"""Setup raises cannot_connect when rediscovery can't recover."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
second_exc = OSError("Unreachable") if rediscovered_connect_fails else None
|
||||
with patch("homeassistant.components.nobo_hub.nobo", autospec=True) as mock_cls:
|
||||
mock_cls.side_effect = [
|
||||
_spec_hub(connect_exc=OSError("Unreachable")),
|
||||
@@ -154,25 +111,27 @@ async def test_setup_autodiscovered_rediscovery_failure(
|
||||
with pytest.raises(ConfigEntryNotReady) as exc_info:
|
||||
await async_setup_entry(hass, mock_config_entry)
|
||||
|
||||
assert exc_info.value.translation_key == expected_key
|
||||
assert exc_info.value.translation_key == "cannot_connect"
|
||||
assert exc_info.value.translation_placeholders == expected_placeholders
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("stored_value", "expected_value"),
|
||||
("stored_options", "expected_options"),
|
||||
[
|
||||
("Constant", "constant"),
|
||||
("Now", "now"),
|
||||
("constant", "constant"),
|
||||
({CONF_OVERRIDE_TYPE: "Constant"}, {CONF_OVERRIDE_TYPE: "constant"}),
|
||||
({CONF_OVERRIDE_TYPE: "Now"}, {CONF_OVERRIDE_TYPE: "now"}),
|
||||
({CONF_OVERRIDE_TYPE: "constant"}, {CONF_OVERRIDE_TYPE: "constant"}),
|
||||
({}, {}),
|
||||
],
|
||||
ids=["Constant", "Now", "already_lowercase", "no_options"],
|
||||
)
|
||||
async def test_migrate_options_lowercases_override_type(
|
||||
async def test_migrate_options(
|
||||
hass: HomeAssistant,
|
||||
mock_nobo_class: MagicMock,
|
||||
stored_value: str,
|
||||
expected_value: str,
|
||||
stored_options: dict[str, str],
|
||||
expected_options: dict[str, str],
|
||||
) -> None:
|
||||
"""Legacy capitalized override_type values are lowercased on migration."""
|
||||
"""Migrating from minor_version 1 lowercases override_type and bumps version."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="My Eco Hub",
|
||||
@@ -180,9 +139,8 @@ async def test_migrate_options_lowercases_override_type(
|
||||
data={
|
||||
CONF_SERIAL: SERIAL,
|
||||
CONF_IP_ADDRESS: STORED_IP,
|
||||
CONF_AUTO_DISCOVERED: False,
|
||||
},
|
||||
options={CONF_OVERRIDE_TYPE: stored_value},
|
||||
options=stored_options,
|
||||
version=1,
|
||||
minor_version=1,
|
||||
)
|
||||
@@ -190,15 +148,15 @@ async def test_migrate_options_lowercases_override_type(
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.minor_version == 2
|
||||
assert entry.options == {CONF_OVERRIDE_TYPE: expected_value}
|
||||
assert entry.minor_version == 3
|
||||
assert entry.options == expected_options
|
||||
|
||||
|
||||
async def test_migrate_options_without_override_type(
|
||||
async def test_migrate_data_drops_auto_discovered(
|
||||
hass: HomeAssistant,
|
||||
mock_nobo_class: MagicMock,
|
||||
) -> None:
|
||||
"""Migration still bumps the version when no override_type is stored."""
|
||||
"""The auto_discovered key is stripped from entry.data on migration."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="My Eco Hub",
|
||||
@@ -206,16 +164,17 @@ async def test_migrate_options_without_override_type(
|
||||
data={
|
||||
CONF_SERIAL: SERIAL,
|
||||
CONF_IP_ADDRESS: STORED_IP,
|
||||
CONF_AUTO_DISCOVERED: False,
|
||||
"auto_discovered": True,
|
||||
},
|
||||
version=1,
|
||||
minor_version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.minor_version == 2
|
||||
assert entry.minor_version == 3
|
||||
assert entry.data == {CONF_SERIAL: SERIAL, CONF_IP_ADDRESS: STORED_IP}
|
||||
assert entry.options == {}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user