From 290737cd45813c469ecfe39cceb772c02f3d80df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Tue, 8 Sep 2026 12:26:21 +0100 Subject: [PATCH] Bump whirlpool-sixth-sense to 2.0.3 (#180700) --- .../components/whirlpool/__init__.py | 5 +- .../components/whirlpool/config_flow.py | 4 +- .../components/whirlpool/diagnostics.py | 1 - homeassistant/components/whirlpool/entity.py | 2 +- .../components/whirlpool/manifest.json | 2 +- requirements_all.txt | 2 +- tests/components/whirlpool/conftest.py | 58 ++++++++++++++----- .../whirlpool/snapshots/test_diagnostics.ambr | 7 --- .../components/whirlpool/test_config_flow.py | 32 +++++++++- tests/components/whirlpool/test_init.py | 9 ++- 10 files changed, 85 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/whirlpool/__init__.py b/homeassistant/components/whirlpool/__init__.py index 2724a87d3079..6f09977bb39b 100644 --- a/homeassistant/components/whirlpool/__init__.py +++ b/homeassistant/components/whirlpool/__init__.py @@ -58,11 +58,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: WhirlpoolConfigEntry) -> ) appliances_manager = AppliancesManager(backend_selector, auth, session) - if not await appliances_manager.fetch_appliances(): + if not await appliances_manager.connect(): raise ConfigEntryNotReady( - translation_domain=DOMAIN, translation_key="appliances_fetch_failed" + translation_domain=DOMAIN, translation_key="cannot_connect" ) - await appliances_manager.connect() entry.runtime_data = appliances_manager diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index 0c0851bf020c..46f4801425ef 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -66,7 +66,9 @@ async def authenticate( if check_appliances_exist: appliances_manager = AppliancesManager(backend_selector, auth, session) - await appliances_manager.fetch_appliances() + if not await appliances_manager.connect(): + return "cannot_connect" + await appliances_manager.disconnect() if ( not appliances_manager.aircons diff --git a/homeassistant/components/whirlpool/diagnostics.py b/homeassistant/components/whirlpool/diagnostics.py index 9fde3a11a454..e3e3f52dd9ad 100644 --- a/homeassistant/components/whirlpool/diagnostics.py +++ b/homeassistant/components/whirlpool/diagnostics.py @@ -28,7 +28,6 @@ async def async_get_config_entry_diagnostics( def get_appliance_diagnostics(appliance: Appliance) -> dict[str, Any]: return { - "data_model": appliance.appliance_info.data_model, "category": appliance.appliance_info.category, "model_number": appliance.appliance_info.model_number, } diff --git a/homeassistant/components/whirlpool/entity.py b/homeassistant/components/whirlpool/entity.py index ec98253e9716..404243a9d756 100644 --- a/homeassistant/components/whirlpool/entity.py +++ b/homeassistant/components/whirlpool/entity.py @@ -48,7 +48,7 @@ class WhirlpoolEntity(Entity): @callback def _async_attr_callback(self) -> None: _LOGGER.debug("Attribute update for entity %s", self.entity_id) - self._attr_available = self._appliance.get_online() + self._attr_available = self._appliance.get_online() or False if not self._attr_available: if not self._unavailable_logged: diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index a6dcc14b9924..74c66e8d6877 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_push", "loggers": ["whirlpool"], "quality_scale": "silver", - "requirements": ["whirlpool-sixth-sense==1.3.1"] + "requirements": ["whirlpool-sixth-sense==2.0.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index ca0bd009072a..c775b0065179 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3446,7 +3446,7 @@ webmin-xmlrpc==0.0.2 weheat==2026.9.3 # homeassistant.components.whirlpool -whirlpool-sixth-sense==1.3.1 +whirlpool-sixth-sense==2.0.3 # homeassistant.components.whois whoisdomain==2.20260806.3 diff --git a/tests/components/whirlpool/conftest.py b/tests/components/whirlpool/conftest.py index a713247d4419..67904cbc7346 100644 --- a/tests/components/whirlpool/conftest.py +++ b/tests/components/whirlpool/conftest.py @@ -101,9 +101,7 @@ def get_aircon_mock(said): """Get a mock of an air conditioner.""" mock_aircon = Mock(spec=aircon.Aircon, said=said) mock_aircon.name = f"Aircon {said}" - mock_aircon.appliance_info = Mock( - data_model="aircon_model", category="aircon", model_number="12345" - ) + mock_aircon.appliance_info = Mock(category="aircon", model_number="12345") mock_aircon.get_online.return_value = True mock_aircon.get_power_on.return_value = True mock_aircon.get_mode.return_value = aircon.Mode.Cool @@ -113,6 +111,12 @@ def get_aircon_mock(said): mock_aircon.get_current_humidity.return_value = 80 mock_aircon.get_humidity.return_value = 50 mock_aircon.get_h_louver_swing.return_value = True + mock_aircon.get_raw_data.return_value = { + "_id": said, + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_aircon @@ -134,9 +138,7 @@ def mock_washer_api(): """Get a mock of a washer.""" mock_washer = Mock(spec=washer.Washer, said="said_washer") mock_washer.name = "Washer" - mock_washer.appliance_info = Mock( - data_model="washer", category="washer_dryer", model_number="12345" - ) + mock_washer.appliance_info = Mock(category="washer_dryer", model_number="12345") mock_washer.get_online.return_value = True mock_washer.get_machine_state.return_value = washer.MachineState.RunningMainCycle mock_washer.get_door_open.return_value = False @@ -148,6 +150,12 @@ def mock_washer_api(): mock_washer.get_cycle_status_soaking.return_value = False mock_washer.get_cycle_status_spinning.return_value = False mock_washer.get_cycle_status_washing.return_value = False + mock_washer.get_raw_data.return_value = { + "_id": "WBR123456789", + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_washer @@ -157,14 +165,18 @@ def mock_dryer_api(): """Get a mock of a dryer.""" mock_dryer = mock.Mock(spec=dryer.Dryer, said="said_dryer") mock_dryer.name = "Dryer" - mock_dryer.appliance_info = Mock( - data_model="dryer", category="washer_dryer", model_number="12345" - ) + mock_dryer.appliance_info = Mock(category="washer_dryer", model_number="12345") mock_dryer.get_online.return_value = True mock_dryer.get_machine_state.return_value = dryer.MachineState.RunningMainCycle mock_dryer.get_door_open.return_value = False mock_dryer.get_time_remaining.return_value = 3540 mock_dryer.get_cycle_status_sensing.return_value = False + mock_dryer.get_raw_data.return_value = { + "_id": "WBR123456789", + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_dryer @@ -173,9 +185,7 @@ def mock_oven_single_cavity_api(): """Get a mock of a single cavity oven.""" mock_oven = Mock(spec=oven.Oven, said="said_oven_single") mock_oven.name = "Single Cavity Oven" - mock_oven.appliance_info = Mock( - data_model="oven", category="oven", model_number="12345" - ) + mock_oven.appliance_info = Mock(category="oven", model_number="12345") mock_oven.get_cavity_state.return_value = oven.CavityState.Standby mock_oven.get_cook_mode.return_value = oven.CookMode.Bake mock_oven.get_online.return_value = True @@ -185,6 +195,12 @@ def mock_oven_single_cavity_api(): mock_oven.get_light.return_value = True mock_oven.get_temp.return_value = 180 mock_oven.get_target_temp.return_value = 200 + mock_oven.get_raw_data.return_value = { + "_id": "WBR123456789", + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_oven @@ -193,9 +209,7 @@ def mock_oven_dual_cavity_api(): """Get a mock of a dual cavity oven.""" mock_oven = Mock(spec=oven.Oven, said="said_oven_dual") mock_oven.name = "Dual Cavity Oven" - mock_oven.appliance_info = Mock( - data_model="oven", category="oven", model_number="12345" - ) + mock_oven.appliance_info = Mock(category="oven", model_number="12345") mock_oven.get_cavity_state.return_value = oven.CavityState.Standby mock_oven.get_cook_mode.return_value = oven.CookMode.Bake mock_oven.get_online.return_value = True @@ -209,6 +223,12 @@ def mock_oven_dual_cavity_api(): mock_oven.get_light.side_effect = lambda cavity: cavity == oven.Cavity.Upper mock_oven.get_temp.return_value = 180 mock_oven.get_target_temp.return_value = 200 + mock_oven.get_raw_data.return_value = { + "_id": "WBR123456789", + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_oven @@ -218,7 +238,13 @@ def mock_refrigerator_api(): mock_refrigerator = Mock(spec=refrigerator.Refrigerator, said="said_refrigerator") mock_refrigerator.name = "Beer fridge" mock_refrigerator.appliance_info = Mock( - data_model="refrigerator", category="refrigerator", model_number="12345" + category="refrigerator", model_number="12345" ) mock_refrigerator.get_offset_temp.return_value = 0 + mock_refrigerator.get_raw_data.return_value = { + "_id": "WBR123456789", + "attributes": { + "Cavity_OpSetHorzLouverSwing": {"value": "1", "updateTime": 1626517992782}, + }, + } return mock_refrigerator diff --git a/tests/components/whirlpool/snapshots/test_diagnostics.ambr b/tests/components/whirlpool/snapshots/test_diagnostics.ambr index eef6018e3a37..818e22468d28 100644 --- a/tests/components/whirlpool/snapshots/test_diagnostics.ambr +++ b/tests/components/whirlpool/snapshots/test_diagnostics.ambr @@ -5,45 +5,38 @@ 'aircons': dict({ 'Aircon said1': dict({ 'category': 'aircon', - 'data_model': 'aircon_model', 'model_number': '12345', }), 'Aircon said2': dict({ 'category': 'aircon', - 'data_model': 'aircon_model', 'model_number': '12345', }), }), 'dryers': dict({ 'Dryer': dict({ 'category': 'washer_dryer', - 'data_model': 'dryer', 'model_number': '12345', }), }), 'ovens': dict({ 'Dual Cavity Oven': dict({ 'category': 'oven', - 'data_model': 'oven', 'model_number': '12345', }), 'Single Cavity Oven': dict({ 'category': 'oven', - 'data_model': 'oven', 'model_number': '12345', }), }), 'refrigerators': dict({ 'Beer fridge': dict({ 'category': 'refrigerator', - 'data_model': 'refrigerator', 'model_number': '12345', }), }), 'washers': dict({ 'Washer': dict({ 'category': 'washer_dryer', - 'data_model': 'washer', 'model_number': '12345', }), }), diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index fa80978b5ec5..389dd60efda5 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -71,6 +71,7 @@ async def test_user_flow( hass: HomeAssistant, region: tuple[str, Region], brand: tuple[str, Brand], + mock_appliances_manager_api: MagicMock, mock_backend_selector_api: MagicMock, mock_whirlpool_setup_entry: MagicMock, ) -> None: @@ -87,6 +88,8 @@ async def test_user_flow( ) assert_successful_user_flow(mock_whirlpool_setup_entry, result, region[0], brand[0]) + mock_appliances_manager_api.return_value.connect.assert_awaited_once() + mock_appliances_manager_api.return_value.disconnect.assert_awaited_once() mock_backend_selector_api.assert_called_once_with(brand[1], region[1]) @@ -109,7 +112,6 @@ async def test_user_flow_invalid_auth( assert result["type"] is FlowResultType.FORM assert result["errors"] == {"base": "invalid_auth"} - # Test that it succeeds if the authentication is valid mock_auth_api.return_value.is_access_token_valid.return_value = True result = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG_INPUT | {CONF_REGION: region[0], CONF_BRAND: brand[0]} @@ -161,6 +163,34 @@ async def test_user_flow_auth_error( assert_successful_user_flow(mock_whirlpool_setup_entry, result, region[0], brand[0]) +async def test_user_flow_connect_error( + hass: HomeAssistant, + region: tuple[str, Region], + brand: tuple[str, Brand], + mock_appliances_manager_api: MagicMock, + mock_whirlpool_setup_entry: MagicMock, +) -> None: + """Test a failed connection in the flow initialized by the user.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_appliances_manager_api.return_value.connect.return_value = False + result = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG_INPUT | {CONF_REGION: region[0], CONF_BRAND: brand[0]} + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} + + # Test that it succeeds if the connection is successful + mock_appliances_manager_api.return_value.connect.return_value = True + result = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG_INPUT | {CONF_REGION: region[0], CONF_BRAND: brand[0]} + ) + + assert_successful_user_flow(mock_whirlpool_setup_entry, result, region[0], brand[0]) + + async def test_already_configured( hass: HomeAssistant, region: tuple[str, Region], brand: tuple[str, Brand] ) -> None: diff --git a/tests/components/whirlpool/test_init.py b/tests/components/whirlpool/test_init.py index f1400835700c..a5c25b7ea913 100644 --- a/tests/components/whirlpool/test_init.py +++ b/tests/components/whirlpool/test_init.py @@ -124,12 +124,11 @@ async def test_setup_auth_failed( assert entry.state is ConfigEntryState.SETUP_ERROR -async def test_setup_fetch_appliances_failed( - hass: HomeAssistant, - mock_appliances_manager_api: MagicMock, +async def test_setup_connect_failed( + hass: HomeAssistant, mock_appliances_manager_api: MagicMock ) -> None: - """Test setup with failed fetch_appliances.""" - mock_appliances_manager_api.return_value.fetch_appliances.return_value = False + """Test setup with failed connect call.""" + mock_appliances_manager_api.return_value.connect = AsyncMock(return_value=False) entry = await init_integration(hass) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state is ConfigEntryState.SETUP_RETRY