@@ -14,6 +14,12 @@ class CustomSubException < HTTP::TimeoutError
1414 end
1515end
1616
17+ # Subclass for testing is_a? vs instance_of? in StatusError handling
18+ unless defined? ( CustomStatusError )
19+ class CustomStatusError < HTTP ::StatusError
20+ end
21+ end
22+
1723class HTTPRetriablePerformerTest < Minitest ::Test
1824 cover "HTTP::Retriable::Performer*"
1925
@@ -196,6 +202,146 @@ def test_perform_unexpected_status_does_not_retry
196202 assert_equal 1 , counter_spy
197203 end
198204
205+ # -- StatusError raised inside the attempt (raise_error feature interop) --
206+
207+ def error_response
208+ @error_response ||= make_response ( status : 503 )
209+ end
210+
211+ def test_perform_retries_status_error_matching_retry_statuses
212+ assert_raises HTTP ::OutOfRetriesError do
213+ perform ( retry_statuses : [ 503 ] , tries : 3 ) do
214+ raise HTTP ::StatusError , error_response
215+ end
216+ end
217+ assert_equal 3 , counter_spy
218+ end
219+
220+ def test_perform_retries_status_error_subclasses_and_attaches_response
221+ err = nil
222+ begin
223+ perform ( retry_statuses : [ 503 ] , tries : 2 ) do
224+ raise CustomStatusError , error_response
225+ end
226+ rescue HTTP ::OutOfRetriesError => e
227+ err = e
228+ end
229+
230+ assert_equal error_response , err . response
231+ assert_equal 2 , counter_spy
232+ end
233+
234+ def test_perform_does_not_retry_status_error_not_matching_retry_statuses
235+ assert_raises HTTP ::StatusError do
236+ perform ( retry_statuses : [ 503 ] , tries : 3 ) do
237+ raise HTTP ::StatusError , make_response ( status : 404 )
238+ end
239+ end
240+ assert_equal 1 , counter_spy
241+ end
242+
243+ def test_perform_does_not_retry_plain_exception_when_retry_statuses_configured
244+ assert_raises CustomException do
245+ perform ( retry_statuses : [ 503 ] , tries : 3 ) do
246+ raise CustomException
247+ end
248+ end
249+ assert_equal 1 , counter_spy
250+ end
251+
252+ def test_perform_does_not_retry_status_error_without_retry_statuses
253+ assert_raises HTTP ::StatusError do
254+ perform ( tries : 3 ) do
255+ raise HTTP ::StatusError , error_response
256+ end
257+ end
258+ assert_equal 1 , counter_spy
259+ end
260+
261+ def test_out_of_retries_error_from_status_error_has_response_and_cause
262+ err = nil
263+ begin
264+ perform ( retry_statuses : [ 503 ] , tries : 2 ) do
265+ raise HTTP ::StatusError , error_response
266+ end
267+ rescue HTTP ::OutOfRetriesError => e
268+ err = e
269+ end
270+
271+ assert_equal error_response , err . response
272+ assert_kind_of HTTP ::StatusError , err . cause
273+ end
274+
275+ def test_out_of_retries_error_from_status_error_in_exceptions_list_has_response
276+ err = nil
277+ begin
278+ perform ( exceptions : [ HTTP ::StatusError ] , tries : 2 ) do
279+ raise HTTP ::StatusError , error_response
280+ end
281+ rescue HTTP ::OutOfRetriesError => e
282+ err = e
283+ end
284+
285+ assert_equal error_response , err . response
286+ end
287+
288+ def test_on_retry_callback_with_status_error_receives_error_and_response
289+ callback_call_spy = 0
290+
291+ callback_spy = proc do |callback_request , error , callback_response |
292+ assert_equal request , callback_request
293+ assert_kind_of HTTP ::StatusError , error
294+ assert_equal error_response , callback_response
295+ callback_call_spy += 1
296+ end
297+
298+ assert_raises HTTP ::OutOfRetriesError do
299+ perform ( retry_statuses : [ 503 ] , tries : 3 , on_retry : callback_spy ) do
300+ raise HTTP ::StatusError , error_response
301+ end
302+ end
303+
304+ assert_equal 2 , callback_call_spy
305+ end
306+
307+ def test_calculate_delay_receives_status_error_response
308+ responses_seen = [ ]
309+
310+ performer = HTTP ::Retriable ::Performer . new ( delay : 0 , retry_statuses : [ 503 ] , tries : 2 )
311+ calculator = performer . instance_variable_get ( :@delay_calculator )
312+ original_call = calculator . method ( :call )
313+ calculator . define_singleton_method ( :call ) do |iteration , resp |
314+ responses_seen << resp
315+ original_call . call ( iteration , resp )
316+ end
317+
318+ begin
319+ performer . perform ( client , request ) { raise HTTP ::StatusError , error_response }
320+ rescue HTTP ::OutOfRetriesError
321+ nil
322+ end
323+
324+ assert_equal error_response , responses_seen . first
325+ end
326+
327+ def test_response_flushing_flushes_status_error_response_when_retries_exhausted
328+ flushed = false
329+ error_response . define_singleton_method ( :flush ) do
330+ flushed = true
331+ self
332+ end
333+
334+ begin
335+ perform ( retry_statuses : [ 503 ] , tries : 2 ) do
336+ raise HTTP ::StatusError , error_response
337+ end
338+ rescue HTTP ::OutOfRetriesError
339+ nil
340+ end
341+
342+ assert flushed , "expected StatusError response to be flushed on final attempt"
343+ end
344+
199345 # -- on_retry callback --
200346
201347 def test_on_retry_callback_with_exception
0 commit comments