diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 9753687ecb3c..c35445057b31 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -12,6 +12,7 @@ from PyViCare.PyViCareOAuthManager import obtain_token_via_basic_auth_pkce from PyViCare.PyViCareUtils import ( PyViCareInvalidConfigurationError, PyViCareInvalidCredentialsError, + PyViCareRateLimitError, ) from homeassistant.components.application_credentials import ( @@ -160,6 +161,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ViCareConfigEntry) -> bo PyViCareInvalidCredentialsError, ) as err: raise ConfigEntryAuthFailed("Authentication failed") from err + except PyViCareRateLimitError as err: + # The quota recovers on its own. + raise ConfigEntryNotReady( + f"ViCare API rate limit exceeded, resets at {err.limitResetDate}" + ) from err # Group devices by gateway: in viaGateway mode one bulk fetch refreshes # every device behind a gateway, so one coordinator serves the gateway. diff --git a/tests/components/vicare/test_init.py b/tests/components/vicare/test_init.py index ad28adaaee6a..28899ea0dc87 100644 --- a/tests/components/vicare/test_init.py +++ b/tests/components/vicare/test_init.py @@ -12,6 +12,7 @@ from PyViCare.PyViCareUtils import ( PyViCareInvalidCredentialsError, PyViCareInvalidDataError, PyViCareNotSupportedFeatureError, + PyViCareRateLimitError, ) from homeassistant.components.vicare.const import DEFAULT_CACHE_DURATION, DOMAIN @@ -332,6 +333,42 @@ async def test_setup_entry_invalid_credentials( assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR +async def test_setup_entry_rate_limited( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test setup retries when the daily API quota is spent.""" + mock_config_entry.add_to_hass(hass) + + rate_limit_error = PyViCareRateLimitError( + { + "extendedPayload": { + "name": "development portal", + "requestCountLimit": 1450, + "limitReset": 1757376004000, + } + } + ) + + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch( + f"{MODULE}._setup_vicare_api", + side_effect=rate_limit_error, + ) as setup_api, + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + setup_api.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + # SETUP_RETRY alone would also match an unrelated ConfigEntryNotReady. + assert "rate limit" in mock_config_entry.reason + assert str(rate_limit_error.limitResetDate) in mock_config_entry.reason + + async def test_setup_entry_invalid_configuration( hass: HomeAssistant, mock_config_entry: MockConfigEntry,