@@ -151,7 +151,8 @@ def test_get_errors(self, mock_get):
151
151
152
152
@mock .patch ('requests.get' )
153
153
def test_get_rate_limit_error (self , mock_get ):
154
- rc = RestClient (jwt = 'a-token' , telemetry = False )
154
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = 0 )
155
+ rc ._skip_sleep = True
155
156
156
157
mock_get .return_value .text = '{"statusCode": 429,' \
157
158
' "errorCode": "code",' \
@@ -172,15 +173,18 @@ def test_get_rate_limit_error(self, mock_get):
172
173
self .assertIsInstance (context .exception , RateLimitError )
173
174
self .assertEqual (context .exception .reset_at , 9 )
174
175
176
+ self .assertEqual (rc ._metrics ['retries' ], 0 )
177
+
175
178
@mock .patch ('requests.get' )
176
179
def test_get_rate_limit_error_without_headers (self , mock_get ):
177
- rc = RestClient (jwt = 'a-token' , telemetry = False )
180
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = 0 )
181
+ rc ._skip_sleep = True
178
182
179
183
mock_get .return_value .text = '{"statusCode": 429,' \
180
184
' "errorCode": "code",' \
181
185
' "message": "message"}'
182
186
mock_get .return_value .status_code = 429
183
-
187
+
184
188
mock_get .return_value .headers = {}
185
189
with self .assertRaises (Auth0Error ) as context :
186
190
rc .get ('the/url' )
@@ -191,6 +195,175 @@ def test_get_rate_limit_error_without_headers(self, mock_get):
191
195
self .assertIsInstance (context .exception , RateLimitError )
192
196
self .assertEqual (context .exception .reset_at , - 1 )
193
197
198
+ self .assertEqual (rc ._metrics ['retries' ], 0 )
199
+
200
+ @mock .patch ('requests.get' )
201
+ def test_get_rate_limit_custom_retries (self , mock_get ):
202
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = 5 )
203
+ rc ._skip_sleep = True
204
+
205
+ mock_get .return_value .text = '{"statusCode": 429,' \
206
+ ' "errorCode": "code",' \
207
+ ' "message": "message"}'
208
+ mock_get .return_value .status_code = 429
209
+ mock_get .return_value .headers = {
210
+ 'x-ratelimit-limit' : '3' ,
211
+ 'x-ratelimit-remaining' : '6' ,
212
+ 'x-ratelimit-reset' : '9' ,
213
+ }
214
+
215
+ with self .assertRaises (Auth0Error ) as context :
216
+ response = rc .get ('the/url' )
217
+
218
+ self .assertEqual (context .exception .status_code , 429 )
219
+ self .assertEqual (context .exception .error_code , 'code' )
220
+ self .assertEqual (context .exception .message , 'message' )
221
+ self .assertIsInstance (context .exception , RateLimitError )
222
+ self .assertEqual (context .exception .reset_at , 9 )
223
+
224
+ self .assertEqual (rc ._metrics ['retries' ], 5 )
225
+ self .assertEqual (rc ._metrics ['retries' ], len (rc ._metrics ['backoff' ]))
226
+
227
+ @mock .patch ('requests.get' )
228
+ def test_get_rate_limit_invalid_retries_below_min (self , mock_get ):
229
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = - 1 )
230
+ rc ._skip_sleep = True
231
+
232
+ mock_get .return_value .text = '{"statusCode": 429,' \
233
+ ' "errorCode": "code",' \
234
+ ' "message": "message"}'
235
+ mock_get .return_value .status_code = 429
236
+ mock_get .return_value .headers = {
237
+ 'x-ratelimit-limit' : '3' ,
238
+ 'x-ratelimit-remaining' : '6' ,
239
+ 'x-ratelimit-reset' : '9' ,
240
+ }
241
+
242
+ with self .assertRaises (Auth0Error ) as context :
243
+ response = rc .get ('the/url' )
244
+
245
+ self .assertEqual (context .exception .status_code , 429 )
246
+ self .assertEqual (context .exception .error_code , 'code' )
247
+ self .assertEqual (context .exception .message , 'message' )
248
+ self .assertIsInstance (context .exception , RateLimitError )
249
+ self .assertEqual (context .exception .reset_at , 9 )
250
+
251
+ self .assertEqual (rc ._metrics ['retries' ], 0 )
252
+
253
+
254
+ @mock .patch ('requests.get' )
255
+ def test_get_rate_limit_invalid_retries_above_max (self , mock_get ):
256
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = 11 )
257
+ rc ._skip_sleep = True
258
+
259
+ mock_get .return_value .text = '{"statusCode": 429,' \
260
+ ' "errorCode": "code",' \
261
+ ' "message": "message"}'
262
+ mock_get .return_value .status_code = 429
263
+ mock_get .return_value .headers = {
264
+ 'x-ratelimit-limit' : '3' ,
265
+ 'x-ratelimit-remaining' : '6' ,
266
+ 'x-ratelimit-reset' : '9' ,
267
+ }
268
+
269
+ with self .assertRaises (Auth0Error ) as context :
270
+ response = rc .get ('the/url' )
271
+
272
+ self .assertEqual (context .exception .status_code , 429 )
273
+ self .assertEqual (context .exception .error_code , 'code' )
274
+ self .assertEqual (context .exception .message , 'message' )
275
+ self .assertIsInstance (context .exception , RateLimitError )
276
+ self .assertEqual (context .exception .reset_at , 9 )
277
+
278
+ self .assertEqual (rc ._metrics ['retries' ], rc .MAX_REQUEST_RETRIES ())
279
+
280
+ @mock .patch ('requests.get' )
281
+ def test_get_rate_limit_retries_use_exponential_backoff (self , mock_get ):
282
+ rc = RestClient (jwt = 'a-token' , telemetry = False , retries = 10 )
283
+ rc ._skip_sleep = True
284
+
285
+ mock_get .return_value .text = '{"statusCode": 429,' \
286
+ ' "errorCode": "code",' \
287
+ ' "message": "message"}'
288
+ mock_get .return_value .status_code = 429
289
+ mock_get .return_value .headers = {
290
+ 'x-ratelimit-limit' : '3' ,
291
+ 'x-ratelimit-remaining' : '6' ,
292
+ 'x-ratelimit-reset' : '9' ,
293
+ }
294
+
295
+ with self .assertRaises (Auth0Error ) as context :
296
+ response = rc .get ('the/url' )
297
+
298
+ self .assertEqual (context .exception .status_code , 429 )
299
+ self .assertEqual (context .exception .error_code , 'code' )
300
+ self .assertEqual (context .exception .message , 'message' )
301
+ self .assertIsInstance (context .exception , RateLimitError )
302
+ self .assertEqual (context .exception .reset_at , 9 )
303
+
304
+ self .assertEqual (rc ._metrics ['retries' ], 10 )
305
+ self .assertEqual (rc ._metrics ['retries' ], len (rc ._metrics ['backoff' ]))
306
+
307
+ baseBackoff = [0 ]
308
+ baseBackoffSum = 0
309
+ finalBackoff = 0
310
+
311
+ for i in range (0 , 9 ):
312
+ backoff = 100 * 2 ** i
313
+ baseBackoff .append (backoff )
314
+ baseBackoffSum += backoff
315
+
316
+ for backoff in rc ._metrics ['backoff' ]:
317
+ finalBackoff += backoff
318
+
319
+ # Assert that exponential backoff is happening.
320
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][1 ], rc ._metrics ['backoff' ][0 ])
321
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][2 ], rc ._metrics ['backoff' ][1 ])
322
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][3 ], rc ._metrics ['backoff' ][2 ])
323
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][4 ], rc ._metrics ['backoff' ][3 ])
324
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][5 ], rc ._metrics ['backoff' ][4 ])
325
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][6 ], rc ._metrics ['backoff' ][5 ])
326
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][7 ], rc ._metrics ['backoff' ][6 ])
327
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][8 ], rc ._metrics ['backoff' ][7 ])
328
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][9 ], rc ._metrics ['backoff' ][8 ])
329
+
330
+ # Ensure jitter is being applied.
331
+ self .assertNotEquals (rc ._metrics ['backoff' ][1 ], baseBackoff [1 ])
332
+ self .assertNotEquals (rc ._metrics ['backoff' ][2 ], baseBackoff [2 ])
333
+ self .assertNotEquals (rc ._metrics ['backoff' ][3 ], baseBackoff [3 ])
334
+ self .assertNotEquals (rc ._metrics ['backoff' ][4 ], baseBackoff [4 ])
335
+ self .assertNotEquals (rc ._metrics ['backoff' ][5 ], baseBackoff [5 ])
336
+ self .assertNotEquals (rc ._metrics ['backoff' ][6 ], baseBackoff [6 ])
337
+ self .assertNotEquals (rc ._metrics ['backoff' ][7 ], baseBackoff [7 ])
338
+ self .assertNotEquals (rc ._metrics ['backoff' ][8 ], baseBackoff [8 ])
339
+ self .assertNotEquals (rc ._metrics ['backoff' ][9 ], baseBackoff [9 ])
340
+
341
+ # Ensure subsequent delay is never less than the minimum.
342
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][1 ], rc .MIN_REQUEST_RETRY_DELAY ())
343
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][2 ], rc .MIN_REQUEST_RETRY_DELAY ())
344
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][3 ], rc .MIN_REQUEST_RETRY_DELAY ())
345
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][4 ], rc .MIN_REQUEST_RETRY_DELAY ())
346
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][5 ], rc .MIN_REQUEST_RETRY_DELAY ())
347
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][6 ], rc .MIN_REQUEST_RETRY_DELAY ())
348
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][7 ], rc .MIN_REQUEST_RETRY_DELAY ())
349
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][8 ], rc .MIN_REQUEST_RETRY_DELAY ())
350
+ self .assertGreaterEqual (rc ._metrics ['backoff' ][9 ], rc .MIN_REQUEST_RETRY_DELAY ())
351
+
352
+ # Ensure delay is never more than the maximum.
353
+ self .assertLessEqual (rc ._metrics ['backoff' ][0 ], rc .MAX_REQUEST_RETRY_DELAY ())
354
+ self .assertLessEqual (rc ._metrics ['backoff' ][1 ], rc .MAX_REQUEST_RETRY_DELAY ())
355
+ self .assertLessEqual (rc ._metrics ['backoff' ][2 ], rc .MAX_REQUEST_RETRY_DELAY ())
356
+ self .assertLessEqual (rc ._metrics ['backoff' ][3 ], rc .MAX_REQUEST_RETRY_DELAY ())
357
+ self .assertLessEqual (rc ._metrics ['backoff' ][4 ], rc .MAX_REQUEST_RETRY_DELAY ())
358
+ self .assertLessEqual (rc ._metrics ['backoff' ][5 ], rc .MAX_REQUEST_RETRY_DELAY ())
359
+ self .assertLessEqual (rc ._metrics ['backoff' ][6 ], rc .MAX_REQUEST_RETRY_DELAY ())
360
+ self .assertLessEqual (rc ._metrics ['backoff' ][7 ], rc .MAX_REQUEST_RETRY_DELAY ())
361
+ self .assertLessEqual (rc ._metrics ['backoff' ][8 ], rc .MAX_REQUEST_RETRY_DELAY ())
362
+ self .assertLessEqual (rc ._metrics ['backoff' ][9 ], rc .MAX_REQUEST_RETRY_DELAY ())
363
+
364
+ # Ensure total delay sum is never more than 10s.
365
+ self .assertLessEqual (finalBackoff , 10000 )
366
+
194
367
@mock .patch ('requests.post' )
195
368
def test_post (self , mock_post ):
196
369
rc = RestClient (jwt = 'a-token' , telemetry = False )
0 commit comments