From e5a07da0c9c857c1df0566e9a829a111b7aa477b Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:06:25 +0200 Subject: [PATCH] Add checks for config entry state in async_config_entry_first_refresh (#128148) --- homeassistant/helpers/update_coordinator.py | 12 +++++ .../rainforest_raven/test_coordinator.py | 7 +++ tests/helpers/test_update_coordinator.py | 49 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 0066def922f9a..f5c2a2a1288ef 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -293,6 +293,18 @@ async def async_config_entry_first_refresh(self) -> None: error_if_core=True, error_if_integration=False, ) + elif ( + self.config_entry.state + is not config_entries.ConfigEntryState.SETUP_IN_PROGRESS + ): + report( + "uses `async_config_entry_first_refresh`, which is only supported " + f"when entry state is {config_entries.ConfigEntryState.SETUP_IN_PROGRESS}, " + f"but it is in state {self.config_entry.state}, " + "This will stop working in Home Assistant 2025.11", + error_if_core=True, + error_if_integration=False, + ) if await self.__wrap_async_setup(): await self._async_refresh( log_failures=False, raise_on_auth_failed=True, raise_on_entry_error=True diff --git a/tests/components/rainforest_raven/test_coordinator.py b/tests/components/rainforest_raven/test_coordinator.py index db70118f7b9e1..5c61c3d8ad454 100644 --- a/tests/components/rainforest_raven/test_coordinator.py +++ b/tests/components/rainforest_raven/test_coordinator.py @@ -8,6 +8,7 @@ import pytest from homeassistant.components.rainforest_raven.coordinator import RAVEnDataCoordinator +from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -18,6 +19,7 @@ async def test_coordinator_device_info(hass: HomeAssistant) -> None: """Test reporting device information from the coordinator.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) assert coordinator.device_fw_version is None @@ -44,6 +46,7 @@ async def test_coordinator_cache_device( ) -> None: """Test that the device isn't re-opened for subsequent refreshes.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() @@ -60,6 +63,7 @@ async def test_coordinator_device_error_setup( ) -> None: """Test handling of a device error during initialization.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) mock_device.get_network_info.side_effect = RAVEnConnectionError @@ -72,6 +76,7 @@ async def test_coordinator_device_error_update( ) -> None: """Test handling of a device error during an update.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() @@ -87,6 +92,7 @@ async def test_coordinator_device_timeout_update( ) -> None: """Test handling of a device timeout during an update.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() @@ -102,6 +108,7 @@ async def test_coordinator_comm_error( ) -> None: """Test handling of an error parsing or reading raw device data.""" entry = create_mock_entry() + entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) coordinator = RAVEnDataCoordinator(hass, entry) mock_device.synchronize.side_effect = RAVEnConnectionError diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 15043dc2c7615..844aa5053e980 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -551,6 +551,9 @@ async def test_async_config_entry_first_refresh_failure( a decreasing level of logging once the first message is logged. """ entry = MockConfigEntry() + entry._async_set_state( + hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None + ) crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry) setattr(crd, method, AsyncMock(side_effect=err_msg[0])) @@ -586,6 +589,9 @@ async def test_async_config_entry_first_refresh_failure_passed_through( a decreasing level of logging once the first message is logged. """ entry = MockConfigEntry() + entry._async_set_state( + hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None + ) crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry) setattr(crd, method, AsyncMock(side_effect=err_msg[0])) @@ -600,6 +606,9 @@ async def test_async_config_entry_first_refresh_failure_passed_through( async def test_async_config_entry_first_refresh_success(hass: HomeAssistant) -> None: """Test first refresh successfully.""" entry = MockConfigEntry() + entry._async_set_state( + hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None + ) crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry) crd.setup_method = AsyncMock() await crd.async_config_entry_first_refresh() @@ -608,6 +617,46 @@ async def test_async_config_entry_first_refresh_success(hass: HomeAssistant) -> crd.setup_method.assert_called_once() +async def test_async_config_entry_first_refresh_invalid_state( + hass: HomeAssistant, +) -> None: + """Test first refresh fails due to invalid state.""" + entry = MockConfigEntry() + crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry) + crd.setup_method = AsyncMock() + with pytest.raises( + RuntimeError, + match="Detected code that uses `async_config_entry_first_refresh`, which " + "is only supported when entry state is ConfigEntryState.SETUP_IN_PROGRESS, " + "but it is in state ConfigEntryState.NOT_LOADED. This will stop working " + "in Home Assistant 2025.11. Please report this issue.", + ): + await crd.async_config_entry_first_refresh() + + assert crd.last_update_success is True + crd.setup_method.assert_not_called() + + +@pytest.mark.usefixtures("mock_integration_frame") +async def test_async_config_entry_first_refresh_invalid_state_in_integration( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test first refresh successfully, despite wrong state.""" + entry = MockConfigEntry() + crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry) + crd.setup_method = AsyncMock() + + await crd.async_config_entry_first_refresh() + assert crd.last_update_success is True + crd.setup_method.assert_called() + assert ( + "Detected that integration 'hue' uses `async_config_entry_first_refresh`, which " + "is only supported when entry state is ConfigEntryState.SETUP_IN_PROGRESS, " + "but it is in state ConfigEntryState.NOT_LOADED, This will stop working " + "in Home Assistant 2025.11" + ) in caplog.text + + async def test_async_config_entry_first_refresh_no_entry(hass: HomeAssistant) -> None: """Test first refresh successfully.""" crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, None)