1
+ from unittest .mock import patch
1
2
2
3
import responses
3
4
import requests
@@ -15,7 +16,7 @@ def test_fetch_url() -> None:
15
16
status = 200
16
17
)
17
18
18
- resp = fetch_url ("http://example.com" )
19
+ resp = fetch_url ("http://example.com" , 1 )
19
20
20
21
assert resp is not None
21
22
assert resp .text == "link"
@@ -26,15 +27,15 @@ def test_fetch_url_connection_error(caplog) -> None: # type: ignore
26
27
27
28
with caplog .at_level (ERROR ):
28
29
# 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 )
30
31
31
32
assert "Connection error occurred:" in caplog .text
32
33
assert resp is None
33
34
34
35
35
36
@responses .activate
36
37
def test_fetch_url_http_error (caplog ) -> None : # type: ignore
37
- error_codes = [403 , 404 , 408 ]
38
+ error_codes = [403 , 404 , 412 ]
38
39
39
40
for error_code in error_codes :
40
41
setup_mock_response (
@@ -44,7 +45,7 @@ def test_fetch_url_http_error(caplog) -> None: # type: ignore
44
45
)
45
46
46
47
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 )
48
49
49
50
assert "HTTP error occurred:" in caplog .text
50
51
assert resp is None
@@ -60,7 +61,7 @@ def test_fetch_url_timeout_error(caplog) -> None: # type: ignore
60
61
61
62
with caplog .at_level (ERROR ):
62
63
# 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 )
64
65
65
66
assert "Timeout error occurred:" in caplog .text
66
67
assert resp is None
@@ -76,7 +77,92 @@ def test_fetch_url_requests_exception(caplog) -> None: # type: ignore
76
77
77
78
with caplog .at_level (ERROR ):
78
79
# 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 )
80
81
81
82
assert "Request error occurred:" in caplog .text
82
83
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