Skip to content

Commit e5a07da

Browse files
authored
Add checks for config entry state in async_config_entry_first_refresh (#128148)
1 parent 1ad3a96 commit e5a07da

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

homeassistant/helpers/update_coordinator.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,18 @@ async def async_config_entry_first_refresh(self) -> None:
293293
error_if_core=True,
294294
error_if_integration=False,
295295
)
296+
elif (
297+
self.config_entry.state
298+
is not config_entries.ConfigEntryState.SETUP_IN_PROGRESS
299+
):
300+
report(
301+
"uses `async_config_entry_first_refresh`, which is only supported "
302+
f"when entry state is {config_entries.ConfigEntryState.SETUP_IN_PROGRESS}, "
303+
f"but it is in state {self.config_entry.state}, "
304+
"This will stop working in Home Assistant 2025.11",
305+
error_if_core=True,
306+
error_if_integration=False,
307+
)
296308
if await self.__wrap_async_setup():
297309
await self._async_refresh(
298310
log_failures=False, raise_on_auth_failed=True, raise_on_entry_error=True

tests/components/rainforest_raven/test_coordinator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99

1010
from homeassistant.components.rainforest_raven.coordinator import RAVEnDataCoordinator
11+
from homeassistant.config_entries import ConfigEntryState
1112
from homeassistant.core import HomeAssistant
1213
from homeassistant.exceptions import ConfigEntryNotReady
1314

@@ -18,6 +19,7 @@
1819
async def test_coordinator_device_info(hass: HomeAssistant) -> None:
1920
"""Test reporting device information from the coordinator."""
2021
entry = create_mock_entry()
22+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
2123
coordinator = RAVEnDataCoordinator(hass, entry)
2224

2325
assert coordinator.device_fw_version is None
@@ -44,6 +46,7 @@ async def test_coordinator_cache_device(
4446
) -> None:
4547
"""Test that the device isn't re-opened for subsequent refreshes."""
4648
entry = create_mock_entry()
49+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
4750
coordinator = RAVEnDataCoordinator(hass, entry)
4851

4952
await coordinator.async_config_entry_first_refresh()
@@ -60,6 +63,7 @@ async def test_coordinator_device_error_setup(
6063
) -> None:
6164
"""Test handling of a device error during initialization."""
6265
entry = create_mock_entry()
66+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
6367
coordinator = RAVEnDataCoordinator(hass, entry)
6468

6569
mock_device.get_network_info.side_effect = RAVEnConnectionError
@@ -72,6 +76,7 @@ async def test_coordinator_device_error_update(
7276
) -> None:
7377
"""Test handling of a device error during an update."""
7478
entry = create_mock_entry()
79+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
7580
coordinator = RAVEnDataCoordinator(hass, entry)
7681

7782
await coordinator.async_config_entry_first_refresh()
@@ -87,6 +92,7 @@ async def test_coordinator_device_timeout_update(
8792
) -> None:
8893
"""Test handling of a device timeout during an update."""
8994
entry = create_mock_entry()
95+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
9096
coordinator = RAVEnDataCoordinator(hass, entry)
9197

9298
await coordinator.async_config_entry_first_refresh()
@@ -102,6 +108,7 @@ async def test_coordinator_comm_error(
102108
) -> None:
103109
"""Test handling of an error parsing or reading raw device data."""
104110
entry = create_mock_entry()
111+
entry._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None)
105112
coordinator = RAVEnDataCoordinator(hass, entry)
106113

107114
mock_device.synchronize.side_effect = RAVEnConnectionError

tests/helpers/test_update_coordinator.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,9 @@ async def test_async_config_entry_first_refresh_failure(
551551
a decreasing level of logging once the first message is logged.
552552
"""
553553
entry = MockConfigEntry()
554+
entry._async_set_state(
555+
hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None
556+
)
554557
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry)
555558
setattr(crd, method, AsyncMock(side_effect=err_msg[0]))
556559

@@ -586,6 +589,9 @@ async def test_async_config_entry_first_refresh_failure_passed_through(
586589
a decreasing level of logging once the first message is logged.
587590
"""
588591
entry = MockConfigEntry()
592+
entry._async_set_state(
593+
hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None
594+
)
589595
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry)
590596
setattr(crd, method, AsyncMock(side_effect=err_msg[0]))
591597

@@ -600,6 +606,9 @@ async def test_async_config_entry_first_refresh_failure_passed_through(
600606
async def test_async_config_entry_first_refresh_success(hass: HomeAssistant) -> None:
601607
"""Test first refresh successfully."""
602608
entry = MockConfigEntry()
609+
entry._async_set_state(
610+
hass, config_entries.ConfigEntryState.SETUP_IN_PROGRESS, None
611+
)
603612
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry)
604613
crd.setup_method = AsyncMock()
605614
await crd.async_config_entry_first_refresh()
@@ -608,6 +617,46 @@ async def test_async_config_entry_first_refresh_success(hass: HomeAssistant) ->
608617
crd.setup_method.assert_called_once()
609618

610619

620+
async def test_async_config_entry_first_refresh_invalid_state(
621+
hass: HomeAssistant,
622+
) -> None:
623+
"""Test first refresh fails due to invalid state."""
624+
entry = MockConfigEntry()
625+
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry)
626+
crd.setup_method = AsyncMock()
627+
with pytest.raises(
628+
RuntimeError,
629+
match="Detected code that uses `async_config_entry_first_refresh`, which "
630+
"is only supported when entry state is ConfigEntryState.SETUP_IN_PROGRESS, "
631+
"but it is in state ConfigEntryState.NOT_LOADED. This will stop working "
632+
"in Home Assistant 2025.11. Please report this issue.",
633+
):
634+
await crd.async_config_entry_first_refresh()
635+
636+
assert crd.last_update_success is True
637+
crd.setup_method.assert_not_called()
638+
639+
640+
@pytest.mark.usefixtures("mock_integration_frame")
641+
async def test_async_config_entry_first_refresh_invalid_state_in_integration(
642+
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
643+
) -> None:
644+
"""Test first refresh successfully, despite wrong state."""
645+
entry = MockConfigEntry()
646+
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, entry)
647+
crd.setup_method = AsyncMock()
648+
649+
await crd.async_config_entry_first_refresh()
650+
assert crd.last_update_success is True
651+
crd.setup_method.assert_called()
652+
assert (
653+
"Detected that integration 'hue' uses `async_config_entry_first_refresh`, which "
654+
"is only supported when entry state is ConfigEntryState.SETUP_IN_PROGRESS, "
655+
"but it is in state ConfigEntryState.NOT_LOADED, This will stop working "
656+
"in Home Assistant 2025.11"
657+
) in caplog.text
658+
659+
611660
async def test_async_config_entry_first_refresh_no_entry(hass: HomeAssistant) -> None:
612661
"""Test first refresh successfully."""
613662
crd = get_crd(hass, DEFAULT_UPDATE_INTERVAL, None)

0 commit comments

Comments
 (0)