diff --git a/homeassistant/components/bmw_connected_drive/coordinator.py b/homeassistant/components/bmw_connected_drive/coordinator.py index 4a586aab373..2634c6069c9 100644 --- a/homeassistant/components/bmw_connected_drive/coordinator.py +++ b/homeassistant/components/bmw_connected_drive/coordinator.py @@ -58,7 +58,10 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]): try: await self.account.get_vehicles() except MyBMWAuthError as err: - # Clear refresh token and trigger reauth + # Allow one retry interval before raising AuthFailed to avoid flaky API issues + if self.last_update_success: + raise UpdateFailed(err) from err + # Clear refresh token and trigger reauth if previous update failed as well self._update_config_entry_refresh_token(None) raise ConfigEntryAuthFailed(err) from err except (MyBMWAPIError, RequestError) as err: diff --git a/tests/components/bmw_connected_drive/test_coordinator.py b/tests/components/bmw_connected_drive/test_coordinator.py new file mode 100644 index 00000000000..ab2d08376dd --- /dev/null +++ b/tests/components/bmw_connected_drive/test_coordinator.py @@ -0,0 +1,92 @@ +"""Test BMW coordinator.""" +from datetime import timedelta +from unittest.mock import patch + +from bimmer_connected.models import MyBMWAPIError, MyBMWAuthError +from freezegun.api import FrozenDateTimeFactory +import respx + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import UpdateFailed + +from . import FIXTURE_CONFIG_ENTRY + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_update_success(hass: HomeAssistant, bmw_fixture: respx.Router) -> None: + """Test the reauth form.""" + config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert ( + hass.data[config_entry.domain][config_entry.entry_id].last_update_success + is True + ) + + +async def test_update_failed( + hass: HomeAssistant, bmw_fixture: respx.Router, freezer: FrozenDateTimeFactory +) -> None: + """Test the reauth form.""" + config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + coordinator = hass.data[config_entry.domain][config_entry.entry_id] + + assert coordinator.last_update_success is True + + freezer.tick(timedelta(minutes=5, seconds=1)) + async_fire_time_changed(hass) + with patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + side_effect=MyBMWAPIError("Test error"), + ): + await hass.async_block_till_done() + + assert coordinator.last_update_success is False + assert isinstance(coordinator.last_exception, UpdateFailed) is True + + +async def test_update_reauth( + hass: HomeAssistant, bmw_fixture: respx.Router, freezer: FrozenDateTimeFactory +) -> None: + """Test the reauth form.""" + config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) + config_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + coordinator = hass.data[config_entry.domain][config_entry.entry_id] + + assert coordinator.last_update_success is True + + freezer.tick(timedelta(minutes=5, seconds=1)) + async_fire_time_changed(hass) + with patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + side_effect=MyBMWAuthError("Test error"), + ): + await hass.async_block_till_done() + + assert coordinator.last_update_success is False + assert isinstance(coordinator.last_exception, UpdateFailed) is True + + freezer.tick(timedelta(minutes=5, seconds=1)) + async_fire_time_changed(hass) + with patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + side_effect=MyBMWAuthError("Test error"), + ): + await hass.async_block_till_done() + + assert coordinator.last_update_success is False + assert isinstance(coordinator.last_exception, ConfigEntryAuthFailed) is True