Projet

Général

Profil

0001-utils-soap-add-wrapping-of-zeep-errors-inside-APIErr.patch

Benjamin Dauvergne, 25 mai 2022 20:01

Télécharger (7,52 ko)

Voir les différences:

Subject: [PATCH] utils/soap: add wrapping of zeep errors inside APIError
 (#58925)

 passerelle/apps/soap/models.py   |  1 +
 passerelle/contrib/utils/axel.py |  2 +-
 passerelle/utils/soap.py         | 53 ++++++++++++++++++++++++++++++++
 tests/test_soap.py               | 16 +++++-----
 tests/test_utils_soap.py         | 30 +++++++++++++++++-
 5 files changed, 93 insertions(+), 9 deletions(-)
passerelle/apps/soap/models.py
79 79
            settings=zeep.Settings(
80 80
                strict=self.zeep_strict, xsd_ignore_sequence_order=self.zeep_xsd_ignore_sequence_order
81 81
            ),
82
            api_error=True,
82 83
            **kwargs,
83 84
        )
84 85

  
passerelle/contrib/utils/axel.py
225 225
        return schema
226 226

  
227 227
    def __call__(self, resource, request_data=None):
228
        client = resource.soap_client()
228
        client = resource.soap_client(api_error=True)
229 229

  
230 230
        serialized_request = ''
231 231
        if self.request_converter:
passerelle/utils/soap.py
19 19
from requests import RequestException
20 20
from zeep import Client
21 21
from zeep.cache import InMemoryCache
22
from zeep.exceptions import Error as ZeepError
23
from zeep.exceptions import Fault, TransportError
24
from zeep.proxy import OperationProxy, ServiceProxy
22 25
from zeep.transports import Transport
23 26

  
24 27
from passerelle.utils.jsonresponse import APIError
......
28 31
    pass
29 32

  
30 33

  
34
class SOAPServiceUnreachable(SOAPError):
35
    def __init__(self, client, exception):
36
        super().__init__(
37
            f'SOAP service at {client.wsdl.location} is unreachable. Please contact its administrator',
38
            data={
39
                'wsdl': client.wsdl.location,
40
                'status_code': exception.status_code,
41
                'error': str(exception),
42
            },
43
        )
44

  
45

  
46
class SOAPFault(SOAPError):
47
    def __init__(self, client, fault):
48
        super().__init__(
49
            f'SOAP service at {client.wsdl.location} returned an error "{fault.message or fault.code}"',
50
            data={
51
                'soap_fault': fault.__dict__,
52
            },
53
        )
54

  
55

  
56
class OperationProxyWrapper(OperationProxy):
57
    def __call__(self, *args, **kwargs):
58
        client = self._proxy._client
59
        try:
60
            return super().__call__(*args, **kwargs)
61
        except TransportError as transport_error:
62
            raise SOAPServiceUnreachable(client, transport_error)
63
        except Fault as fault:
64
            raise SOAPFault(client, fault)
65
        except ZeepError as zeep_error:
66
            raise SOAPError(str(zeep_error))
67

  
68

  
69
class ServiceProxyWrapper(ServiceProxy):
70
    def __getitem__(self, key):
71
        operation = super().__getitem__(key)
72
        operation.__class__ = OperationProxyWrapper
73
        return operation
74

  
75

  
31 76
class SOAPClient(Client):
32 77
    """Wrapper around zeep.Client
33 78

  
......
36 81

  
37 82
    def __init__(self, resource, **kwargs):
38 83
        wsdl_url = kwargs.pop('wsdl_url', None) or resource.wsdl_url
84
        self.api_error = kwargs.pop('api_error', False)
39 85
        transport_kwargs = kwargs.pop('transport_kwargs', {})
40 86
        transport_class = getattr(resource, 'soap_transport_class', SOAPTransport)
41 87
        transport = transport_class(
......
43 89
        )
44 90
        super().__init__(wsdl_url, transport=transport, **kwargs)
45 91

  
92
    @property
93
    def service(self):
94
        service = super().service
95
        if self.api_error:
96
            service.__class__ = ServiceProxyWrapper
97
        return service
98

  
46 99

  
47 100
class ResponseFixContentWrapper:
48 101
    def __init__(self, response):
tests/test_soap.py
139 139
        'greeting': 'Hello',
140 140
        'who': 'John!',
141 141
    }
142
    VALIDATION_ERROR = 'Missing element firstName (sayHello.firstName)'
142 143

  
143 144

  
144 145
class SOAP12(SOAP11):
......
254 255
        'greeting': 'Hello',
255 256
        'who': ['John!'],
256 257
    }
258
    VALIDATION_ERROR = 'Expected at least 1 items (minOccurs check) 0 items found. (sayHello.firstName)'
257 259

  
258 260

  
259 261
class BrokenSOAP12(SOAP12):
......
301 303
    assert list(connector.operations_and_schemas) == [('sayHello', soap.INPUT_SCHEMA, soap.OUTPUT_SCHEMA)]
302 304

  
303 305

  
304
def test_say_hello_method_validation_error(connector, app):
305
    resp = app.get('/soap/test/method/sayHello/', status=500)
306
    assert dict(resp.json, err_desc=None) == {
307
        'err': 1,
308
        'err_class': 'zeep.exceptions.ValidationError',
309
        'err_desc': None,
310
        'data': None,
306
def test_say_hello_method_validation_error(connector, soap, app):
307
    resp = app.get('/soap/test/method/sayHello/')
308
    assert resp.json == {
309
        "err": 1,
310
        "err_class": "passerelle.utils.soap.SOAPError",
311
        "err_desc": soap.VALIDATION_ERROR,
312
        "data": None,
311 313
    }
312 314

  
313 315

  
tests/test_utils_soap.py
18 18
import requests
19 19
from django.utils.encoding import force_bytes
20 20
from zeep import Settings
21
from zeep.exceptions import TransportError, XMLParseError
21
from zeep.exceptions import Fault, TransportError, XMLParseError
22 22
from zeep.plugins import Plugin
23 23

  
24
from passerelle.utils.jsonresponse import APIError
24 25
from passerelle.utils.soap import SOAPClient
25 26

  
26 27
WSDL = 'tests/data/soap.wsdl'
......
107 108
    assert len(result) == 2
108 109
    assert result['skipMe'] == 1.2
109 110
    assert result['price'] == 4.2
111

  
112

  
113
@mock.patch('requests.sessions.Session.send')
114
def test_api_error(mocked_send, caplog):
115
    response = requests.Response()
116
    response.status_code = 502
117
    response.headers = {'Content-Type': 'application/xml'}
118
    response._content = b'x'
119
    mocked_send.return_value = response
120

  
121
    soap_resource = SOAPResource()
122
    client = SOAPClient(soap_resource)
123
    with pytest.raises(TransportError):
124
        client.service.GetLastTradePrice(tickerSymbol='banana')
125

  
126
    client = SOAPClient(soap_resource, api_error=True)
127
    with pytest.raises(APIError, match=r'SOAP service at.*is unreachable'):
128
        client.service.GetLastTradePrice(tickerSymbol='banana')
129
    with mock.patch('zeep.proxy.OperationProxy.__call__') as operation_proxy_call:
130

  
131
        operation_proxy_call.side_effect = Fault('boom!')
132
        with pytest.raises(APIError, match=r'returned an error.*"boom!"'):
133
            client.service.GetLastTradePrice(tickerSymbol='banana')
134

  
135
        operation_proxy_call.side_effect = XMLParseError('Unexpected element')
136
        with pytest.raises(APIError, match=r'Unexpected element'):
137
            client.service.GetLastTradePrice(tickerSymbol='banana')
110
-