Skip to content

Commit

Permalink
Fix rate limit parsing when response's body is empty (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
dorsha authored Feb 24, 2025
1 parent 8785e35 commit e56d018
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 13 deletions.
40 changes: 28 additions & 12 deletions descope/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,34 @@ def __init__(
self.public_keys = {kid: (pub_key, alg)}

def _raise_rate_limit_exception(self, response):
resp = response.json()
raise RateLimitException(
resp.get("errorCode", HTTPStatus.TOO_MANY_REQUESTS),
ERROR_TYPE_API_RATE_LIMIT,
resp.get("errorDescription", ""),
resp.get("errorMessage", ""),
rate_limit_parameters={
API_RATE_LIMIT_RETRY_AFTER_HEADER: int(
response.headers.get(API_RATE_LIMIT_RETRY_AFTER_HEADER, 0)
)
},
)
try:
resp = response.json()
raise RateLimitException(
resp.get("errorCode", HTTPStatus.TOO_MANY_REQUESTS),
ERROR_TYPE_API_RATE_LIMIT,
resp.get("errorDescription", ""),
resp.get("errorMessage", ""),
rate_limit_parameters={
API_RATE_LIMIT_RETRY_AFTER_HEADER: self._parse_retry_after(
response.headers
)
},
)
except RateLimitException:
raise
except Exception as e:
raise RateLimitException(
status_code=HTTPStatus.TOO_MANY_REQUESTS,
error_type=ERROR_TYPE_API_RATE_LIMIT,
error_message=ERROR_TYPE_API_RATE_LIMIT,
error_description=ERROR_TYPE_API_RATE_LIMIT,
)

def _parse_retry_after(self, headers):
try:
return int(headers.get(API_RATE_LIMIT_RETRY_AFTER_HEADER, 0))
except (ValueError, TypeError):
return 0

def do_get(
self,
Expand Down
2 changes: 1 addition & 1 deletion tests/management/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ def test_search_all(self):
verify=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

# Test success flow with time parameters
with patch("requests.post") as mock_post:
network_resp = mock.Mock()
Expand Down
81 changes: 81 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from http import HTTPStatus
import json
import os
import unittest
Expand Down Expand Up @@ -678,6 +679,86 @@ def test_api_rate_limit_exception(self):
{API_RATE_LIMIT_RETRY_AFTER_HEADER: 10},
)

def test_api_rate_limit_invalid_header(self):
auth = Auth(self.dummy_project_id, self.public_key_dict)

# Test do_post empty body
with patch("requests.post") as mock_request:
mock_request.return_value.ok = False
mock_request.return_value.status_code = 429
mock_request.return_value.json.return_value = {
"errorCode": "E130429",
"errorDescription": "https://docs.descope.com/rate-limit",
"errorMessage": "API rate limit exceeded.",
}
mock_request.return_value.headers = {
API_RATE_LIMIT_RETRY_AFTER_HEADER: "hello"
}
with self.assertRaises(RateLimitException) as cm:
auth.do_post("http://test.com", {}, None, None)
the_exception = cm.exception
self.assertEqual(the_exception.status_code, "E130429")
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(
the_exception.error_description, "https://docs.descope.com/rate-limit"
)
self.assertEqual(the_exception.error_message, "API rate limit exceeded.")
self.assertEqual(
the_exception.rate_limit_parameters,
{API_RATE_LIMIT_RETRY_AFTER_HEADER: 0},
)

def test_api_rate_limit_invalid_response_body(self):
auth = Auth(self.dummy_project_id, self.public_key_dict)

# Test do_post empty body
with patch("requests.post") as mock_request:
mock_request.return_value.ok = False
mock_request.return_value.status_code = 429
mock_request.return_value.json.return_value = "aaa"
with self.assertRaises(RateLimitException) as cm:
auth.do_post("http://test.com", {}, None, None)
the_exception = cm.exception
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.rate_limit_parameters, {})

def test_api_rate_limit_empty_response_body(self):
auth = Auth(self.dummy_project_id, self.public_key_dict)

# Test do_post empty body
with patch("requests.post") as mock_request:
mock_request.return_value.ok = False
mock_request.return_value.status_code = 429
mock_request.return_value.json.return_value = ""
with self.assertRaises(RateLimitException) as cm:
auth.do_post("http://test.com", {}, None, None)
the_exception = cm.exception
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.rate_limit_parameters, {})

def test_api_rate_limit_none_response_body(self):
auth = Auth(self.dummy_project_id, self.public_key_dict)

# Test do_post empty body
with patch("requests.post") as mock_request:
mock_request.return_value.ok = False
mock_request.return_value.status_code = 429
mock_request.return_value.json.return_value = None
with self.assertRaises(RateLimitException) as cm:
auth.do_post("http://test.com", {}, None, None)
the_exception = cm.exception
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
self.assertEqual(the_exception.rate_limit_parameters, {})

def test_raise_from_response(self):
auth = Auth(self.dummy_project_id, self.public_key_dict)
with patch("requests.get") as mock_request:
Expand Down

0 comments on commit e56d018

Please sign in to comment.