From be53ad544934192764de833f4230ebd8cbaa4fda Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 18 Jun 2025 07:29:04 +0200 Subject: [PATCH 01/10] Disable Z-Wave idle notification button (#147026) * Update test * Disable Z-Wave idle notification button * Update tests --- .../components/zwave_js/discovery.py | 1 + .../zwave_js/snapshots/test_diagnostics.ambr | 8 ++-- tests/components/zwave_js/test_button.py | 41 ++++++++++++++++--- tests/components/zwave_js/test_init.py | 12 +----- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 924778a9e5b2e4..4e9a3321beb6c5 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -1188,6 +1188,7 @@ class ZWaveDiscoverySchema: any_available_states={(0, "idle")}, ), allow_multi=True, + entity_registry_enabled_default=False, ), # event # stateful = False diff --git a/tests/components/zwave_js/snapshots/test_diagnostics.ambr b/tests/components/zwave_js/snapshots/test_diagnostics.ambr index dc0dbba59b5979..40ed3bbf8365d7 100644 --- a/tests/components/zwave_js/snapshots/test_diagnostics.ambr +++ b/tests/components/zwave_js/snapshots/test_diagnostics.ambr @@ -97,8 +97,8 @@ 'value_id': '52-113-0-Home Security-Cover status', }), dict({ - 'disabled': False, - 'disabled_by': None, + 'disabled': True, + 'disabled_by': 'integration', 'domain': 'button', 'entity_category': 'config', 'entity_id': 'button.multisensor_6_idle_home_security_cover_status', @@ -120,8 +120,8 @@ 'value_id': '52-113-0-Home Security-Cover status', }), dict({ - 'disabled': False, - 'disabled_by': None, + 'disabled': True, + 'disabled_by': 'integration', 'domain': 'button', 'entity_category': 'config', 'entity_id': 'button.multisensor_6_idle_home_security_motion_sensor_status', diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index 0282a268b54531..422888cab23437 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -1,13 +1,21 @@ """Test the Z-Wave JS button entities.""" +from datetime import timedelta +from unittest.mock import MagicMock + import pytest +from zwave_js_server.model.node import Node from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.zwave_js.const import DOMAIN, SERVICE_REFRESH_VALUE from homeassistant.components.zwave_js.helpers import get_valueless_base_unique_id -from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY +from homeassistant.const import ATTR_ENTITY_ID, EntityCategory, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt as dt_util + +from tests.common import MockConfigEntry, async_fire_time_changed @pytest.fixture @@ -71,11 +79,32 @@ async def test_ping_entity( async def test_notification_idle_button( - hass: HomeAssistant, client, multisensor_6, integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + client: MagicMock, + multisensor_6: Node, + integration: MockConfigEntry, ) -> None: """Test Notification idle button.""" node = multisensor_6 - state = hass.states.get("button.multisensor_6_idle_home_security_cover_status") + entity_id = "button.multisensor_6_idle_home_security_cover_status" + entity_entry = entity_registry.async_get(entity_id) + assert entity_entry + assert entity_entry.entity_category is EntityCategory.CONFIG + assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION + assert hass.states.get(entity_id) is None # disabled by default + + entity_registry.async_update_entity( + entity_id, + disabled_by=None, + ) + async_fire_time_changed( + hass, + dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) assert state assert state.state == "unknown" assert ( @@ -88,13 +117,13 @@ async def test_notification_idle_button( BUTTON_DOMAIN, SERVICE_PRESS, { - ATTR_ENTITY_ID: "button.multisensor_6_idle_home_security_cover_status", + ATTR_ENTITY_ID: entity_id, }, blocking=True, ) - assert len(client.async_send_command_no_wait.call_args_list) == 1 - args = client.async_send_command_no_wait.call_args_list[0][0][0] + assert client.async_send_command_no_wait.call_count == 1 + args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.manually_idle_notification_value" assert args["nodeId"] == node.node_id assert args["valueId"] == { diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index ef74373ad9e23c..fa82b051e59497 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1812,7 +1812,8 @@ async def test_disabled_node_status_entity_on_node_replaced( assert state.state == STATE_UNAVAILABLE -async def test_disabled_entity_on_value_removed( +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_remove_entity_on_value_removed( hass: HomeAssistant, zp3111: Node, client: MagicMock, @@ -1823,15 +1824,6 @@ async def test_disabled_entity_on_value_removed( "button.4_in_1_sensor_idle_home_security_cover_status" ) - # must reload the integration when enabling an entity - await hass.config_entries.async_unload(integration.entry_id) - await hass.async_block_till_done() - assert integration.state is ConfigEntryState.NOT_LOADED - integration.add_to_hass(hass) - await hass.config_entries.async_setup(integration.entry_id) - await hass.async_block_till_done() - assert integration.state is ConfigEntryState.LOADED - state = hass.states.get(idle_cover_status_button_entity) assert state assert state.state != STATE_UNAVAILABLE From b8cd3f3635e7d2f1e3ccd60a02364f8b23eab524 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 18 Jun 2025 09:11:01 +0200 Subject: [PATCH 02/10] Bump holidays lib to 0.75 (#147043) --- homeassistant/components/holiday/manifest.json | 2 +- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/holiday/manifest.json b/homeassistant/components/holiday/manifest.json index 5a5f1daf96792e..c76d6638730027 100644 --- a/homeassistant/components/holiday/manifest.json +++ b/homeassistant/components/holiday/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/holiday", "iot_class": "local_polling", - "requirements": ["holidays==0.74", "babel==2.15.0"] + "requirements": ["holidays==0.75", "babel==2.15.0"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 9091dd131dd084..f9fae38f1f5113 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_polling", "loggers": ["holidays"], "quality_scale": "internal", - "requirements": ["holidays==0.74"] + "requirements": ["holidays==0.75"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5a5547d58606f9..01b36bc3354c86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1161,7 +1161,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.74 +holidays==0.75 # homeassistant.components.frontend home-assistant-frontend==20250531.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 46974b17ba0d3d..09c4731dfa2031 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1007,7 +1007,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.74 +holidays==0.75 # homeassistant.components.frontend home-assistant-frontend==20250531.3 From 3449dae7a27715ceaff76e84edd29df05b9feebd Mon Sep 17 00:00:00 2001 From: msw Date: Wed, 18 Jun 2025 00:14:45 -0700 Subject: [PATCH 03/10] Capitalize "Ice Bites" and switch to "Cubed ice" (#147060) (#147061) --- .../components/smartthings/strings.json | 4 +- .../smartthings/snapshots/test_switch.ambr | 56 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index 038894a3d5bb80..5a1d111b617e0a 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -605,10 +605,10 @@ "name": "Wrinkle prevent" }, "ice_maker": { - "name": "Ice cubes" + "name": "Cubed ice" }, "ice_maker_2": { - "name": "Ice bites" + "name": "Ice Bites" }, "sabbath_mode": { "name": "Sabbath mode" diff --git a/tests/components/smartthings/snapshots/test_switch.ambr b/tests/components/smartthings/snapshots/test_switch.ambr index a182c3bf2a229e..d0ea3dbcdad826 100644 --- a/tests/components/smartthings/snapshots/test_switch.ambr +++ b/tests/components/smartthings/snapshots/test_switch.ambr @@ -47,7 +47,7 @@ 'state': 'on', }) # --- -# name: test_all_entities[da_ref_normal_000001][switch.refrigerator_ice_cubes-entry] +# name: test_all_entities[da_ref_normal_000001][switch.refrigerator_cubed_ice-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -60,7 +60,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': None, - 'entity_id': 'switch.refrigerator_ice_cubes', + 'entity_id': 'switch.refrigerator_cubed_ice', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -72,7 +72,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Ice cubes', + 'original_name': 'Cubed ice', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, @@ -82,13 +82,13 @@ 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ref_normal_000001][switch.refrigerator_ice_cubes-state] +# name: test_all_entities[da_ref_normal_000001][switch.refrigerator_cubed_ice-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Refrigerator Ice cubes', + 'friendly_name': 'Refrigerator Cubed ice', }), 'context': , - 'entity_id': 'switch.refrigerator_ice_cubes', + 'entity_id': 'switch.refrigerator_cubed_ice', 'last_changed': , 'last_reported': , 'last_updated': , @@ -239,7 +239,7 @@ 'state': 'off', }) # --- -# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_ice_cubes-entry] +# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_cubed_ice-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -252,7 +252,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': None, - 'entity_id': 'switch.refrigerator_ice_cubes', + 'entity_id': 'switch.refrigerator_cubed_ice', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -264,7 +264,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Ice cubes', + 'original_name': 'Cubed ice', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, @@ -274,13 +274,13 @@ 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_ice_cubes-state] +# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_cubed_ice-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Refrigerator Ice cubes', + 'friendly_name': 'Refrigerator Cubed ice', }), 'context': , - 'entity_id': 'switch.refrigerator_ice_cubes', + 'entity_id': 'switch.refrigerator_cubed_ice', 'last_changed': , 'last_reported': , 'last_updated': , @@ -383,7 +383,7 @@ 'state': 'off', }) # --- -# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_bites-entry] +# name: test_all_entities[da_ref_normal_01011][switch.frigo_cubed_ice-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -396,7 +396,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': None, - 'entity_id': 'switch.frigo_ice_bites', + 'entity_id': 'switch.frigo_cubed_ice', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -408,30 +408,30 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Ice bites', + 'original_name': 'Cubed ice', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'ice_maker_2', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_icemaker-02_switch_switch_switch', + 'translation_key': 'ice_maker', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_icemaker_switch_switch_switch', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_bites-state] +# name: test_all_entities[da_ref_normal_01011][switch.frigo_cubed_ice-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Frigo Ice bites', + 'friendly_name': 'Frigo Cubed ice', }), 'context': , - 'entity_id': 'switch.frigo_ice_bites', + 'entity_id': 'switch.frigo_cubed_ice', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'on', }) # --- -# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_cubes-entry] +# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_bites-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -444,7 +444,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': None, - 'entity_id': 'switch.frigo_ice_cubes', + 'entity_id': 'switch.frigo_ice_bites', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -456,23 +456,23 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Ice cubes', + 'original_name': 'Ice Bites', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'ice_maker', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_icemaker_switch_switch_switch', + 'translation_key': 'ice_maker_2', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_icemaker-02_switch_switch_switch', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_cubes-state] +# name: test_all_entities[da_ref_normal_01011][switch.frigo_ice_bites-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Frigo Ice cubes', + 'friendly_name': 'Frigo Ice Bites', }), 'context': , - 'entity_id': 'switch.frigo_ice_cubes', + 'entity_id': 'switch.frigo_ice_bites', 'last_changed': , 'last_reported': , 'last_updated': , From ba2aac46145fc01827c444a9a917b73d055a2461 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 18 Jun 2025 09:15:27 +0200 Subject: [PATCH 04/10] Bump aiowebdav2 to 0.4.6 (#147054) --- homeassistant/components/webdav/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/webdav/manifest.json b/homeassistant/components/webdav/manifest.json index 63d093745d1873..9e9e1c8866edb1 100644 --- a/homeassistant/components/webdav/manifest.json +++ b/homeassistant/components/webdav/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["aiowebdav2"], "quality_scale": "bronze", - "requirements": ["aiowebdav2==0.4.5"] + "requirements": ["aiowebdav2==0.4.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 01b36bc3354c86..925a6172cf8b6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -429,7 +429,7 @@ aiowaqi==3.1.0 aiowatttime==0.1.1 # homeassistant.components.webdav -aiowebdav2==0.4.5 +aiowebdav2==0.4.6 # homeassistant.components.webostv aiowebostv==0.7.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 09c4731dfa2031..e2007ddb1ad797 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -411,7 +411,7 @@ aiowaqi==3.1.0 aiowatttime==0.1.1 # homeassistant.components.webdav -aiowebdav2==0.4.5 +aiowebdav2==0.4.6 # homeassistant.components.webostv aiowebostv==0.7.3 From 07110e288dfd44e9fceb795f283e5ab8b8d32438 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 18 Jun 2025 09:16:08 +0200 Subject: [PATCH 05/10] If no Reolink HTTP api available, do not set configuration_url (#146684) * If no http api available, do not set configuration_url * Add tests --- homeassistant/components/reolink/entity.py | 12 ++++++++++-- tests/components/reolink/test_init.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index 2e0f1ac9e6a4e9..467472fef9c900 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -75,7 +75,10 @@ def __init__( ) http_s = "https" if self._host.api.use_https else "http" - self._conf_url = f"{http_s}://{self._host.api.host}:{self._host.api.port}" + if self._host.api.baichuan_only: + self._conf_url = None + else: + self._conf_url = f"{http_s}://{self._host.api.host}:{self._host.api.port}" self._dev_id = self._host.unique_id self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self._dev_id)}, @@ -184,6 +187,11 @@ def __init__( if mac := self._host.api.baichuan.mac_address(dev_ch): connections.add((CONNECTION_NETWORK_MAC, mac)) + if self._conf_url is None: + conf_url = None + else: + conf_url = f"{self._conf_url}/?ch={dev_ch}" + self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self._dev_id)}, connections=connections, @@ -195,7 +203,7 @@ def __init__( hw_version=self._host.api.camera_hardware_version(dev_ch), sw_version=self._host.api.camera_sw_version(dev_ch), serial_number=self._host.api.camera_uid(dev_ch), - configuration_url=f"{self._conf_url}/?ch={dev_ch}", + configuration_url=conf_url, ) @property diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 86c4ed861a119c..482928560b93be 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -1186,6 +1186,19 @@ def register_callback( assert hass.states.get(entity_id).state == STATE_OFF +async def test_baichaun_only( + hass: HomeAssistant, + reolink_connect: MagicMock, + config_entry: MockConfigEntry, +) -> None: + """Test initializing a baichuan only device.""" + reolink_connect.baichuan_only = True + + with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SWITCH]): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + async def test_remove( hass: HomeAssistant, reolink_connect: MagicMock, From 43d8a151ab2a9e981da48396dd76e4665025d89e Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Wed, 18 Jun 2025 03:21:21 -0400 Subject: [PATCH 06/10] Remove internals from Sonos test_init.py (#147063) * fix: test init * fix: revert * fix: revert * fix: revert * fix: revert * fix: simplify --- tests/components/sonos/conftest.py | 11 ++- tests/components/sonos/test_init.py | 115 ++++++++++++++-------------- 2 files changed, 66 insertions(+), 60 deletions(-) diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 4994d36f1bf59b..d121d5a4a1240b 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -85,6 +85,15 @@ def __init__(self, service_type, ip_address="192.168.42.2") -> None: self.subscribe = AsyncMock(return_value=SonosMockSubscribe(ip_address)) +class SonosMockRenderingService(SonosMockService): + """Mock rendering service.""" + + def __init__(self, return_value: dict[str, str], ip_address="192.168.42.2") -> None: + """Initialize the instance.""" + super().__init__("RenderingControl", ip_address) + self.GetVolume = Mock(return_value=30) + + class SonosMockAlarmClock(SonosMockService): """Mock a Sonos AlarmClock Service used in callbacks.""" @@ -239,7 +248,7 @@ def cache_mock( mock_soco.avTransport.GetPositionInfo = Mock( return_value=self.current_track_info ) - mock_soco.renderingControl = SonosMockService("RenderingControl", ip_address) + mock_soco.renderingControl = SonosMockRenderingService(ip_address) mock_soco.zoneGroupTopology = SonosMockService("ZoneGroupTopology", ip_address) mock_soco.contentDirectory = SonosMockService("ContentDirectory", ip_address) mock_soco.deviceProperties = SonosMockService("DeviceProperties", ip_address) diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index c6be606eb201bd..1bc8baff752a92 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -3,15 +3,15 @@ import asyncio from datetime import timedelta import logging -from unittest.mock import Mock, patch +from unittest.mock import Mock, PropertyMock, patch +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant import config_entries from homeassistant.components import sonos -from homeassistant.components.sonos import SonosDiscoveryManager from homeassistant.components.sonos.const import ( - DATA_SONOS_DISCOVERY_MANAGER, + DISCOVERY_INTERVAL, SONOS_SPEAKER_ACTIVITY, ) from homeassistant.components.sonos.exception import SonosUpdateError @@ -87,76 +87,73 @@ async def test_not_configuring_sonos_not_creates_entry(hass: HomeAssistant) -> N async def test_async_poll_manual_hosts_warnings( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + soco_factory: SoCoMockFactory, + freezer: FrozenDateTimeFactory, ) -> None: """Test that host warnings are not logged repeatedly.""" - await async_setup_component( - hass, - sonos.DOMAIN, - {"sonos": {"media_player": {"interface_addr": "127.0.0.1"}}}, - ) - await hass.async_block_till_done() - manager: SonosDiscoveryManager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] - manager.hosts.add("10.10.10.10") + + soco = soco_factory.cache_mock(MockSoCo(), "10.10.10.1", "Bedroom") with ( caplog.at_level(logging.DEBUG), - patch.object(manager, "_async_handle_discovery_message"), - patch( - "homeassistant.components.sonos.async_call_later" - ) as mock_async_call_later, - patch("homeassistant.components.sonos.async_dispatcher_send"), - patch( - "homeassistant.components.sonos.sync_get_visible_zones", - side_effect=[ - OSError(), - OSError(), - [], - [], - OSError(), - ], - ), + patch.object( + type(soco), "visible_zones", new_callable=PropertyMock + ) as mock_visible_zones, ): # First call fails, it should be logged as a WARNING message + mock_visible_zones.side_effect = OSError() caplog.clear() - await manager.async_poll_manual_hosts() - assert len(caplog.messages) == 1 - record = caplog.records[0] - assert record.levelname == "WARNING" - assert "Could not get visible Sonos devices from" in record.message - assert mock_async_call_later.call_count == 1 + await _setup_hass(hass) + assert [ + rec.levelname + for rec in caplog.records + if "Could not get visible Sonos devices from" in rec.message + ] == ["WARNING"] # Second call fails again, it should be logged as a DEBUG message + mock_visible_zones.side_effect = OSError() caplog.clear() - await manager.async_poll_manual_hosts() - assert len(caplog.messages) == 1 - record = caplog.records[0] - assert record.levelname == "DEBUG" - assert "Could not get visible Sonos devices from" in record.message - assert mock_async_call_later.call_count == 2 - - # Third call succeeds, it should log an info message + freezer.tick(DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + assert [ + rec.levelname + for rec in caplog.records + if "Could not get visible Sonos devices from" in rec.message + ] == ["DEBUG"] + + # Third call succeeds, logs message indicating reconnect + mock_visible_zones.return_value = {soco} + mock_visible_zones.side_effect = None caplog.clear() - await manager.async_poll_manual_hosts() - assert len(caplog.messages) == 1 - record = caplog.records[0] - assert record.levelname == "WARNING" - assert "Connection reestablished to Sonos device" in record.message - assert mock_async_call_later.call_count == 3 - - # Fourth call succeeds again, no need to log + freezer.tick(DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + assert [ + rec.levelname + for rec in caplog.records + if "Connection reestablished to Sonos device" in rec.message + ] == ["WARNING"] + + # Fourth call succeeds, it should log nothing caplog.clear() - await manager.async_poll_manual_hosts() - assert len(caplog.messages) == 0 - assert mock_async_call_later.call_count == 4 + freezer.tick(DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + assert "Connection reestablished to Sonos device" not in caplog.text - # Fifth call fail again again, should be logged as a WARNING message + # Fifth call fails again again, should be logged as a WARNING message + mock_visible_zones.side_effect = OSError() caplog.clear() - await manager.async_poll_manual_hosts() - assert len(caplog.messages) == 1 - record = caplog.records[0] - assert record.levelname == "WARNING" - assert "Could not get visible Sonos devices from" in record.message - assert mock_async_call_later.call_count == 5 + freezer.tick(DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert [ + rec.levelname + for rec in caplog.records + if "Could not get visible Sonos devices from" in rec.message + ] == ["WARNING"] class _MockSoCoOsError(MockSoCo): From 3fad76dfa120685be2294258030733d6a212ce07 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Wed, 18 Jun 2025 09:22:37 +0200 Subject: [PATCH 07/10] Use missed typed ConfigEntry in devolo Home Control (#147049) --- homeassistant/components/devolo_home_control/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/devolo_home_control/__init__.py b/homeassistant/components/devolo_home_control/__init__.py index b8dc948913f785..331bb5df94ab0d 100644 --- a/homeassistant/components/devolo_home_control/__init__.py +++ b/homeassistant/components/devolo_home_control/__init__.py @@ -91,7 +91,9 @@ async def async_unload_entry( async def async_remove_config_entry_device( - hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry + hass: HomeAssistant, + config_entry: DevoloHomeControlConfigEntry, + device_entry: DeviceEntry, ) -> bool: """Remove a config entry from a device.""" return True From 75d6b885cfa8483c337433c4b81613816c399983 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Wed, 18 Jun 2025 09:23:37 +0200 Subject: [PATCH 08/10] Fix typo in state name references of `homee` (#146905) Fix typo in state references Replace wrong semicolons with colon. --- homeassistant/components/homee/strings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homee/strings.json b/homeassistant/components/homee/strings.json index b5849f8b1a603d..8b10b3ebb8ae0f 100644 --- a/homeassistant/components/homee/strings.json +++ b/homeassistant/components/homee/strings.json @@ -177,9 +177,9 @@ "state_attributes": { "event_type": { "state": { - "upper": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::upper%]", - "lower": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::lower%]", - "released": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::released%]" + "upper": "[%key:component::homee::entity::event::button_state::state_attributes::event_type::state::upper%]", + "lower": "[%key:component::homee::entity::event::button_state::state_attributes::event_type::state::lower%]", + "released": "[%key:component::homee::entity::event::button_state::state_attributes::event_type::state::released%]" } } } @@ -189,7 +189,7 @@ "state_attributes": { "event_type": { "state": { - "release": "[%key;component::homee::entity::event::button_state::state_attributes::event_type::state::released%]", + "release": "[%key:component::homee::entity::event::button_state::state_attributes::event_type::state::released%]", "up": "Up", "down": "Down", "stop": "Stop", From 596951ea9fbb9dd80759a6d55898d0aec6728080 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Wed, 18 Jun 2025 09:24:09 +0200 Subject: [PATCH 09/10] Cleanup devolo Home Control tests (#147051) --- .../devolo_home_control/test_binary_sensor.py | 5 --- .../devolo_home_control/test_config_flow.py | 38 ------------------- .../devolo_home_control/test_diagnostics.py | 2 - .../devolo_home_control/test_init.py | 2 - .../devolo_home_control/test_siren.py | 5 --- 5 files changed, 52 deletions(-) diff --git a/tests/components/devolo_home_control/test_binary_sensor.py b/tests/components/devolo_home_control/test_binary_sensor.py index fd28ce2fdf640b..b2a58ef5038842 100644 --- a/tests/components/devolo_home_control/test_binary_sensor.py +++ b/tests/components/devolo_home_control/test_binary_sensor.py @@ -2,7 +2,6 @@ from unittest.mock import patch -import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -19,7 +18,6 @@ ) -@pytest.mark.usefixtures("mock_zeroconf") async def test_binary_sensor( hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion ) -> None: @@ -58,7 +56,6 @@ async def test_binary_sensor( ) -@pytest.mark.usefixtures("mock_zeroconf") async def test_remote_control( hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion ) -> None: @@ -99,7 +96,6 @@ async def test_remote_control( ) -@pytest.mark.usefixtures("mock_zeroconf") async def test_disabled(hass: HomeAssistant) -> None: """Test setup of a disabled device.""" entry = configure_integration(hass) @@ -113,7 +109,6 @@ async def test_disabled(hass: HomeAssistant) -> None: assert hass.states.get(f"{BINARY_SENSOR_DOMAIN}.test_door") is None -@pytest.mark.usefixtures("mock_zeroconf") async def test_remove_from_hass(hass: HomeAssistant) -> None: """Test removing entity.""" entry = configure_integration(hass) diff --git a/tests/components/devolo_home_control/test_config_flow.py b/tests/components/devolo_home_control/test_config_flow.py index aab3e69b38fad9..9367d746d2ef9c 100644 --- a/tests/components/devolo_home_control/test_config_flow.py +++ b/tests/components/devolo_home_control/test_config_flow.py @@ -66,44 +66,6 @@ async def test_form_already_configured(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" -async def test_form_advanced_options(hass: HomeAssistant) -> None: - """Test if we get the advanced options if user has enabled it.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER, "show_advanced_options": True}, - ) - assert result["type"] is FlowResultType.FORM - assert result["errors"] == {} - - with ( - patch( - "homeassistant.components.devolo_home_control.async_setup_entry", - return_value=True, - ) as mock_setup_entry, - patch( - "homeassistant.components.devolo_home_control.Mydevolo.uuid", - return_value="123456", - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "username": "test-username", - "password": "test-password", - }, - ) - await hass.async_block_till_done() - - assert result2["type"] is FlowResultType.CREATE_ENTRY - assert result2["title"] == "devolo Home Control" - assert result2["data"] == { - "username": "test-username", - "password": "test-password", - } - - assert len(mock_setup_entry.mock_calls) == 1 - - async def test_form_zeroconf(hass: HomeAssistant) -> None: """Test that the zeroconf confirmation form is served.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/devolo_home_control/test_diagnostics.py b/tests/components/devolo_home_control/test_diagnostics.py index dfadc4d1c4b1f5..558ed6394fae97 100644 --- a/tests/components/devolo_home_control/test_diagnostics.py +++ b/tests/components/devolo_home_control/test_diagnostics.py @@ -1,7 +1,5 @@ """Tests for the devolo Home Control diagnostics.""" -from __future__ import annotations - from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_control/test_init.py b/tests/components/devolo_home_control/test_init.py index da007303688af2..fb97447264d165 100644 --- a/tests/components/devolo_home_control/test_init.py +++ b/tests/components/devolo_home_control/test_init.py @@ -19,7 +19,6 @@ from tests.typing import WebSocketGenerator -@pytest.mark.usefixtures("mock_zeroconf") async def test_setup_entry(hass: HomeAssistant) -> None: """Test setup entry.""" entry = configure_integration(hass) @@ -44,7 +43,6 @@ async def test_setup_entry_maintenance(hass: HomeAssistant) -> None: assert entry.state is ConfigEntryState.SETUP_RETRY -@pytest.mark.usefixtures("mock_zeroconf") async def test_setup_gateway_offline(hass: HomeAssistant) -> None: """Test setup entry fails on gateway offline.""" entry = configure_integration(hass) diff --git a/tests/components/devolo_home_control/test_siren.py b/tests/components/devolo_home_control/test_siren.py index 71f4dfdd34decd..7c943e05cef5d6 100644 --- a/tests/components/devolo_home_control/test_siren.py +++ b/tests/components/devolo_home_control/test_siren.py @@ -2,7 +2,6 @@ from unittest.mock import patch -import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN @@ -14,7 +13,6 @@ from .mocks import HomeControlMock, HomeControlMockSiren -@pytest.mark.usefixtures("mock_zeroconf") async def test_siren( hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion ) -> None: @@ -45,7 +43,6 @@ async def test_siren( assert hass.states.get(f"{SIREN_DOMAIN}.test").state == STATE_UNAVAILABLE -@pytest.mark.usefixtures("mock_zeroconf") async def test_siren_switching( hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion ) -> None: @@ -98,7 +95,6 @@ async def test_siren_switching( property_set.assert_called_once_with(0) -@pytest.mark.usefixtures("mock_zeroconf") async def test_siren_change_default_tone( hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion ) -> None: @@ -130,7 +126,6 @@ async def test_siren_change_default_tone( property_set.assert_called_once_with(2) -@pytest.mark.usefixtures("mock_zeroconf") async def test_remove_from_hass(hass: HomeAssistant) -> None: """Test removing entity.""" entry = configure_integration(hass) From fec65f40fc2edcb0f5eb649cdfdc365960f9639f Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 18 Jun 2025 11:20:51 +0300 Subject: [PATCH 10/10] Bump aioamazondevices to 3.1.12 (#147055) * Bump aioamazondevices to 3.1.10 * bump to 3.1.12 --- homeassistant/components/alexa_devices/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa_devices/manifest.json b/homeassistant/components/alexa_devices/manifest.json index 7a7713f861b923..aeecb5bc96c75a 100644 --- a/homeassistant/components/alexa_devices/manifest.json +++ b/homeassistant/components/alexa_devices/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["aioamazondevices"], "quality_scale": "bronze", - "requirements": ["aioamazondevices==3.1.4"] + "requirements": ["aioamazondevices==3.1.12"] } diff --git a/requirements_all.txt b/requirements_all.txt index 925a6172cf8b6f..10386058750064 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -182,7 +182,7 @@ aioairzone-cloud==0.6.12 aioairzone==1.0.0 # homeassistant.components.alexa_devices -aioamazondevices==3.1.4 +aioamazondevices==3.1.12 # homeassistant.components.ambient_network # homeassistant.components.ambient_station diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e2007ddb1ad797..d7efe2904ddd81 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -170,7 +170,7 @@ aioairzone-cloud==0.6.12 aioairzone==1.0.0 # homeassistant.components.alexa_devices -aioamazondevices==3.1.4 +aioamazondevices==3.1.12 # homeassistant.components.ambient_network # homeassistant.components.ambient_station