Skip to content

Commit fa34ff5

Browse files
committed
Read the server's error code on every status it declines on
The mapping was consulted only on 400. The server also returns an error code with 500 - InternalServerError among them - so those reached the user as "500 Server Error: INTERNAL SERVER ERROR for url: <endpoint>", with the internal path and none of the message the code was paired with. The instruction to send the log to support has never printed.
1 parent b6fbdbd commit fa34ff5

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

‎codeplain_REST_api.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ def post_request(
143143
self.console.debug(f"Failed to decode JSON response: {e}. Response text: {response.text}")
144144
raise Exception(f"Error rendering plain code: Failed to decode API response ({e}).\n") from e
145145

146-
if response.status_code == requests.codes.bad_request and "error_code" in response_json:
146+
# Any status the server declines on, not only 400. The server returns
147+
# error_code with 500 as well, and reading it only on 400 meant those
148+
# reached the user as the raw "500 Server Error for url: ..." rather than
149+
# the message the code was paired with.
150+
if not response.ok and "error_code" in response_json:
147151
self._raise_for_error_code(response_json)
148152

149153
response.raise_for_status()

‎tests/test_rest_error_codes.py‎

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Tests for mapping API error codes onto typed client exceptions."""
22

3-
from unittest.mock import MagicMock
3+
from unittest.mock import MagicMock, patch
44

55
import pytest
6+
import requests
67

78
import plain2code_exceptions
89
from codeplain_REST_api import CodeplainAPI
910

1011

1112
def test_conformance_fix_exhaustion_maps_to_its_typed_exception():
12-
"""The server reports fix-attempt exhaustion as a structured 400; the client raises
13+
"""The server reports fix-attempt exhaustion as a structured error; the client raises
1314
the matching typed exception so the render fails with the server's message instead
1415
of a raw HTTP error."""
1516
api = CodeplainAPI(api_key="test-key", console=MagicMock())
@@ -35,3 +36,49 @@ def test_the_exhaustion_error_is_expected_rather_than_a_crash():
3536
from plain2code import EXPECTED_EXCEPTIONS
3637

3738
assert plain2code_exceptions.ConformanceTestsFixExhausted in EXPECTED_EXCEPTIONS
39+
40+
41+
class TestErrorCodesAreReadOnEveryDeclinedStatus:
42+
"""The client used to consult `error_code` only on 400, so every structured error the
43+
server returns with a 500 - its own `InternalServerError` among them - reached the user
44+
as `500 Server Error: INTERNAL SERVER ERROR for url: <internal endpoint>`. The message
45+
the code was paired with never printed."""
46+
47+
@staticmethod
48+
def _api_returning(status, body):
49+
response = MagicMock()
50+
response.status_code = status
51+
response.ok = 200 <= status < 300
52+
response.json.return_value = body
53+
response.raise_for_status.side_effect = requests.exceptions.HTTPError(f"{status} Server Error")
54+
55+
api = CodeplainAPI(api_key="test-key", console=MagicMock())
56+
api.api_url = "https://api.example"
57+
return api, response
58+
59+
def test_a_500_carrying_an_error_code_raises_the_typed_exception(self):
60+
api, response = self._api_returning(
61+
500,
62+
{
63+
"error_code": "ConformanceTestsFixExhausted",
64+
"message": "The conformance tests for functionality 1 still failed.",
65+
},
66+
)
67+
68+
with patch("requests.post", return_value=response):
69+
with pytest.raises(plain2code_exceptions.ConformanceTestsFixExhausted, match="still failed"):
70+
api.post_request("https://api.example/x", {}, {}, None, num_retries=0)
71+
72+
def test_the_generic_server_error_reaches_the_user_as_its_own_message(self):
73+
api, response = self._api_returning(500, {"error_code": "InternalServerError", "message": "boom"})
74+
75+
with patch("requests.post", return_value=response):
76+
with pytest.raises(plain2code_exceptions.InternalServerError, match="support@codeplain.ai"):
77+
api.post_request("https://api.example/x", {}, {}, None, num_retries=0)
78+
79+
def test_a_success_is_returned_untouched(self):
80+
api, response = self._api_returning(200, {"result": "ok"})
81+
response.raise_for_status.side_effect = None
82+
83+
with patch("requests.post", return_value=response):
84+
assert api.post_request("https://api.example/x", {}, {}, None, num_retries=0) == {"result": "ok"}

0 commit comments

Comments
 (0)