From 9db60c830cdcfbdf206d979bbe2b600212bd2236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Fri, 4 Apr 2025 09:56:40 +0200 Subject: [PATCH] Do not fetch disconnected Home Connect appliances (#142200) * Do not fetch disconnected Home Connect appliances * Apply suggestions Co-authored-by: Martin Hjelmare * Update docstring --------- Co-authored-by: Martin Hjelmare --- .../components/home_connect/coordinator.py | 32 ++++++--- .../home_connect/test_coordinator.py | 71 +++++++++++++++++-- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/home_connect/coordinator.py b/homeassistant/components/home_connect/coordinator.py index 079db6b148e..edaf5ea7332 100644 --- a/homeassistant/components/home_connect/coordinator.py +++ b/homeassistant/components/home_connect/coordinator.py @@ -73,6 +73,19 @@ class HomeConnectApplianceData: self.settings.update(other.settings) self.status.update(other.status) + @classmethod + def empty(cls, appliance: HomeAppliance) -> HomeConnectApplianceData: + """Return empty data.""" + return cls( + commands=set(), + events={}, + info=appliance, + options={}, + programs=[], + settings={}, + status={}, + ) + class HomeConnectCoordinator( DataUpdateCoordinator[dict[str, HomeConnectApplianceData]] @@ -358,15 +371,7 @@ class HomeConnectCoordinator( model=appliance.vib, ) if appliance.ha_id not in self.data: - self.data[appliance.ha_id] = HomeConnectApplianceData( - commands=set(), - events={}, - info=appliance, - options={}, - programs=[], - settings={}, - status={}, - ) + self.data[appliance.ha_id] = HomeConnectApplianceData.empty(appliance) else: self.data[appliance.ha_id].info.connected = appliance.connected old_appliances.remove(appliance.ha_id) @@ -402,6 +407,15 @@ class HomeConnectCoordinator( name=appliance.name, model=appliance.vib, ) + if not appliance.connected: + _LOGGER.debug( + "Appliance %s is not connected, skipping data fetch", + appliance.ha_id, + ) + if appliance_data_to_update: + appliance_data_to_update.info.connected = False + return appliance_data_to_update + return HomeConnectApplianceData.empty(appliance) try: settings = { setting.key: setting diff --git a/tests/components/home_connect/test_coordinator.py b/tests/components/home_connect/test_coordinator.py index 050758a6568..68f8299e613 100644 --- a/tests/components/home_connect/test_coordinator.py +++ b/tests/components/home_connect/test_coordinator.py @@ -54,6 +54,14 @@ from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry, async_fire_time_changed +INITIAL_FETCH_CLIENT_METHODS = [ + "get_settings", + "get_status", + "get_all_programs", + "get_available_commands", + "get_available_program", +] + @pytest.fixture def platforms() -> list[str]: @@ -214,15 +222,32 @@ async def test_coordinator_failure_refresh_and_stream( assert state.state != STATE_UNAVAILABLE +@pytest.mark.parametrize( + "appliance", + ["Dishwasher"], + indirect=True, +) +async def test_coordinator_not_fetching_on_disconnected_appliance( + config_entry: MockConfigEntry, + integration_setup: Callable[[MagicMock], Awaitable[bool]], + setup_credentials: None, + client: MagicMock, + appliance: HomeAppliance, +) -> None: + """Test that the coordinator does not fetch anything on disconnected appliance.""" + appliance.connected = False + + assert config_entry.state == ConfigEntryState.NOT_LOADED + await integration_setup(client) + assert config_entry.state == ConfigEntryState.LOADED + + for method in INITIAL_FETCH_CLIENT_METHODS: + assert getattr(client, method).call_count == 0 + + @pytest.mark.parametrize( "mock_method", - [ - "get_settings", - "get_status", - "get_all_programs", - "get_available_commands", - "get_available_program", - ], + INITIAL_FETCH_CLIENT_METHODS, ) async def test_coordinator_update_failing( mock_method: str, @@ -551,3 +576,35 @@ async def test_devices_updated_on_refresh( assert not device_registry.async_get_device({(DOMAIN, appliances[0].ha_id)}) for appliance in appliances[2:3]: assert device_registry.async_get_device({(DOMAIN, appliance.ha_id)}) + + +@pytest.mark.parametrize("appliance", ["Washer"], indirect=True) +async def test_paired_disconnected_devices_not_fetching( + hass: HomeAssistant, + config_entry: MockConfigEntry, + integration_setup: Callable[[MagicMock], Awaitable[bool]], + setup_credentials: None, + client: MagicMock, + appliance: HomeAppliance, +) -> None: + """Test that Home Connect API is not fetched after pairing a disconnected device.""" + client.get_home_appliances = AsyncMock(return_value=ArrayOfHomeAppliances([])) + assert config_entry.state == ConfigEntryState.NOT_LOADED + assert await integration_setup(client) + assert config_entry.state == ConfigEntryState.LOADED + + appliance.connected = False + await client.add_events( + [ + EventMessage( + appliance.ha_id, + EventType.PAIRED, + data=ArrayOfEvents([]), + ) + ] + ) + await hass.async_block_till_done() + + client.get_specific_appliance.assert_awaited_once_with(appliance.ha_id) + for method in INITIAL_FETCH_CLIENT_METHODS: + assert getattr(client, method).call_count == 0