diff --git a/hubspot/connection/__init__.py b/hubspot/connection/__init__.py index f43f40b..0b33c2d 100644 --- a/hubspot/connection/__init__.py +++ b/hubspot/connection/__init__.py @@ -29,7 +29,7 @@ from requests.adapters import HTTPAdapter from requests.auth import AuthBase from requests.sessions import Session -from voluptuous import Schema +from voluptuous import Optional, Schema from hubspot.connection._validators import Constant from hubspot.connection.exc import HubspotAuthenticationError @@ -48,6 +48,7 @@ 'status': Constant('error'), 'message': unicode, 'requestId': unicode, + Optional('failureMessages'): list, }, required=True, extra=True, @@ -204,6 +205,8 @@ def _require_successful_response(response): raise exception_class( error_data['message'], error_data['requestId'], + error_data, + response.status_code, ) elif 500 <= response.status_code < 600: raise HubspotServerError(response.reason, response.status_code) diff --git a/hubspot/connection/exc.py b/hubspot/connection/exc.py index 2732fac..b1afe05 100644 --- a/hubspot/connection/exc.py +++ b/hubspot/connection/exc.py @@ -29,13 +29,23 @@ class HubspotClientError(HubspotException): HubSpot deemed the request invalid. This represents an HTTP response code of 40X, except 401 + :param unicode error_message: The error message returned by HubSpot :param unicode request_id: + :param dict optional error_data: The response data returned by HubSpot + :param int optional http_status_code: """ - def __init__(self, msg, request_id): + def __init__(self, error_message, request_id, error_data=None, http_status_code=None): + if error_data and 'failureMessages' in error_data: + msg = u'{0} ({1!s})'.format(error_message, error_data['failureMessages']) + else: + msg = error_message super(HubspotClientError, self).__init__(msg) + self.http_status_code = http_status_code + self.error_message = error_message self.request_id = request_id + self.error_data = error_data class HubspotAuthenticationError(HubspotClientError): diff --git a/hubspot/connection/testing.py b/hubspot/connection/testing.py index dfc1379..8ab5175 100644 --- a/hubspot/connection/testing.py +++ b/hubspot/connection/testing.py @@ -64,9 +64,8 @@ def __exit__(self, exc_type, exc_value, traceback): return expected_api_call_count = len(self._expected_api_calls) - pending_api_call_count = expected_api_call_count - self._request_count error_message = \ - '{} more requests were expected'.format(pending_api_call_count) + '{} requests were expected, but only {} calls were received'.format(expected_api_call_count, self._request_count) assert expected_api_call_count == self._request_count, error_message def send_get_request(self, url_path, query_string_args=None): diff --git a/tests/test_connection.py b/tests/test_connection.py index b1d6890..a42fce5 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -244,12 +244,25 @@ def test_server_error_response(self): def test_client_error_response(self): request_id = get_uuid4_str() - error_message = 'Json node is missing child property' + error_message = 'Errors found processing batch update' + failure_messages = [ + { + 'index': 0, + 'error': { + 'status': 'error', + 'message': 'Email address is invalid', + } + } + ] + invalid_emails = [''] body_deserialization = { 'status': 'error', 'message': error_message, + 'correlationId': '2ebc27ce-cc2d-4b81-99f3-01aa96e05206', + 'invalidEmails': invalid_emails, + 'failureMessages': failure_messages, 'requestId': request_id, - } + } response_data_maker = _ResponseMaker(400, body_deserialization) connection = _MockPortalConnection(response_data_maker) @@ -258,7 +271,28 @@ def test_client_error_response(self): exception = context_manager.exception eq_(request_id, exception.request_id) + eq_("Errors found processing batch update " + "([{u'index': 0, u'error': " + "{u'status': u'error'," + " u'message': u'Email address is invalid'}}])", + str(exception)) + eq_(error_message, exception.error_message) + eq_(invalid_emails, exception.error_data['invalidEmails']) + eq_(failure_messages, exception.error_data['failureMessages']) + eq_(400, exception.http_status_code) + + def test_str_client_exception_without_failure_messages(self): + request_id = get_uuid4_str() + error_message = 'Json node is missing child property' + error_data = { + 'status': 'error', + 'message': error_message, + 'requestId': request_id, + } + + exception = HubspotClientError(error_message, request_id, error_data) eq_(error_message, str(exception)) + eq_(error_message, exception.error_message) class TestAuthentication(object): diff --git a/tests/test_testing_utils.py b/tests/test_testing_utils.py index 3d42928..e735401 100644 --- a/tests/test_testing_utils.py +++ b/tests/test_testing_utils.py @@ -171,7 +171,7 @@ def test_multiple_api_calls(self): def test_unsuccessful_api_call(self): exception = \ - HubspotAuthenticationError('Must authenticate', get_uuid4_str()) + HubspotAuthenticationError('Must authenticate', get_uuid4_str(), {}) expected_api_call = UnsuccessfulAPICall( _STUB_URL_PATH, 'GET', @@ -189,7 +189,7 @@ def test_unsuccessful_api_call(self): def test_too_few_requests(self): connection = \ self._make_connection_for_expected_api_call(_STUB_API_CALL_1) - error_message = '1 more requests were expected' + error_message = '1 requests were expected, but only 0 calls were received' with assert_raises_substring(AssertionError, error_message): with connection: # Do not make any requests in the connection