passerelle/tests/test_utils_soap.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

217 lines
7.3 KiB
Python
Raw Normal View History

2022-01-19 16:52:48 +01:00
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
2022-05-30 14:10:33 +02:00
from unittest import mock
2022-01-19 16:52:48 +01:00
import pytest
import requests
import responses
2022-01-19 16:52:48 +01:00
from zeep import Settings
from zeep.exceptions import Fault, TransportError, XMLParseError
2022-01-19 16:52:48 +01:00
from zeep.plugins import Plugin
2022-05-30 14:10:33 +02:00
from passerelle.utils import Request
from passerelle.utils.jsonresponse import APIError, to_json
from passerelle.utils.soap import SOAPClient, SOAPFault
2022-01-19 16:52:48 +01:00
with open('tests/data/soap.wsdl') as fd:
WSDL = fd.read()
WSDL_URL = 'http://example.com/soap.wsdl'
2022-01-19 16:52:48 +01:00
class FooPlugin(Plugin):
pass
class BarPlugin(Plugin):
pass
FOO_XSD = '''\
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/foo.xsd">
<complexType name="Address">
<sequence>
<element minOccurs="0" maxOccurs="1" name="NameFirst" type="string"/>
<element minOccurs="0" maxOccurs="1" name="NameLast" type="string"/>
<element minOccurs="0" maxOccurs="1" name="Email" type="string"/>
</sequence>
</complexType>
</schema>
'''
@pytest.fixture
def http_mock():
with responses.RequestsMock() as http_mock:
yield http_mock
@pytest.fixture
def wsdl_response(http_mock):
http_mock.get('http://example.com/foo.xsd', FOO_XSD)
return http_mock.get(WSDL_URL, WSDL)
@responses.activate
@pytest.fixture
def soap_resource(http_mock, wsdl_response):
class SOAPResource:
def __init__(self):
self.wsdl_url = WSDL_URL
self.logger = mock.Mock()
2022-01-19 16:52:48 +01:00
def make_requests(self, **kwargs):
return Request(logger=self.logger, **kwargs)
yield SOAPResource()
2022-01-19 16:52:48 +01:00
def test_soap_client(soap_resource):
2022-01-19 16:52:48 +01:00
plugins = [FooPlugin, BarPlugin]
client = SOAPClient(soap_resource, plugins=plugins)
assert client.wsdl.location == WSDL_URL
2022-01-19 16:52:48 +01:00
assert client.plugins == plugins
def test_disable_strict_mode(soap_resource, http_mock):
http_mock.post(
'http://example.com/stockquote',
body=b'''\
<?xml version='1.0' encoding='utf-8'?>
2022-01-19 16:52:48 +01:00
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:TradePrice xmlns:ns0="http://example.com/stockquote.xsd">
<price>4.20</price>
</ns0:TradePrice>
</soap-env:Body>
</soap-env:Envelope>''',
2022-01-19 16:52:48 +01:00
)
client = SOAPClient(soap_resource)
match = 'Unexpected element %s, expected %s' % (repr('price'), repr('skipMe'))
with pytest.raises(XMLParseError, match=match):
client.service.GetLastTradePrice(tickerSymbol='banana')
client = SOAPClient(soap_resource, settings=Settings(strict=False))
result = client.service.GetLastTradePrice(tickerSymbol='banana')
assert len(result) == 2
assert result['skipMe'] is None
assert result['price'] == 4.2
def test_remove_first_bytes_for_xml(http_mock, soap_resource, caplog):
http_mock.add(
responses.POST,
'http://example.com/stockquote',
body=b'\x8b'
+ b'''\
<?xml version='1.0' encoding='utf-8'?>
2022-01-19 16:52:48 +01:00
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:TradePrice xmlns:ns0="http://example.com/stockquote.xsd">
<skipMe>1.2</skipMe>'
2022-01-19 16:52:48 +01:00
<price>4.20</price>
</ns0:TradePrice>
</soap-env:Body>
</soap-env:Envelope>''',
2022-01-19 16:52:48 +01:00
)
soap_resource.logger = logging.getLogger('soap_resource')
soap_resource.logger.setLevel(logging.INFO)
2022-01-19 16:52:48 +01:00
client = SOAPClient(soap_resource)
with pytest.raises(TransportError):
client.service.GetLastTradePrice(tickerSymbol='banana')
caplog.clear()
2022-01-19 16:52:48 +01:00
client = SOAPClient(soap_resource, transport_kwargs={'remove_first_bytes_for_xml': True})
result = client.service.GetLastTradePrice(tickerSymbol='banana')
assert result['skipMe'] == 1.2
assert result['price'] == 4.2
assert caplog.messages == ['POST http://example.com/stockquote (=> 200)']
assert 'response_content' not in caplog.records[-1].__dict__
caplog.clear()
soap_resource.logger.setLevel(logging.DEBUG)
result = client.service.GetLastTradePrice(tickerSymbol='banana')
assert caplog.messages == ['POST http://example.com/stockquote (=> 200)']
assert 'response_content' in caplog.records[-1].__dict__
def test_api_error(soap_resource, http_mock, caplog):
http_mock.add(responses.POST, 'http://example.com/stockquote', status=502, body=b'x')
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')
http_mock.replace(
responses.POST, 'http://example.com/stockquote', status=502, body=requests.ConnectTimeout('too long!')
)
with pytest.raises(APIError, match=r'SOAP service at.*is unreachable.*too long!'):
client.service.GetLastTradePrice(tickerSymbol='banana')
def test_fault_detail_on_500(soap_resource, http_mock):
http_mock.add(
responses.POST,
'http://example.com/stockquote',
body=b'''<?xml version='1.0' encoding='utf-8'?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:TradePrice xmlns:ns0="http://example.com/stockquote.xsd">
<skipMe>1.2</skipMe>
<price>4.20</price>
</ns0:TradePrice>
</soap-env:Body>
</soap-env:Envelope>''',
status=500,
)
client = SOAPClient(soap_resource, api_error=True)
with pytest.raises(SOAPFault) as exc:
client.service.GetLastTradePrice(tickerSymbol='banana')
response = to_json().err_to_response(exc.value)
assert response['err'] == 1
assert 'xmlns:soap' in response['data']['soap_fault']['detail']
def test_wsdl_cache(freezer, http_mock, wsdl_response, soap_resource):
assert wsdl_response.call_count == 0
SOAPClient(soap_resource, api_error=True)
assert wsdl_response.call_count == 1
SOAPClient(soap_resource, api_error=True)
assert wsdl_response.call_count == 1
freezer.tick(299)
SOAPClient(soap_resource, api_error=True)
assert wsdl_response.call_count == 1
freezer.tick(1)
SOAPClient(soap_resource, api_error=True)
assert wsdl_response.call_count == 2