From cffbb385cb2b4c56ba3e13c815219fe42f7debec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Jun 2026 16:50:18 -0500 Subject: [PATCH] Fix network adapter change detection and address review feedback --- homeassistant/components/network/__init__.py | 4 +++- homeassistant/components/network/network.py | 15 +++++++++------ tests/components/hassio/test_init.py | 2 +- tests/components/network/test_init.py | 20 +++++++++++++------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index c3cbf212a56d..35ce350ca6f5 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -21,7 +21,7 @@ from .const import ( SIGNAL_NETWORK_ADAPTERS_CHANGED, ) from .models import Adapter -from .network import Network, async_get_loaded_network, async_get_network +from .network import DATA_NETWORK, Network, async_get_loaded_network, async_get_network _LOGGER = logging.getLogger(__name__) @@ -55,6 +55,8 @@ def async_get_loaded_adapters(hass: HomeAssistant) -> list[Adapter]: async def async_reload_adapters(hass: HomeAssistant) -> None: """Reload the network adapters and notify listeners if they changed.""" + if DATA_NETWORK not in hass.data: + return if await async_get_loaded_network(hass).async_reload(): async_dispatcher_send(hass, SIGNAL_NETWORK_ADAPTERS_CHANGED) diff --git a/homeassistant/components/network/network.py b/homeassistant/components/network/network.py index cfb12ea70692..0af30a509513 100644 --- a/homeassistant/components/network/network.py +++ b/homeassistant/components/network/network.py @@ -64,13 +64,16 @@ class Network: await storage_load_task async def async_reload(self) -> bool: - """Reload adapters from the system, returning True if they changed.""" - adapters = await async_load_adapters() - if adapters == self.adapters: - return False - self.adapters = adapters + """Reload adapters from the system, returning True if they changed. + + Freshly loaded adapters are unconfigured (all disabled), so the new + set must be configured before it can be compared against the current, + already configured adapters. + """ + previous = self.adapters + self.adapters = await async_load_adapters() self.async_configure() - return True + return self.adapters != previous @callback def async_configure(self) -> None: diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 65028940ae43..417488ed45d9 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -254,6 +254,7 @@ async def test_setup_onboarding_supervisor_update_error( supervisor_client.supervisor.update.assert_called_once() +@pytest.mark.usefixtures("supervisor_client") @pytest.mark.parametrize( ("update_key", "expected_calls"), [("network", 1), ("supervisor", 0)], @@ -261,7 +262,6 @@ async def test_setup_onboarding_supervisor_update_error( ) async def test_supervisor_network_event_reloads_adapters( hass: HomeAssistant, - supervisor_client: AsyncMock, update_key: str, expected_calls: int, ) -> None: diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index ce8df3621461..5640e0252d5a 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -707,8 +707,8 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [ async def test_async_reload_adapters(hass: HomeAssistant) -> None: """Test reloading adapters dispatches a signal only when they change.""" with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], + "homeassistant.components.network.network.async_load_adapters", + return_value=deepcopy(_ADAPTERS_WITH_MANUAL_CONFIG), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -721,22 +721,28 @@ async def test_async_reload_adapters(hass: HomeAssistant) -> None: async_dispatcher_connect(hass, SIGNAL_NETWORK_ADAPTERS_CHANGED, _track) - # Unchanged adapters do not dispatch a signal. + # The same hardware loads unconfigured (all disabled); once configured it + # matches the current adapters, so no signal is dispatched. + unconfigured = deepcopy(_ADAPTERS_WITH_MANUAL_CONFIG) + for adapter in unconfigured: + adapter["enabled"] = False with patch( "homeassistant.components.network.network.async_load_adapters", - return_value=network.async_get_loaded_adapters(hass), + return_value=unconfigured, ): await network.async_reload_adapters(hass) assert signals == [] - # Changed adapters dispatch a signal and update the loaded adapters. + # A physically different adapter set dispatches a signal. + changed = deepcopy(_ADAPTERS_WITH_MANUAL_CONFIG) + changed[1]["ipv4"][0]["address"] = "192.168.1.99" with patch( "homeassistant.components.network.network.async_load_adapters", - return_value=deepcopy(_ADAPTERS_WITH_MANUAL_CONFIG), + return_value=changed, ): await network.async_reload_adapters(hass) assert len(signals) == 1 - assert network.async_get_loaded_adapters(hass) == _ADAPTERS_WITH_MANUAL_CONFIG + assert network.async_get_loaded_adapters(hass) == changed async def test_async_get_announce_addresses(hass: HomeAssistant) -> None: