@@ -43,7 +43,9 @@ def rate_limit_detected(w):
43
43
return False
44
44
45
45
46
- def create_mock_rate_limit_response (status_code = 429 , retry_after = None , content_type = "application/json" ):
46
+ def create_mock_rate_limit_response (
47
+ status_code = 429 , retry_after = None , content_type = "application/json"
48
+ ):
47
49
"""Create a mock response object for testing rate limit scenarios."""
48
50
# Use Mock(spec=requests.Response) to properly simulate a requests.Response object
49
51
mock_response = Mock (spec = requests .Response )
@@ -52,12 +54,12 @@ def create_mock_rate_limit_response(status_code=429, retry_after=None, content_t
52
54
mock_response .headers = {}
53
55
54
56
if retry_after is not None :
55
- mock_response .headers [' Retry-After' ] = retry_after
57
+ mock_response .headers [" Retry-After" ] = retry_after
56
58
57
- mock_response .headers [' Content-Type' ] = content_type
59
+ mock_response .headers [" Content-Type" ] = content_type
58
60
mock_response .json .return_value = {
59
- ' message' : ' Rate limit exceeded' ,
60
- ' trackingId' : ' test-tracking-id-12345'
61
+ " message" : " Rate limit exceeded" ,
62
+ " trackingId" : " test-tracking-id-12345" ,
61
63
}
62
64
63
65
# Mock the request attribute that ApiError constructor needs
@@ -91,22 +93,27 @@ def test_rate_limit_error_with_valid_retry_after():
91
93
"""Test RateLimitError works correctly with valid Retry-After headers."""
92
94
# Test with various valid integer values
93
95
test_cases = [
94
- ('30' , 30 ), # Normal case
95
- ('60' , 60 ), # One minute
96
- ('0' , 1 ), # Zero should default to 1 (minimum)
97
- ('1' , 1 ), # Minimum value
98
- (' 300' , 300 ), # Five minutes
96
+ ("30" , 30 ), # Normal case
97
+ ("60" , 60 ), # One minute
98
+ ("0" , 1 ), # Zero should default to 1 (minimum)
99
+ ("1" , 1 ), # Minimum value
100
+ (" 300" , 300 ), # Five minutes
99
101
]
100
102
101
103
for header_value , expected_value in test_cases :
102
- mock_response = create_mock_rate_limit_response (retry_after = header_value )
104
+ mock_response = create_mock_rate_limit_response (
105
+ retry_after = header_value
106
+ )
103
107
104
108
try :
105
109
error = webexpythonsdk .RateLimitError (mock_response )
106
- assert error .retry_after == expected_value , \
107
- f"Expected retry_after={ expected_value } , got { error .retry_after } for header '{ header_value } '"
110
+ assert (
111
+ error .retry_after == expected_value
112
+ ), f"Expected retry_after={ expected_value } , got { error .retry_after } for header '{ header_value } '"
108
113
except Exception as e :
109
- pytest .fail (f"RateLimitError creation failed for valid header '{ header_value } ': { e } " )
114
+ pytest .fail (
115
+ f"RateLimitError creation failed for valid header '{ header_value } ': { e } "
116
+ )
110
117
111
118
112
119
def test_rate_limit_error_without_retry_after ():
@@ -115,9 +122,13 @@ def test_rate_limit_error_without_retry_after():
115
122
116
123
try :
117
124
error = webexpythonsdk .RateLimitError (mock_response )
118
- assert error .retry_after == 15 , f"Expected default retry_after=15, got { error .retry_after } "
125
+ assert (
126
+ error .retry_after == 15
127
+ ), f"Expected default retry_after=15, got { error .retry_after } "
119
128
except Exception as e :
120
- pytest .fail (f"RateLimitError creation failed when Retry-After header is missing: { e } " )
129
+ pytest .fail (
130
+ f"RateLimitError creation failed when Retry-After header is missing: { e } "
131
+ )
121
132
122
133
123
134
def test_rate_limit_error_with_malformed_retry_after ():
@@ -127,65 +138,77 @@ def test_rate_limit_error_with_malformed_retry_after():
127
138
like 'rand(30),add(30)' cause ValueError exceptions.
128
139
"""
129
140
malformed_headers = [
130
- ' rand(30),add(30)' , # The exact case from the user report
131
- ' invalid' , # Non-numeric string
132
- ' 30.5' , # Float (should fail int conversion)
133
- ' 30 seconds' , # String with numbers and text
134
- ' 30,60' , # Comma-separated values
135
- '' , # Empty string
136
- ' None' , # String 'None'
137
- ' null' , # String 'null'
141
+ " rand(30),add(30)" , # The exact case from the user report
142
+ " invalid" , # Non-numeric string
143
+ " 30.5" , # Float (should fail int conversion)
144
+ " 30 seconds" , # String with numbers and text
145
+ " 30,60" , # Comma-separated values
146
+ "" , # Empty string
147
+ " None" , # String 'None'
148
+ " null" , # String 'null'
138
149
]
139
150
140
151
for malformed_header in malformed_headers :
141
- mock_response = create_mock_rate_limit_response (retry_after = malformed_header )
152
+ mock_response = create_mock_rate_limit_response (
153
+ retry_after = malformed_header
154
+ )
142
155
143
156
try :
144
157
# This should NOT raise a ValueError - it should handle gracefully
145
158
error = webexpythonsdk .RateLimitError (mock_response )
146
159
# If we get here, the error was handled gracefully
147
160
# The retry_after should default to 15 for malformed headers
148
- assert error .retry_after == 15 , \
149
- f"Expected default retry_after=15 for malformed header '{ malformed_header } ', got { error .retry_after } "
161
+ assert (
162
+ error .retry_after == 15
163
+ ), f"Expected default retry_after=15 for malformed header '{ malformed_header } ', got { error .retry_after } "
150
164
except ValueError as e :
151
165
# This is the bug we're testing for - it should NOT happen
152
- pytest .fail (f"RateLimitError raised ValueError for malformed header '{ malformed_header } ': { e } " )
166
+ pytest .fail (
167
+ f"RateLimitError raised ValueError for malformed header '{ malformed_header } ': { e } "
168
+ )
153
169
except Exception as e :
154
170
# Other exceptions are acceptable as long as they're not ValueError
155
171
if isinstance (e , ValueError ):
156
- pytest .fail (f"RateLimitError raised ValueError for malformed header '{ malformed_header } ': { e } " )
172
+ pytest .fail (
173
+ f"RateLimitError raised ValueError for malformed header '{ malformed_header } ': { e } "
174
+ )
157
175
158
176
159
177
def test_rate_limit_error_with_non_string_retry_after ():
160
178
"""Test RateLimitError handles non-string Retry-After header values."""
161
179
# Test cases with expected behavior based on how Python int() actually works
162
180
test_cases = [
163
- (None , 15 ), # None value -> defaults to 15
164
- (30 , 30 ), # Integer -> converts to 30 (not malformed)
165
- (30.5 , 30 ), # Float -> converts to 30 (truncated)
166
- (True , 1 ), # Boolean True -> converts to 1
167
- (False , 1 ), # Boolean False -> converts to 0, then max(1, 0) = 1
168
- ([], 15 ), # List -> TypeError, defaults to 15
169
- ({}, 15 ), # Dict -> TypeError, defaults to 15
170
- ]
181
+ (None , 15 ), # None value -> defaults to 15
182
+ (30 , 30 ), # Integer -> converts to 30 (not malformed)
183
+ (30.5 , 30 ), # Float -> converts to 30 (truncated)
184
+ (True , 1 ), # Boolean True -> converts to 1
185
+ (False , 1 ), # Boolean False -> converts to 0, then max(1, 0) = 1
186
+ ([], 15 ), # List -> TypeError, defaults to 15
187
+ ({}, 15 ), # Dict -> TypeError, defaults to 15
188
+ ]
171
189
172
190
for non_string_value , expected_value in test_cases :
173
- mock_response = create_mock_rate_limit_response (retry_after = non_string_value )
191
+ mock_response = create_mock_rate_limit_response (
192
+ retry_after = non_string_value
193
+ )
174
194
175
195
try :
176
196
error = webexpythonsdk .RateLimitError (mock_response )
177
- assert error .retry_after == expected_value , \
178
- f"Expected retry_after={ expected_value } , got { error .retry_after } for non-string value { non_string_value } "
197
+ assert (
198
+ error .retry_after == expected_value
199
+ ), f"Expected retry_after={ expected_value } , got { error .retry_after } for non-string value { non_string_value } "
179
200
except Exception as e :
180
- pytest .fail (f"RateLimitError creation failed for non-string value { non_string_value } : { e } " )
201
+ pytest .fail (
202
+ f"RateLimitError creation failed for non-string value { non_string_value } : { e } "
203
+ )
181
204
182
205
183
206
def test_rate_limit_error_integration_with_check_response_code ():
184
207
"""Test that check_response_code properly raises RateLimitError for 429 responses."""
185
208
from webexpythonsdk .utils import check_response_code
186
209
187
210
# Test with valid Retry-After header
188
- mock_response = create_mock_rate_limit_response (retry_after = '45' )
211
+ mock_response = create_mock_rate_limit_response (retry_after = "45" )
189
212
190
213
with pytest .raises (webexpythonsdk .RateLimitError ) as exc_info :
191
214
check_response_code (mock_response , 200 ) # Expect 200, get 429
@@ -200,7 +223,9 @@ def test_rate_limit_error_integration_with_malformed_header():
200
223
from webexpythonsdk .utils import check_response_code
201
224
202
225
# Test with malformed Retry-After header
203
- mock_response = create_mock_rate_limit_response (retry_after = 'rand(30),add(30)' )
226
+ mock_response = create_mock_rate_limit_response (
227
+ retry_after = "rand(30),add(30)"
228
+ )
204
229
205
230
with pytest .raises (webexpythonsdk .RateLimitError ) as exc_info :
206
231
check_response_code (mock_response , 200 ) # Expect 200, get 429
@@ -213,37 +238,42 @@ def test_rate_limit_error_integration_with_malformed_header():
213
238
214
239
def test_rate_limit_error_edge_cases ():
215
240
"""Test RateLimitError with edge case Retry-After values."""
216
- # Test cases based on how Python int() actually works with strings
241
+ # Test cases based on how Python int() actually works with strings
217
242
edge_cases = [
218
- ( '-1' , 1 ), # Negative string -> converts to -1, then max(1, -1) = 1
219
- ( ' 999999' , 999999 ), # Very large number string -> converts to 999999
220
- ( ' 0.0' , 15 ), # Float string -> treated as malformed, defaults to 15
221
- ( ' 0.9' , 15 ), # Float string -> treated as malformed, defaults to 15
222
- ( ' 1.0' , 15 ), # Float string -> treated as malformed, defaults to 15
223
- ( ' 1.9' , 15 ), # Float string -> treated as malformed, defaults to 15
224
- ( ' 2.0' , 15 ), # Float string -> treated as malformed, defaults to 15
225
- ]
243
+ ( "-1" , 1 ), # Negative string -> converts to -1, then max(1, -1) = 1
244
+ ( " 999999" , 999999 ), # Very large number string -> converts to 999999
245
+ ( " 0.0" , 15 ), # Float string -> treated as malformed, defaults to 15
246
+ ( " 0.9" , 15 ), # Float string -> treated as malformed, defaults to 15
247
+ ( " 1.0" , 15 ), # Float string -> treated as malformed, defaults to 15
248
+ ( " 1.9" , 15 ), # Float string -> treated as malformed, defaults to 15
249
+ ( " 2.0" , 15 ), # Float string -> treated as malformed, defaults to 15
250
+ ]
226
251
227
252
for header_value , expected_value in edge_cases :
228
- mock_response = create_mock_rate_limit_response (retry_after = header_value )
253
+ mock_response = create_mock_rate_limit_response (
254
+ retry_after = header_value
255
+ )
229
256
230
257
try :
231
258
error = webexpythonsdk .RateLimitError (mock_response )
232
259
# All float strings are being treated as malformed and defaulting to 15
233
260
# Integer strings work normally with max(1, value)
234
- if '.' in header_value : # Float strings
261
+ if "." in header_value : # Float strings
235
262
actual_expected = 15 # Treated as malformed
236
263
else :
237
264
actual_expected = max (1 , expected_value )
238
- assert error .retry_after == actual_expected , \
239
- f"Expected retry_after={ actual_expected } , got { error .retry_after } for header '{ header_value } '"
265
+ assert (
266
+ error .retry_after == actual_expected
267
+ ), f"Expected retry_after={ actual_expected } , got { error .retry_after } for header '{ header_value } '"
240
268
except Exception as e :
241
- pytest .fail (f"RateLimitError creation failed for edge case header '{ header_value } ': { e } " )
269
+ pytest .fail (
270
+ f"RateLimitError creation failed for edge case header '{ header_value } ': { e } "
271
+ )
242
272
243
273
244
274
def test_rate_limit_error_response_attributes ():
245
275
"""Test that RateLimitError properly extracts all response attributes."""
246
- mock_response = create_mock_rate_limit_response (retry_after = '60' )
276
+ mock_response = create_mock_rate_limit_response (retry_after = "60" )
247
277
248
278
error = webexpythonsdk .RateLimitError (mock_response )
249
279
0 commit comments