@@ -32,46 +32,75 @@ def retry_config(self):
3232 jitter = 0.0 , # No jitter for predictable tests
3333 )
3434
35- def test_retries_on_500_error (self , sync_http_client , retry_config , monkeypatch ):
36- """Test that 500 errors trigger retry."""
37- call_count = 0
35+ @staticmethod
36+ def create_mock_request_with_retries (
37+ failure_count : int ,
38+ failure_response = None ,
39+ failure_exception = None ,
40+ success_response = None
41+ ):
42+ """
43+ Create a mock request function that fails N times before succeeding.
44+
45+ Args:
46+ failure_count: Number of times to fail before success
47+ failure_response: Response to return on failure (status_code, json, headers)
48+ failure_exception: Exception to raise on failure (instead of response)
49+ success_response: Response to return on success (default: 200 with {"success": True})
50+
51+ Returns:
52+ A tuple of (mock_function, call_count_tracker) where call_count_tracker
53+ is a list that tracks the number of calls
54+ """
55+ call_count = [0 ] # Use list to allow modification in nested function
3856
3957 def mock_request (* args , ** kwargs ):
40- nonlocal call_count
41- call_count += 1
42- if call_count < 3 :
43- return httpx .Response (status_code = 500 , json = {"error" : "Server error" })
58+ call_count [0 ] += 1
59+ if call_count [0 ] <= failure_count :
60+ if failure_exception :
61+ raise failure_exception
62+ if failure_response :
63+ return httpx .Response (** failure_response )
64+
65+ if success_response :
66+ return httpx .Response (** success_response )
4467 return httpx .Response (status_code = 200 , json = {"success" : True })
4568
69+ return mock_request , call_count
70+
71+ def test_retries_on_408_error (self , sync_http_client , retry_config , monkeypatch ):
72+ """Test that 408 (Request Timeout) errors trigger retry."""
73+ mock_request , call_count = self .create_mock_request_with_retries (
74+ failure_count = 1 ,
75+ failure_response = {"status_code" : 408 , "json" : {"error" : "Request timeout" }}
76+ )
77+
4678 monkeypatch .setattr (sync_http_client ._client , "request" , MagicMock (side_effect = mock_request ))
4779
4880 with patch ("time.sleep" ) as mock_sleep :
4981 response = sync_http_client .request ("test/path" , retry_config = retry_config )
5082
51- assert call_count == 3
83+ assert call_count [ 0 ] == 2
5284 assert response == {"success" : True }
53- # Verify sleep was called with exponential backoff
54- assert mock_sleep .call_count == 2
85+ assert mock_sleep .call_count == 1
5586
56- def test_retries_on_408_error (self , sync_http_client , retry_config , monkeypatch ):
57- """Test that 408 (Request Timeout) errors trigger retry."""
58- call_count = 0
59-
60- def mock_request (* args , ** kwargs ):
61- nonlocal call_count
62- call_count += 1
63- if call_count < 2 :
64- return httpx .Response (status_code = 408 , json = {"error" : "Request timeout" })
65- return httpx .Response (status_code = 200 , json = {"success" : True })
87+ def test_retries_on_500_error (self , sync_http_client , retry_config , monkeypatch ):
88+ """Test that 500 errors trigger retry."""
89+ mock_request , call_count = self .create_mock_request_with_retries (
90+ failure_count = 2 ,
91+ failure_response = {"status_code" : 500 , "json" : {"error" : "Server error" }}
92+ )
6693
6794 monkeypatch .setattr (sync_http_client ._client , "request" , MagicMock (side_effect = mock_request ))
6895
6996 with patch ("time.sleep" ) as mock_sleep :
7097 response = sync_http_client .request ("test/path" , retry_config = retry_config )
7198
72- assert call_count == 2
99+ assert call_count [ 0 ] == 3
73100 assert response == {"success" : True }
74- assert mock_sleep .call_count == 1
101+ # Verify sleep was called with exponential backoff
102+ assert mock_sleep .call_count == 2
103+
75104
76105 def test_retries_on_502_error (self , sync_http_client , retry_config , monkeypatch ):
77106 """Test that 502 (Bad Gateway) errors trigger retry."""
@@ -233,21 +262,17 @@ def mock_request(*args, **kwargs):
233262
234263 def test_retries_on_network_error (self , sync_http_client , retry_config , monkeypatch ):
235264 """Test that network errors trigger retry."""
236- call_count = 0
237-
238- def mock_request (* args , ** kwargs ):
239- nonlocal call_count
240- call_count += 1
241- if call_count < 3 :
242- raise httpx .ConnectError ("Connection failed" )
243- return httpx .Response (status_code = 200 , json = {"success" : True })
265+ mock_request , call_count = self .create_mock_request_with_retries (
266+ failure_count = 2 ,
267+ failure_exception = httpx .ConnectError ("Connection failed" )
268+ )
244269
245270 monkeypatch .setattr (sync_http_client ._client , "request" , MagicMock (side_effect = mock_request ))
246271
247272 with patch ("time.sleep" ):
248273 response = sync_http_client .request ("test/path" , retry_config = retry_config )
249274
250- assert call_count == 3
275+ assert call_count [ 0 ] == 3
251276 assert response == {"success" : True }
252277
253278 def test_retries_on_timeout_error (self , sync_http_client , retry_config , monkeypatch ):
@@ -394,46 +419,74 @@ def retry_config(self):
394419 jitter = 0.0 , # No jitter for predictable tests
395420 )
396421
397- @pytest .mark .asyncio
398- async def test_retries_on_500_error (self , async_http_client , retry_config , monkeypatch ):
399- """Test that 500 errors trigger retry."""
400- call_count = 0
422+ @staticmethod
423+ def create_mock_request_with_retries (
424+ failure_count : int ,
425+ failure_response = None ,
426+ failure_exception = None ,
427+ success_response = None
428+ ):
429+ """
430+ Create an async mock request function that fails N times before succeeding.
431+
432+ Args:
433+ failure_count: Number of times to fail before success
434+ failure_response: Response to return on failure (status_code, json, headers)
435+ failure_exception: Exception to raise on failure (instead of response)
436+ success_response: Response to return on success (default: 200 with {"success": True})
437+
438+ Returns:
439+ A tuple of (mock_function, call_count_tracker) where call_count_tracker
440+ is a list that tracks the number of calls
441+ """
442+ call_count = [0 ] # Use list to allow modification in nested function
401443
402444 async def mock_request (* args , ** kwargs ):
403- nonlocal call_count
404- call_count += 1
405- if call_count < 3 :
406- return httpx .Response (status_code = 500 , json = {"error" : "Server error" })
445+ call_count [0 ] += 1
446+ if call_count [0 ] <= failure_count :
447+ if failure_exception :
448+ raise failure_exception
449+ if failure_response :
450+ return httpx .Response (** failure_response )
451+
452+ if success_response :
453+ return httpx .Response (** success_response )
407454 return httpx .Response (status_code = 200 , json = {"success" : True })
408455
456+ return mock_request , call_count
457+
458+ @pytest .mark .asyncio
459+ async def test_retries_on_500_error (self , async_http_client , retry_config , monkeypatch ):
460+ """Test that 500 errors trigger retry."""
461+ mock_request , call_count = self .create_mock_request_with_retries (
462+ failure_count = 2 ,
463+ failure_response = {"status_code" : 500 , "json" : {"error" : "Server error" }}
464+ )
465+
409466 monkeypatch .setattr (async_http_client ._client , "request" , AsyncMock (side_effect = mock_request ))
410467
411468 with patch ("asyncio.sleep" ) as mock_sleep :
412469 response = await async_http_client .request ("test/path" , retry_config = retry_config )
413470
414- assert call_count == 3
471+ assert call_count [ 0 ] == 3
415472 assert response == {"success" : True }
416473 # Verify sleep was called with exponential backoff
417474 assert mock_sleep .call_count == 2
418475
419476 @pytest .mark .asyncio
420477 async def test_retries_on_408_error (self , async_http_client , retry_config , monkeypatch ):
421478 """Test that 408 (Request Timeout) errors trigger retry."""
422- call_count = 0
423-
424- async def mock_request (* args , ** kwargs ):
425- nonlocal call_count
426- call_count += 1
427- if call_count < 2 :
428- return httpx .Response (status_code = 408 , json = {"error" : "Request timeout" })
429- return httpx .Response (status_code = 200 , json = {"success" : True })
479+ mock_request , call_count = self .create_mock_request_with_retries (
480+ failure_count = 1 ,
481+ failure_response = {"status_code" : 408 , "json" : {"error" : "Request timeout" }}
482+ )
430483
431484 monkeypatch .setattr (async_http_client ._client , "request" , AsyncMock (side_effect = mock_request ))
432485
433486 with patch ("asyncio.sleep" ) as mock_sleep :
434487 response = await async_http_client .request ("test/path" , retry_config = retry_config )
435488
436- assert call_count == 2
489+ assert call_count [ 0 ] == 2
437490 assert response == {"success" : True }
438491 assert mock_sleep .call_count == 1
439492
0 commit comments