Skip to content

Commit f641002

Browse files
Enhancements to Error Handling and Test Coverage for CheckoutApiException
1 parent d9b9695 commit f641002

File tree

2 files changed

+140
-12
lines changed

2 files changed

+140
-12
lines changed

checkout_sdk/exception.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
class CheckoutException(Exception):
88

99
def __init__(self, message=None):
10+
if message is None:
11+
message = ""
1012
super().__init__(message)
1113

1214

@@ -37,8 +39,15 @@ class CheckoutApiException(CheckoutException):
3739
def __init__(self, response):
3840
self.http_metadata = map_to_http_metadata(response)
3941
if response.text:
40-
payload = response.json()
41-
self.error_details = payload['error_codes'] if 'error_codes' in payload else None
42-
self.error_type = payload['error_type'] if 'error_type' in payload else None
42+
try:
43+
payload = response.json()
44+
self.error_details = payload.get('error_codes')
45+
self.error_type = payload.get('error_type')
46+
except ValueError:
47+
self.error_details = None
48+
self.error_type = None
49+
else:
50+
self.error_details = None
51+
self.error_type = None
4352
super().__init__('The API response status code ({}) does not indicate success.'
4453
.format(response.status_code))

tests/exception_test.py

Lines changed: 128 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,60 @@
77
CheckoutApiException
88
)
99
from checkout_sdk.authorization_type import AuthorizationType
10+
from checkout_sdk.utils import map_to_http_metadata
1011

1112

1213
def test_checkout_exception():
13-
with pytest.raises(CheckoutException):
14+
with pytest.raises(CheckoutException) as exc_info:
1415
raise CheckoutException("Test message")
16+
exception = exc_info.value
17+
assert str(exception) == "Test message"
1518

1619

1720
def test_checkout_argument_exception():
18-
with pytest.raises(CheckoutArgumentException):
21+
with pytest.raises(CheckoutArgumentException) as exc_info:
1922
raise CheckoutArgumentException("Argument error occurred")
23+
exception = exc_info.value
24+
assert str(exception) == "Argument error occurred"
25+
26+
27+
def test_checkout_argument_exception_no_message():
28+
with pytest.raises(CheckoutArgumentException) as exc_info:
29+
raise CheckoutArgumentException()
30+
exception = exc_info.value
31+
assert str(exception) == ""
2032

2133

2234
def test_checkout_authorization_exception():
23-
with pytest.raises(CheckoutAuthorizationException):
35+
with pytest.raises(CheckoutAuthorizationException) as exc_info:
2436
raise CheckoutAuthorizationException("Authorization error occurred")
37+
exception = exc_info.value
38+
assert str(exception) == "Authorization error occurred"
2539

2640

2741
def test_invalid_authorization():
2842
auth_type = Mock(spec=AuthorizationType)
2943
auth_type.name = "SECRET_KEY"
30-
with pytest.raises(CheckoutAuthorizationException, match="SECRET_KEY authorization type"):
44+
with pytest.raises(CheckoutAuthorizationException) as exc_info:
3145
CheckoutAuthorizationException.invalid_authorization(auth_type)
46+
assert "SECRET_KEY authorization type" in str(exc_info.value)
3247

3348

3449
def test_invalid_key():
3550
key_type = Mock(spec=AuthorizationType)
3651
key_type.name = "PUBLIC_KEY"
37-
with pytest.raises(CheckoutAuthorizationException, match="PUBLIC_KEY is required for this operation"):
52+
with pytest.raises(CheckoutAuthorizationException) as exc_info:
3853
CheckoutAuthorizationException.invalid_key(key_type)
54+
assert "PUBLIC_KEY is required for this operation" in str(exc_info.value)
55+
56+
57+
@pytest.mark.parametrize("auth_type_name", ["SECRET_KEY", "PUBLIC_KEY", "CUSTOM_KEY"])
58+
def test_invalid_authorization_various_types(auth_type_name):
59+
auth_type = Mock(spec=AuthorizationType)
60+
auth_type.name = auth_type_name
61+
with pytest.raises(CheckoutAuthorizationException) as exc_info:
62+
CheckoutAuthorizationException.invalid_authorization(auth_type)
63+
assert f"{auth_type_name} authorization type" in str(exc_info.value)
3964

4065

4166
def test_checkout_api_exception():
@@ -47,12 +72,106 @@ def test_checkout_api_exception():
4772
"error_codes": ["invalid_field"]
4873
}
4974

50-
exception_instance = CheckoutApiException(response)
51-
5275
with pytest.raises(CheckoutApiException) as exc_info:
53-
raise exception_instance
54-
76+
raise CheckoutApiException(response)
5577
exception = exc_info.value
5678
assert exception.http_metadata.status_code == 400
5779
assert exception.error_type == "request_invalid"
5880
assert exception.error_details == ["invalid_field"]
81+
82+
83+
def test_checkout_api_exception_without_error_details():
84+
response = Mock()
85+
response.status_code = 500
86+
response.text = '{"message": "Internal Server Error"}'
87+
response.json.return_value = {
88+
"message": "Internal Server Error"
89+
}
90+
91+
with pytest.raises(CheckoutApiException) as exc_info:
92+
raise CheckoutApiException(response)
93+
exception = exc_info.value
94+
assert exception.http_metadata.status_code == 500
95+
assert exception.error_type is None
96+
assert exception.error_details is None
97+
98+
99+
def test_checkout_api_exception_empty_response():
100+
response = Mock()
101+
response.status_code = 404
102+
response.text = ''
103+
response.json.return_value = {}
104+
105+
with pytest.raises(CheckoutApiException) as exc_info:
106+
raise CheckoutApiException(response)
107+
exception = exc_info.value
108+
assert exception.http_metadata.status_code == 404
109+
assert exception.error_type is None
110+
assert exception.error_details is None
111+
112+
113+
def test_checkout_api_exception_non_json_response():
114+
response = Mock()
115+
response.status_code = 502
116+
response.text = 'Bad Gateway'
117+
response.json.side_effect = ValueError("No JSON object could be decoded")
118+
119+
with pytest.raises(CheckoutApiException) as exc_info:
120+
raise CheckoutApiException(response)
121+
exception = exc_info.value
122+
assert exception.http_metadata.status_code == 502
123+
assert exception.error_type is None
124+
assert exception.error_details is None
125+
126+
127+
@pytest.mark.parametrize("status_code", [400, 401, 403, 404, 500])
128+
def test_checkout_api_exception_various_status_codes(status_code):
129+
response = Mock()
130+
response.status_code = status_code
131+
response.text = ''
132+
response.json.return_value = {}
133+
134+
with pytest.raises(CheckoutApiException) as exc_info:
135+
raise CheckoutApiException(response)
136+
exception = exc_info.value
137+
assert exception.http_metadata.status_code == status_code
138+
139+
140+
def test_map_to_http_metadata():
141+
response = Mock()
142+
response.status_code = 200
143+
response.headers = {'Content-Type': 'application/json'}
144+
145+
metadata = map_to_http_metadata(response)
146+
assert metadata.status_code == 200
147+
assert metadata.headers == {'Content-Type': 'application/json'}
148+
149+
150+
def test_checkout_api_exception_message():
151+
response = Mock()
152+
response.status_code = 400
153+
response.text = '{"error_type": "invalid_request", "error_codes": ["bad_request"]}'
154+
response.json.return_value = {
155+
"error_type": "invalid_request",
156+
"error_codes": ["bad_request"]
157+
}
158+
159+
with pytest.raises(CheckoutApiException) as exc_info:
160+
raise CheckoutApiException(response)
161+
exception = exc_info.value
162+
expected_message = "The API response status code (400) does not indicate success."
163+
assert str(exception) == expected_message
164+
165+
166+
def test_checkout_api_exception_no_response_text():
167+
response = Mock()
168+
response.status_code = 400
169+
response.text = None
170+
response.json.return_value = {}
171+
172+
with pytest.raises(CheckoutApiException) as exc_info:
173+
raise CheckoutApiException(response)
174+
exception = exc_info.value
175+
assert exception.http_metadata.status_code == 400
176+
assert exception.error_type is None
177+
assert exception.error_details is None

0 commit comments

Comments
 (0)