Skip to content

Commit

Permalink
fix: Fix handling responses with invalid JSON (box/box-codegen#667) (#…
Browse files Browse the repository at this point in the history
…485)

Closes: #470
  • Loading branch information
box-sdk-build authored Feb 20, 2025
1 parent bd7fefa commit 46399d8
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .codegen.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "engineHash": "8a9cc1d", "specHash": "f20ba3f", "version": "1.11.1" }
{ "engineHash": "22f85cc", "specHash": "f20ba3f", "version": "1.11.1" }
21 changes: 11 additions & 10 deletions box_sdk_gen/networking/box_network_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ def fetch(self, options: 'FetchOptions') -> FetchResponse:
url=network_response.url,
status=network_response.status_code,
headers=dict(response.network_response.headers),
data=(
json_to_serialized_data(network_response.text)
if network_response.text
else None
),
data=(self._read_json_body(network_response.text)),
content=io.BytesIO(network_response.content),
)

Expand Down Expand Up @@ -254,11 +250,7 @@ def _raise_on_unsuccessful_request(
)

network_response = response.network_response

try:
response_json = network_response.json()
except ValueError:
response_json = {}
response_json = BoxNetworkClient._read_json_body(network_response.text)

raise BoxAPIError(
message=f'{network_response.status_code} {response_json.get("message", "")}; Request ID: {response_json.get("request_id", "")}',
Expand Down Expand Up @@ -307,6 +299,15 @@ def _validate_seekable(stream: ByteStream, raised_exception: Optional[Exception]
error=raised_exception,
)

@staticmethod
def _read_json_body(response_body: str) -> dict:
if not response_body:
return {}
try:
return json_to_serialized_data(response_body)
except (ValueError, TypeError):
return {}

def _reset_stream(
self,
stream: ByteStream,
Expand Down
61 changes: 52 additions & 9 deletions test/box_network_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Authentication,
BoxSDKError,
BoxClient,
ResponseFormat,
)
from box_sdk_gen.networking.box_network_client import (
BoxNetworkClient,
Expand Down Expand Up @@ -467,7 +468,7 @@ def test_fetch_get_json_format_response_success(
method="get",
url="https://example.com",
network_session=network_session_mock,
response_format="json",
response_format=ResponseFormat.JSON,
)
)

Expand All @@ -488,7 +489,7 @@ def test_fetch_get_binary_format_response_success(
method="get",
url="https://example.com",
network_session=network_session_mock,
response_format="binary",
response_format=ResponseFormat.BINARY,
)
)

Expand Down Expand Up @@ -526,6 +527,37 @@ def test_retryable_status_codes(
assert mock_requests_session.request.call_count == 3


@pytest.mark.parametrize("retryable_status_code", [429, 500, 503])
def test_retryable_status_codes_with_invalid__response_body(
network_client,
mock_requests_session,
network_session_mock,
response_200,
retryable_status_code,
response_failure_no_status,
):
response_failure_no_status.status_code = retryable_status_code
response_failure_no_status.text = 'Invalid JSON'
response_200.text = '{"id": "123456"}'
mock_requests_session.request.side_effect = [
response_failure_no_status,
response_failure_no_status,
response_200,
]

fetch_response = network_client.fetch(
FetchOptions(
method="get",
url="https://example.com",
network_session=network_session_mock,
response_format=ResponseFormat.JSON,
)
)
assert fetch_response.status == 200
assert fetch_response.data == {"id": "123456"}
assert mock_requests_session.request.call_count == 3


def test_status_code_202_with_no_retry_after_header(
network_client, mock_requests_session, network_session_mock, response_202
):
Expand All @@ -539,7 +571,7 @@ def test_status_code_202_with_no_retry_after_header(
)
)
assert fetch_response.status == 202
assert fetch_response.data == None
assert fetch_response.data == {}


def test_retryable_status_code_202(
Expand Down Expand Up @@ -590,7 +622,7 @@ def test_202_should_be_returned_if_retry_limit_is_reached(
)

assert fetch_response.status == 202
assert fetch_response.data == None
assert fetch_response.data == {}


@pytest.mark.parametrize("not_retryable_status_code", [404, 403, 400])
Expand Down Expand Up @@ -871,15 +903,13 @@ def test_raising_api_error_with_valid_json_body(network_client):
assert e.name == "BoxAPIError"


def test_raising_api_error_without_valid_json_body(network_client):
@pytest.mark.parametrize('response_body', ['', 'Invalid json', 123])
def test_raising_api_error_without_valid_json_body(network_client, response_body):
client_error_response = Mock(Response)
client_error_response.status_code = 400
client_error_response.ok = False
client_error_response.headers = {}
client_error_response.text = ""
client_error_response.json.side_effect = json.JSONDecodeError(
"Expecting value: line 1 column 1 (char 0)", "", 0
)
client_error_response.text = response_body

request = APIRequest(
method="POST",
Expand Down Expand Up @@ -960,6 +990,19 @@ def test_proxy_config():
assert requests_session.proxies["https"] == "http://user:[email protected]:3128/"


@pytest.mark.parametrize(
"response_body, expected_json",
[
('', {}),
('Invalid json', {}),
(123, {}),
('{"name": "John"}', {'name': 'John'}),
],
)
def test_read_json_body(response_body, expected_json):
assert BoxNetworkClient._read_json_body(response_body) == expected_json


def test_get_options_stream_position(network_client, mock_byte_stream):
mock_byte_stream.seek(1)
options = FetchOptions(
Expand Down

0 comments on commit 46399d8

Please sign in to comment.