diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 9e0783a99b1..d4140759a7b 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -72,6 +72,7 @@ class ProtectData: self._pending_camera_ids: set[str] = set() self._unsub_interval: CALLBACK_TYPE | None = None self._unsub_websocket: CALLBACK_TYPE | None = None + self._auth_failures = 0 self.last_update_success = False self.api = protect @@ -117,9 +118,13 @@ class ProtectData: try: updates = await self.api.update(force=force) except NotAuthorized: - await self.async_stop() - _LOGGER.exception("Reauthentication required") - self._entry.async_start_reauth(self._hass) + if self._auth_failures < 10: + _LOGGER.exception("Auth error while updating") + self._auth_failures += 1 + else: + await self.async_stop() + _LOGGER.exception("Reauthentication required") + self._entry.async_start_reauth(self._hass) self.last_update_success = False except ClientError: if self.last_update_success: @@ -129,6 +134,7 @@ class ProtectData: self._async_process_updates(self.api.bootstrap) else: self.last_update_success = True + self._auth_failures = 0 self._async_process_updates(updates) @callback diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index f6f0645df18..9392caa30ac 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -9,14 +9,18 @@ import aiohttp from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect.data import NVR, Bootstrap, Light -from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN +from homeassistant.components.unifiprotect.const import ( + CONF_DISABLE_RTSP, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from . import _patch_discovery -from .utils import MockUFPFixture, init_entry +from .utils import MockUFPFixture, init_entry, time_changed from tests.common import MockConfigEntry @@ -145,12 +149,23 @@ async def test_setup_failed_update(hass: HomeAssistant, ufp: MockUFPFixture): async def test_setup_failed_update_reauth(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with update that gives unauthroized error.""" - ufp.api.update = AsyncMock(side_effect=NotAuthorized) - await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert ufp.entry.state == ConfigEntryState.SETUP_RETRY - assert ufp.api.update.called + assert ufp.entry.state == ConfigEntryState.LOADED + + # reauth should not be triggered until there are 10 auth failures in a row + # to verify it is not transient + ufp.api.update = AsyncMock(side_effect=NotAuthorized) + for _ in range(10): + await time_changed(hass, DEFAULT_SCAN_INTERVAL) + assert len(hass.config_entries.flow._progress) == 0 + + assert ufp.api.update.call_count == 10 + assert ufp.entry.state == ConfigEntryState.LOADED + + await time_changed(hass, DEFAULT_SCAN_INTERVAL) + assert ufp.api.update.call_count == 11 + assert len(hass.config_entries.flow._progress) == 1 async def test_setup_failed_error(hass: HomeAssistant, ufp: MockUFPFixture):