1+ from unittest .mock import patch
12
23import responses
34import requests
@@ -15,7 +16,7 @@ def test_fetch_url() -> None:
1516 status = 200
1617 )
1718
18- resp = fetch_url ("http://example.com" )
19+ resp = fetch_url ("http://example.com" , 1 )
1920
2021 assert resp is not None
2122 assert resp .text == "link"
@@ -26,15 +27,15 @@ def test_fetch_url_connection_error(caplog) -> None: # type: ignore
2627
2728 with caplog .at_level (ERROR ):
2829 # Fetch url whose response isn't mocked to raise ConnectionError
29- resp = fetch_url ("http://connection.error" )
30+ resp = fetch_url ("http://connection.error" , 1 )
3031
3132 assert "Connection error occurred:" in caplog .text
3233 assert resp is None
3334
3435
3536@responses .activate
3637def test_fetch_url_http_error (caplog ) -> None : # type: ignore
37- error_codes = [403 , 404 , 408 ]
38+ error_codes = [403 , 404 , 412 ]
3839
3940 for error_code in error_codes :
4041 setup_mock_response (
@@ -44,7 +45,7 @@ def test_fetch_url_http_error(caplog) -> None: # type: ignore
4445 )
4546
4647 with caplog .at_level (ERROR ):
47- resp = fetch_url (f"http://http.error/{ error_code } " )
48+ resp = fetch_url (f"http://http.error/{ error_code } " , 1 )
4849
4950 assert "HTTP error occurred:" in caplog .text
5051 assert resp is None
@@ -60,7 +61,7 @@ def test_fetch_url_timeout_error(caplog) -> None: # type: ignore
6061
6162 with caplog .at_level (ERROR ):
6263 # Fetch url whose response isn't mocked to raise ConnectionError
63- resp = fetch_url ("http://timeout.error" )
64+ resp = fetch_url ("http://timeout.error" , 1 )
6465
6566 assert "Timeout error occurred:" in caplog .text
6667 assert resp is None
@@ -76,7 +77,92 @@ def test_fetch_url_requests_exception(caplog) -> None: # type: ignore
7677
7778 with caplog .at_level (ERROR ):
7879 # Fetch url whose response isn't mocked to raise ConnectionError
79- resp = fetch_url ("http://requests.exception" )
80+ resp = fetch_url ("http://requests.exception" , 1 )
8081
8182 assert "Request error occurred:" in caplog .text
8283 assert resp is None
84+
85+
86+ @patch ("time.sleep" )
87+ @responses .activate
88+ def test_fetch_url_transient_error_retry_5 (mock_sleep , caplog ) -> None : # type: ignore
89+ setup_mock_response (
90+ url = "http://transient.error" ,
91+ body = "<html><body><a href='http://transient.error'>link</a></body></html>" ,
92+ status = 503
93+ )
94+
95+ max_retry_attempts = 5
96+
97+ with caplog .at_level (ERROR ):
98+ resp = fetch_url ("http://transient.error" , max_retry_attempts )
99+
100+ assert resp is None
101+
102+ # Assert url was fetched once then retried x ammount of times
103+ assert len (responses .calls ) == max_retry_attempts + 1
104+
105+ # Assert sleep time grew with every request
106+ expected_delays = [1 , 2 , 3 , 4 , 5 ]
107+ actual_delays = [call .args [0 ] for call in mock_sleep .call_args_list ]
108+ assert actual_delays == expected_delays
109+
110+ assert "Transient HTTP error occurred:" in caplog .text
111+
112+
113+ @patch ("time.sleep" )
114+ @responses .activate
115+ def test_fetch_url_transient_error_retry_10 (mock_sleep , caplog ) -> None : # type: ignore
116+ setup_mock_response (
117+ url = "http://transient.error" ,
118+ body = "<html><body><a href='http://transient.error'>link</a></body></html>" ,
119+ status = 503
120+ )
121+
122+ max_retry_attempts = 10
123+
124+ with caplog .at_level (ERROR ):
125+ resp = fetch_url ("http://transient.error" , max_retry_attempts )
126+
127+ assert resp is None
128+
129+ # Assert url was fetched once then retried x ammount of times
130+ assert len (responses .calls ) == max_retry_attempts + 1
131+
132+ # Assert sleep time grew with every request
133+ expected_delays = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]
134+ actual_delays = [call .args [0 ] for call in mock_sleep .call_args_list ]
135+ assert actual_delays == expected_delays
136+
137+ assert "Transient HTTP error occurred:" in caplog .text
138+
139+
140+ @patch ("time.sleep" )
141+ @responses .activate
142+ def test_fetch_url_transient_error_retry_success (mock_sleep , caplog ) -> None : # type: ignore
143+ setup_mock_response (
144+ url = "http://transient.error" ,
145+ body = "<html><body><a href='http://transient.error'>link</a></body></html>" ,
146+ status = 503
147+ )
148+ setup_mock_response (
149+ url = "http://transient.error" ,
150+ body = "<html><body><a href='http://transient.error'>link</a></body></html>" ,
151+ status = 200
152+ )
153+
154+ max_retry_attempts = 1
155+
156+ with caplog .at_level (ERROR ):
157+ resp = fetch_url ("http://transient.error" , max_retry_attempts )
158+
159+ assert resp is not None
160+ assert resp .text == "link"
161+
162+ # Assert url was fetched 2 times
163+ assert len (responses .calls ) == 2
164+
165+ # Assert time.sleep was called
166+ mock_sleep .assert_called_once_with (1 )
167+
168+ assert "Transient HTTP error occurred:" in caplog .text
0 commit comments