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
55import pytest
6+ import requests
67
78import plain2code_exceptions
89from codeplain_REST_api import CodeplainAPI
910
1011
1112def 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