utils/soap: add wrapping of zeep errors inside APIError (#58925)
This commit is contained in:
parent
32d647583f
commit
1ec9dbcb13
|
@ -79,6 +79,7 @@ class SOAPConnector(BaseResource, HTTPResource):
|
||||||
settings=zeep.Settings(
|
settings=zeep.Settings(
|
||||||
strict=self.zeep_strict, xsd_ignore_sequence_order=self.zeep_xsd_ignore_sequence_order
|
strict=self.zeep_strict, xsd_ignore_sequence_order=self.zeep_xsd_ignore_sequence_order
|
||||||
),
|
),
|
||||||
|
api_error=True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ class Operation:
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
def __call__(self, resource, request_data=None):
|
def __call__(self, resource, request_data=None):
|
||||||
client = resource.soap_client()
|
client = resource.soap_client(api_error=True)
|
||||||
|
|
||||||
serialized_request = ''
|
serialized_request = ''
|
||||||
if self.request_converter:
|
if self.request_converter:
|
||||||
|
|
|
@ -19,6 +19,9 @@ from urllib import parse as urlparse
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
from zeep.cache import InMemoryCache
|
from zeep.cache import InMemoryCache
|
||||||
|
from zeep.exceptions import Error as ZeepError
|
||||||
|
from zeep.exceptions import Fault, TransportError
|
||||||
|
from zeep.proxy import OperationProxy, ServiceProxy
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
|
|
||||||
from passerelle.utils.jsonresponse import APIError
|
from passerelle.utils.jsonresponse import APIError
|
||||||
|
@ -28,6 +31,48 @@ class SOAPError(APIError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SOAPServiceUnreachable(SOAPError):
|
||||||
|
def __init__(self, client, exception):
|
||||||
|
super().__init__(
|
||||||
|
f'SOAP service at {client.wsdl.location} is unreachable. Please contact its administrator',
|
||||||
|
data={
|
||||||
|
'wsdl': client.wsdl.location,
|
||||||
|
'status_code': exception.status_code,
|
||||||
|
'error': str(exception),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SOAPFault(SOAPError):
|
||||||
|
def __init__(self, client, fault):
|
||||||
|
super().__init__(
|
||||||
|
f'SOAP service at {client.wsdl.location} returned an error "{fault.message or fault.code}"',
|
||||||
|
data={
|
||||||
|
'soap_fault': fault.__dict__,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OperationProxyWrapper(OperationProxy):
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
client = self._proxy._client
|
||||||
|
try:
|
||||||
|
return super().__call__(*args, **kwargs)
|
||||||
|
except TransportError as transport_error:
|
||||||
|
raise SOAPServiceUnreachable(client, transport_error)
|
||||||
|
except Fault as fault:
|
||||||
|
raise SOAPFault(client, fault)
|
||||||
|
except ZeepError as zeep_error:
|
||||||
|
raise SOAPError(str(zeep_error))
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceProxyWrapper(ServiceProxy):
|
||||||
|
def __getitem__(self, key):
|
||||||
|
operation = super().__getitem__(key)
|
||||||
|
operation.__class__ = OperationProxyWrapper
|
||||||
|
return operation
|
||||||
|
|
||||||
|
|
||||||
class SOAPClient(Client):
|
class SOAPClient(Client):
|
||||||
"""Wrapper around zeep.Client
|
"""Wrapper around zeep.Client
|
||||||
|
|
||||||
|
@ -36,6 +81,7 @@ class SOAPClient(Client):
|
||||||
|
|
||||||
def __init__(self, resource, **kwargs):
|
def __init__(self, resource, **kwargs):
|
||||||
wsdl_url = kwargs.pop('wsdl_url', None) or resource.wsdl_url
|
wsdl_url = kwargs.pop('wsdl_url', None) or resource.wsdl_url
|
||||||
|
self.api_error = kwargs.pop('api_error', False)
|
||||||
transport_kwargs = kwargs.pop('transport_kwargs', {})
|
transport_kwargs = kwargs.pop('transport_kwargs', {})
|
||||||
transport_class = getattr(resource, 'soap_transport_class', SOAPTransport)
|
transport_class = getattr(resource, 'soap_transport_class', SOAPTransport)
|
||||||
transport = transport_class(
|
transport = transport_class(
|
||||||
|
@ -43,6 +89,13 @@ class SOAPClient(Client):
|
||||||
)
|
)
|
||||||
super().__init__(wsdl_url, transport=transport, **kwargs)
|
super().__init__(wsdl_url, transport=transport, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service(self):
|
||||||
|
service = super().service
|
||||||
|
if self.api_error:
|
||||||
|
service.__class__ = ServiceProxyWrapper
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
class ResponseFixContentWrapper:
|
class ResponseFixContentWrapper:
|
||||||
def __init__(self, response):
|
def __init__(self, response):
|
||||||
|
|
|
@ -139,6 +139,7 @@ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
'greeting': 'Hello',
|
'greeting': 'Hello',
|
||||||
'who': 'John!',
|
'who': 'John!',
|
||||||
}
|
}
|
||||||
|
VALIDATION_ERROR = 'Missing element firstName (sayHello.firstName)'
|
||||||
|
|
||||||
|
|
||||||
class SOAP12(SOAP11):
|
class SOAP12(SOAP11):
|
||||||
|
@ -254,6 +255,7 @@ class SOAP12(SOAP11):
|
||||||
'greeting': 'Hello',
|
'greeting': 'Hello',
|
||||||
'who': ['John!'],
|
'who': ['John!'],
|
||||||
}
|
}
|
||||||
|
VALIDATION_ERROR = 'Expected at least 1 items (minOccurs check) 0 items found. (sayHello.firstName)'
|
||||||
|
|
||||||
|
|
||||||
class BrokenSOAP12(SOAP12):
|
class BrokenSOAP12(SOAP12):
|
||||||
|
@ -301,13 +303,13 @@ def test_schemas(connector, soap):
|
||||||
assert list(connector.operations_and_schemas) == [('sayHello', soap.INPUT_SCHEMA, soap.OUTPUT_SCHEMA)]
|
assert list(connector.operations_and_schemas) == [('sayHello', soap.INPUT_SCHEMA, soap.OUTPUT_SCHEMA)]
|
||||||
|
|
||||||
|
|
||||||
def test_say_hello_method_validation_error(connector, app):
|
def test_say_hello_method_validation_error(connector, soap, app):
|
||||||
resp = app.get('/soap/test/method/sayHello/', status=500)
|
resp = app.get('/soap/test/method/sayHello/')
|
||||||
assert dict(resp.json, err_desc=None) == {
|
assert resp.json == {
|
||||||
'err': 1,
|
"err": 1,
|
||||||
'err_class': 'zeep.exceptions.ValidationError',
|
"err_class": "passerelle.utils.soap.SOAPError",
|
||||||
'err_desc': None,
|
"err_desc": soap.VALIDATION_ERROR,
|
||||||
'data': None,
|
"data": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,10 @@ import pytest
|
||||||
import requests
|
import requests
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from zeep import Settings
|
from zeep import Settings
|
||||||
from zeep.exceptions import TransportError, XMLParseError
|
from zeep.exceptions import Fault, TransportError, XMLParseError
|
||||||
from zeep.plugins import Plugin
|
from zeep.plugins import Plugin
|
||||||
|
|
||||||
|
from passerelle.utils.jsonresponse import APIError
|
||||||
from passerelle.utils.soap import SOAPClient
|
from passerelle.utils.soap import SOAPClient
|
||||||
|
|
||||||
WSDL = 'tests/data/soap.wsdl'
|
WSDL = 'tests/data/soap.wsdl'
|
||||||
|
@ -107,3 +108,30 @@ def test_remove_first_bytes_for_xml(mocked_post):
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
assert result['skipMe'] == 1.2
|
assert result['skipMe'] == 1.2
|
||||||
assert result['price'] == 4.2
|
assert result['price'] == 4.2
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('requests.sessions.Session.send')
|
||||||
|
def test_api_error(mocked_send, caplog):
|
||||||
|
response = requests.Response()
|
||||||
|
response.status_code = 502
|
||||||
|
response.headers = {'Content-Type': 'application/xml'}
|
||||||
|
response._content = b'x'
|
||||||
|
mocked_send.return_value = response
|
||||||
|
|
||||||
|
soap_resource = SOAPResource()
|
||||||
|
client = SOAPClient(soap_resource)
|
||||||
|
with pytest.raises(TransportError):
|
||||||
|
client.service.GetLastTradePrice(tickerSymbol='banana')
|
||||||
|
|
||||||
|
client = SOAPClient(soap_resource, api_error=True)
|
||||||
|
with pytest.raises(APIError, match=r'SOAP service at.*is unreachable'):
|
||||||
|
client.service.GetLastTradePrice(tickerSymbol='banana')
|
||||||
|
with mock.patch('zeep.proxy.OperationProxy.__call__') as operation_proxy_call:
|
||||||
|
|
||||||
|
operation_proxy_call.side_effect = Fault('boom!')
|
||||||
|
with pytest.raises(APIError, match=r'returned an error.*"boom!"'):
|
||||||
|
client.service.GetLastTradePrice(tickerSymbol='banana')
|
||||||
|
|
||||||
|
operation_proxy_call.side_effect = XMLParseError('Unexpected element')
|
||||||
|
with pytest.raises(APIError, match=r'Unexpected element'):
|
||||||
|
client.service.GetLastTradePrice(tickerSymbol='banana')
|
||||||
|
|
Loading…
Reference in New Issue