@@ -1117,3 +1117,106 @@ def stop_listen(self) -> None:
11171117
11181118 # Ensure the coordinator is released
11191119 assert weak_ref () is None
1120+
1121+
1122+ @pytest .mark .parametrize (
1123+ ("exc" , "expected_exception" , "message" ),
1124+ [
1125+ * KNOWN_ERRORS ,
1126+ (Exception (), Exception , "Unknown exception" ),
1127+ (
1128+ update_coordinator .UpdateFailed (retry_after = 60 ),
1129+ update_coordinator .UpdateFailed ,
1130+ "Error fetching test data" ,
1131+ ),
1132+ ],
1133+ )
1134+ @pytest .mark .parametrize (
1135+ "method" ,
1136+ ["update_method" , "setup_method" ],
1137+ )
1138+ async def test_update_failed_retry_after (
1139+ hass : HomeAssistant ,
1140+ exc : Exception ,
1141+ expected_exception : type [Exception ],
1142+ message : str ,
1143+ method : str ,
1144+ caplog : pytest .LogCaptureFixture ,
1145+ ) -> None :
1146+ """Test async_config_entry_first_refresh raises ConfigEntryNotReady on failure.
1147+
1148+ Verify we do not log the exception since raising ConfigEntryNotReady
1149+ will be caught by config_entries.async_setup which will log it with
1150+ a decreasing level of logging once the first message is logged.
1151+ """
1152+ entry = MockConfigEntry ()
1153+ entry .mock_state (
1154+ hass ,
1155+ config_entries .ConfigEntryState .SETUP_IN_PROGRESS ,
1156+ )
1157+ crd = get_crd (hass , DEFAULT_UPDATE_INTERVAL , entry )
1158+ setattr (crd , method , AsyncMock (side_effect = exc ))
1159+
1160+ with pytest .raises (ConfigEntryNotReady ):
1161+ await crd .async_config_entry_first_refresh ()
1162+
1163+ assert crd .last_update_success is False
1164+ assert isinstance (crd .last_exception , expected_exception )
1165+ assert message not in caplog .text
1166+
1167+ # Only to check the retry_after wasn't hit
1168+ assert crd ._retry_after is None
1169+
1170+
1171+ @pytest .mark .parametrize (
1172+ ("exc" , "expected_exception" , "message" ),
1173+ [
1174+ (
1175+ update_coordinator .UpdateFailed (retry_after = 60 ),
1176+ update_coordinator .UpdateFailed ,
1177+ "Error fetching test data" ,
1178+ ),
1179+ ],
1180+ )
1181+ async def test_refresh_known_errors_retry_after (
1182+ exc : update_coordinator .UpdateFailed ,
1183+ expected_exception : type [Exception ],
1184+ message : str ,
1185+ crd : update_coordinator .DataUpdateCoordinator [int ],
1186+ caplog : pytest .LogCaptureFixture ,
1187+ hass : HomeAssistant ,
1188+ ) -> None :
1189+ """Test raising known errors, this time with retry_after."""
1190+ unsub = crd .async_add_listener (lambda : None )
1191+
1192+ crd .update_method = AsyncMock (side_effect = exc )
1193+
1194+ with (
1195+ patch .object (hass .loop , "time" , return_value = 1_000.0 ),
1196+ patch .object (hass .loop , "call_at" ) as mock_call_at ,
1197+ ):
1198+ await crd .async_refresh ()
1199+
1200+ assert crd .data is None
1201+ assert crd .last_update_success is False
1202+ assert isinstance (crd .last_exception , expected_exception )
1203+ assert message in caplog .text
1204+
1205+ when = mock_call_at .call_args [0 ][0 ]
1206+
1207+ expected = 1_000.0 + crd ._microsecond + exc .retry_after
1208+ assert abs (when - expected ) < 0.005 , (when , expected )
1209+
1210+ assert crd ._retry_after is None
1211+
1212+ # Next schedule should fall back to regular update_interval
1213+ mock_call_at .reset_mock ()
1214+ crd ._schedule_refresh ()
1215+ when2 = mock_call_at .call_args [0 ][0 ]
1216+ expected_cancelled = (
1217+ 1_000.0 + crd ._microsecond + crd .update_interval .total_seconds ()
1218+ )
1219+ assert abs (when2 - expected_cancelled ) < 0.005 , (when2 , expected_cancelled )
1220+
1221+ unsub ()
1222+ crd ._unschedule_refresh ()
0 commit comments