diff --git a/checkout_sdk/exception.py b/checkout_sdk/exception.py index 32c5aee..4c0419b 100644 --- a/checkout_sdk/exception.py +++ b/checkout_sdk/exception.py @@ -7,6 +7,8 @@ class CheckoutException(Exception): def __init__(self, message=None): + if message is None: + message = "" super().__init__(message) @@ -37,8 +39,15 @@ class CheckoutApiException(CheckoutException): def __init__(self, response): self.http_metadata = map_to_http_metadata(response) if response.text: - payload = response.json() - self.error_details = payload['error_codes'] if 'error_codes' in payload else None - self.error_type = payload['error_type'] if 'error_type' in payload else None + try: + payload = response.json() + self.error_details = payload.get('error_codes') + self.error_type = payload.get('error_type') + except ValueError: + self.error_details = None + self.error_type = None + else: + self.error_details = None + self.error_type = None super().__init__('The API response status code ({}) does not indicate success.' .format(response.status_code)) diff --git a/tests/exception_test.py b/tests/exception_test.py index c81213d..6ee5d7b 100644 --- a/tests/exception_test.py +++ b/tests/exception_test.py @@ -7,35 +7,60 @@ CheckoutApiException ) from checkout_sdk.authorization_type import AuthorizationType +from checkout_sdk.utils import map_to_http_metadata def test_checkout_exception(): - with pytest.raises(CheckoutException): + with pytest.raises(CheckoutException) as exc_info: raise CheckoutException("Test message") + exception = exc_info.value + assert str(exception) == "Test message" def test_checkout_argument_exception(): - with pytest.raises(CheckoutArgumentException): + with pytest.raises(CheckoutArgumentException) as exc_info: raise CheckoutArgumentException("Argument error occurred") + exception = exc_info.value + assert str(exception) == "Argument error occurred" + + +def test_checkout_argument_exception_no_message(): + with pytest.raises(CheckoutArgumentException) as exc_info: + raise CheckoutArgumentException() + exception = exc_info.value + assert str(exception) == "" def test_checkout_authorization_exception(): - with pytest.raises(CheckoutAuthorizationException): + with pytest.raises(CheckoutAuthorizationException) as exc_info: raise CheckoutAuthorizationException("Authorization error occurred") + exception = exc_info.value + assert str(exception) == "Authorization error occurred" def test_invalid_authorization(): auth_type = Mock(spec=AuthorizationType) auth_type.name = "SECRET_KEY" - with pytest.raises(CheckoutAuthorizationException, match="SECRET_KEY authorization type"): + with pytest.raises(CheckoutAuthorizationException) as exc_info: CheckoutAuthorizationException.invalid_authorization(auth_type) + assert "SECRET_KEY authorization type" in str(exc_info.value) def test_invalid_key(): key_type = Mock(spec=AuthorizationType) key_type.name = "PUBLIC_KEY" - with pytest.raises(CheckoutAuthorizationException, match="PUBLIC_KEY is required for this operation"): + with pytest.raises(CheckoutAuthorizationException) as exc_info: CheckoutAuthorizationException.invalid_key(key_type) + assert "PUBLIC_KEY is required for this operation" in str(exc_info.value) + + +@pytest.mark.parametrize("auth_type_name", ["SECRET_KEY", "PUBLIC_KEY", "CUSTOM_KEY"]) +def test_invalid_authorization_various_types(auth_type_name): + auth_type = Mock(spec=AuthorizationType) + auth_type.name = auth_type_name + with pytest.raises(CheckoutAuthorizationException) as exc_info: + CheckoutAuthorizationException.invalid_authorization(auth_type) + assert f"{auth_type_name} authorization type" in str(exc_info.value) def test_checkout_api_exception(): @@ -47,12 +72,106 @@ def test_checkout_api_exception(): "error_codes": ["invalid_field"] } - exception_instance = CheckoutApiException(response) - with pytest.raises(CheckoutApiException) as exc_info: - raise exception_instance - + raise CheckoutApiException(response) exception = exc_info.value assert exception.http_metadata.status_code == 400 assert exception.error_type == "request_invalid" assert exception.error_details == ["invalid_field"] + + +def test_checkout_api_exception_without_error_details(): + response = Mock() + response.status_code = 500 + response.text = '{"message": "Internal Server Error"}' + response.json.return_value = { + "message": "Internal Server Error" + } + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + assert exception.http_metadata.status_code == 500 + assert exception.error_type is None + assert exception.error_details is None + + +def test_checkout_api_exception_empty_response(): + response = Mock() + response.status_code = 404 + response.text = '' + response.json.return_value = {} + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + assert exception.http_metadata.status_code == 404 + assert exception.error_type is None + assert exception.error_details is None + + +def test_checkout_api_exception_non_json_response(): + response = Mock() + response.status_code = 502 + response.text = 'Bad Gateway' + response.json.side_effect = ValueError("No JSON object could be decoded") + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + assert exception.http_metadata.status_code == 502 + assert exception.error_type is None + assert exception.error_details is None + + +@pytest.mark.parametrize("status_code", [400, 401, 403, 404, 500]) +def test_checkout_api_exception_various_status_codes(status_code): + response = Mock() + response.status_code = status_code + response.text = '' + response.json.return_value = {} + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + assert exception.http_metadata.status_code == status_code + + +def test_map_to_http_metadata(): + response = Mock() + response.status_code = 200 + response.headers = {'Content-Type': 'application/json'} + + metadata = map_to_http_metadata(response) + assert metadata.status_code == 200 + assert metadata.headers == {'Content-Type': 'application/json'} + + +def test_checkout_api_exception_message(): + response = Mock() + response.status_code = 400 + response.text = '{"error_type": "invalid_request", "error_codes": ["bad_request"]}' + response.json.return_value = { + "error_type": "invalid_request", + "error_codes": ["bad_request"] + } + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + expected_message = "The API response status code (400) does not indicate success." + assert str(exception) == expected_message + + +def test_checkout_api_exception_no_response_text(): + response = Mock() + response.status_code = 400 + response.text = None + response.json.return_value = {} + + with pytest.raises(CheckoutApiException) as exc_info: + raise CheckoutApiException(response) + exception = exc_info.value + assert exception.http_metadata.status_code == 400 + assert exception.error_type is None + assert exception.error_details is None