Skip to content

Commit e56d018

Browse files
authored
Fix rate limit parsing when response's body is empty (#491)
1 parent 8785e35 commit e56d018

File tree

3 files changed

+110
-13
lines changed

3 files changed

+110
-13
lines changed

descope/auth.py

+28-12
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,34 @@ def __init__(
105105
self.public_keys = {kid: (pub_key, alg)}
106106

107107
def _raise_rate_limit_exception(self, response):
108-
resp = response.json()
109-
raise RateLimitException(
110-
resp.get("errorCode", HTTPStatus.TOO_MANY_REQUESTS),
111-
ERROR_TYPE_API_RATE_LIMIT,
112-
resp.get("errorDescription", ""),
113-
resp.get("errorMessage", ""),
114-
rate_limit_parameters={
115-
API_RATE_LIMIT_RETRY_AFTER_HEADER: int(
116-
response.headers.get(API_RATE_LIMIT_RETRY_AFTER_HEADER, 0)
117-
)
118-
},
119-
)
108+
try:
109+
resp = response.json()
110+
raise RateLimitException(
111+
resp.get("errorCode", HTTPStatus.TOO_MANY_REQUESTS),
112+
ERROR_TYPE_API_RATE_LIMIT,
113+
resp.get("errorDescription", ""),
114+
resp.get("errorMessage", ""),
115+
rate_limit_parameters={
116+
API_RATE_LIMIT_RETRY_AFTER_HEADER: self._parse_retry_after(
117+
response.headers
118+
)
119+
},
120+
)
121+
except RateLimitException:
122+
raise
123+
except Exception as e:
124+
raise RateLimitException(
125+
status_code=HTTPStatus.TOO_MANY_REQUESTS,
126+
error_type=ERROR_TYPE_API_RATE_LIMIT,
127+
error_message=ERROR_TYPE_API_RATE_LIMIT,
128+
error_description=ERROR_TYPE_API_RATE_LIMIT,
129+
)
130+
131+
def _parse_retry_after(self, headers):
132+
try:
133+
return int(headers.get(API_RATE_LIMIT_RETRY_AFTER_HEADER, 0))
134+
except (ValueError, TypeError):
135+
return 0
120136

121137
def do_get(
122138
self,

tests/management/test_user.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ def test_search_all(self):
965965
verify=True,
966966
timeout=DEFAULT_TIMEOUT_SECONDS,
967967
)
968-
968+
969969
# Test success flow with time parameters
970970
with patch("requests.post") as mock_post:
971971
network_resp = mock.Mock()

tests/test_auth.py

+81
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from http import HTTPStatus
12
import json
23
import os
34
import unittest
@@ -678,6 +679,86 @@ def test_api_rate_limit_exception(self):
678679
{API_RATE_LIMIT_RETRY_AFTER_HEADER: 10},
679680
)
680681

682+
def test_api_rate_limit_invalid_header(self):
683+
auth = Auth(self.dummy_project_id, self.public_key_dict)
684+
685+
# Test do_post empty body
686+
with patch("requests.post") as mock_request:
687+
mock_request.return_value.ok = False
688+
mock_request.return_value.status_code = 429
689+
mock_request.return_value.json.return_value = {
690+
"errorCode": "E130429",
691+
"errorDescription": "https://docs.descope.com/rate-limit",
692+
"errorMessage": "API rate limit exceeded.",
693+
}
694+
mock_request.return_value.headers = {
695+
API_RATE_LIMIT_RETRY_AFTER_HEADER: "hello"
696+
}
697+
with self.assertRaises(RateLimitException) as cm:
698+
auth.do_post("http://test.com", {}, None, None)
699+
the_exception = cm.exception
700+
self.assertEqual(the_exception.status_code, "E130429")
701+
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
702+
self.assertEqual(
703+
the_exception.error_description, "https://docs.descope.com/rate-limit"
704+
)
705+
self.assertEqual(the_exception.error_message, "API rate limit exceeded.")
706+
self.assertEqual(
707+
the_exception.rate_limit_parameters,
708+
{API_RATE_LIMIT_RETRY_AFTER_HEADER: 0},
709+
)
710+
711+
def test_api_rate_limit_invalid_response_body(self):
712+
auth = Auth(self.dummy_project_id, self.public_key_dict)
713+
714+
# Test do_post empty body
715+
with patch("requests.post") as mock_request:
716+
mock_request.return_value.ok = False
717+
mock_request.return_value.status_code = 429
718+
mock_request.return_value.json.return_value = "aaa"
719+
with self.assertRaises(RateLimitException) as cm:
720+
auth.do_post("http://test.com", {}, None, None)
721+
the_exception = cm.exception
722+
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
723+
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
724+
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
725+
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
726+
self.assertEqual(the_exception.rate_limit_parameters, {})
727+
728+
def test_api_rate_limit_empty_response_body(self):
729+
auth = Auth(self.dummy_project_id, self.public_key_dict)
730+
731+
# Test do_post empty body
732+
with patch("requests.post") as mock_request:
733+
mock_request.return_value.ok = False
734+
mock_request.return_value.status_code = 429
735+
mock_request.return_value.json.return_value = ""
736+
with self.assertRaises(RateLimitException) as cm:
737+
auth.do_post("http://test.com", {}, None, None)
738+
the_exception = cm.exception
739+
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
740+
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
741+
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
742+
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
743+
self.assertEqual(the_exception.rate_limit_parameters, {})
744+
745+
def test_api_rate_limit_none_response_body(self):
746+
auth = Auth(self.dummy_project_id, self.public_key_dict)
747+
748+
# Test do_post empty body
749+
with patch("requests.post") as mock_request:
750+
mock_request.return_value.ok = False
751+
mock_request.return_value.status_code = 429
752+
mock_request.return_value.json.return_value = None
753+
with self.assertRaises(RateLimitException) as cm:
754+
auth.do_post("http://test.com", {}, None, None)
755+
the_exception = cm.exception
756+
self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS)
757+
self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT)
758+
self.assertEqual(the_exception.error_description, ERROR_TYPE_API_RATE_LIMIT)
759+
self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT)
760+
self.assertEqual(the_exception.rate_limit_parameters, {})
761+
681762
def test_raise_from_response(self):
682763
auth = Auth(self.dummy_project_id, self.public_key_dict)
683764
with patch("requests.get") as mock_request:

0 commit comments

Comments
 (0)