tests: improve ResponsesSoap (#72638)

* add support for overriden service binding address
* encode wsdl_content when it is not bytes() but str()
* keep deserialized SOAP requests bodies in mock.soap_requests for
  inspection
* allow nesting ResponsesSoap context manager by using the same
  RequestsMock context manager.
This commit is contained in:
Benjamin Dauvergne 2022-12-20 14:59:42 +01:00
parent db49dab374
commit c8155b70c4
2 changed files with 46 additions and 58 deletions

View File

@ -18,7 +18,6 @@ import os
from unittest import mock from unittest import mock
import pytest import pytest
import responses
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
from zeep import Settings from zeep import Settings
@ -92,41 +91,30 @@ def django_db_setup(django_db_setup, django_db_blocker):
)[0] )[0]
) )
with responses.RequestsMock() as mock: family_service = ResponsesSoap(
family_service = ResponsesSoap( wsdl_url='https://example.org/FamilyService?wsdl',
wsdl_url='https://example.org/FamilyService?wsdl', wsdl_content=get_xml_file('FamilyService.wsdl'),
wsdl_content=get_xml_file('FamilyService.wsdl'), settings=Settings(strict=False, xsd_ignore_sequence_order=True),
settings=Settings(strict=False, xsd_ignore_sequence_order=True), )
) with family_service():
mock.add(responses.GET, family_service.wsdl_url, body=family_service.wsdl_content, status=200) family_service.add_soap_response('readCategoryList', get_xml_file('R_read_category_list.xml'))
family_service.add_soap_response( family_service.add_soap_response(
mock, 'readCategoryList', get_xml_file('R_read_category_list.xml') 'readChildIndicatorList', get_xml_file('R_read_child_indicator_list.xml')
) )
family_service.add_soap_response('readCivilityList', get_xml_file('R_read_civility_list.xml'))
family_service.add_soap_response('readCountryList', get_xml_file('R_read_country_list.xml'))
family_service.add_soap_response('readCSPList', get_xml_file('R_read_csp_list.xml'))
family_service.add_soap_response('readDietCodeList', get_xml_file('R_read_dietcode_list.xml'))
family_service.add_soap_response('readOrganList', get_xml_file('R_read_organ_list.xml'))
family_service.add_soap_response('readPAIList', get_xml_file('R_read_pai_list.xml'))
family_service.add_soap_response('readQualityList', get_xml_file('R_read_quality_list.xml'))
family_service.add_soap_response('readQuotientList', get_xml_file('R_read_quotient_list.xml'))
family_service.add_soap_response( family_service.add_soap_response(
mock, 'readChildIndicatorList', get_xml_file('R_read_child_indicator_list.xml') 'readRLIndicatorList', get_xml_file('R_read_rl_indicator_list.xml')
) )
family_service.add_soap_response( family_service.add_soap_response('readSituationList', get_xml_file('R_read_situation_list.xml'))
mock, 'readCivilityList', get_xml_file('R_read_civility_list.xml') family_service.add_soap_response('readStreetList', get_xml_file('R_read_street_list.xml'))
) family_service.add_soap_response('readVaccinList', get_xml_file('R_read_vaccin_list.xml'))
family_service.add_soap_response(mock, 'readCountryList', get_xml_file('R_read_country_list.xml'))
family_service.add_soap_response(mock, 'readCSPList', get_xml_file('R_read_csp_list.xml'))
family_service.add_soap_response(
mock, 'readDietCodeList', get_xml_file('R_read_dietcode_list.xml')
)
family_service.add_soap_response(mock, 'readOrganList', get_xml_file('R_read_organ_list.xml'))
family_service.add_soap_response(mock, 'readPAIList', get_xml_file('R_read_pai_list.xml'))
family_service.add_soap_response(mock, 'readQualityList', get_xml_file('R_read_quality_list.xml'))
family_service.add_soap_response(
mock, 'readQuotientList', get_xml_file('R_read_quotient_list.xml')
)
family_service.add_soap_response(
mock, 'readRLIndicatorList', get_xml_file('R_read_rl_indicator_list.xml')
)
family_service.add_soap_response(
mock, 'readSituationList', get_xml_file('R_read_situation_list.xml')
)
family_service.add_soap_response(mock, 'readStreetList', get_xml_file('R_read_street_list.xml'))
family_service.add_soap_response(mock, 'readVaccinList', get_xml_file('R_read_vaccin_list.xml'))
con.daily() con.daily()
# reset change in zeep private interface to bypass clear_cache fixture # reset change in zeep private interface to bypass clear_cache fixture

View File

@ -74,9 +74,11 @@ def endpoint_get(expected_url, app, resource, endpoint, **kwargs):
class ResponsesSoap: class ResponsesSoap:
def __init__(self, wsdl_url, wsdl_content, settings=None): def __init__(self, wsdl_url, wsdl_content, settings=None, address=None, requests_mock=None):
self.wsdl_url = wsdl_url self.wsdl_url = wsdl_url
self.wsdl_content = wsdl_content self.wsdl_content = wsdl_content
if isinstance(wsdl_content, str):
wsdl_content = wsdl_content.encode()
self.wsdl = zeep.wsdl.Document(io.BytesIO(wsdl_content), None, settings=settings) self.wsdl = zeep.wsdl.Document(io.BytesIO(wsdl_content), None, settings=settings)
self.soap_responses = [] self.soap_responses = []
assert ( assert (
@ -88,28 +90,32 @@ class ResponsesSoap:
), f'more or less than one port: {len(self.service.ports.values())}' ), f'more or less than one port: {len(self.service.ports.values())}'
self.port = list(self.service.ports.values())[0] self.port = list(self.service.ports.values())[0]
self.binding = self.port.binding self.binding = self.port.binding
self.address = self.port.binding_options['address'] self.address = address or self.port.binding_options['address']
self.requests_mock = requests_mock or responses.RequestsMock()
self.soap_requests = []
def soap_matcher(self, operation_name, request_check=None): def soap_matcher(self, mock, operation_name, request_check=None):
operation = self.binding.get(operation_name) operation = self.binding.get(operation_name)
input_element_qname = operation.input.body.qname input_element_qname = operation.input.body.qname
def matcher(prepared_request): def matcher(prepared_request):
doc = ET.parse(io.BytesIO(prepared_request.body)) doc = ET.parse(io.BytesIO(prepared_request.body))
request = operation.input.deserialize(doc.getroot())
if doc.find(f'.//{str(input_element_qname)}') is not None: if doc.find(f'.//{str(input_element_qname)}') is not None:
try: if request_check:
return True, f'Element "{str(input_element_qname)}" found' request_check(request)
finally: self.soap_requests.append(request)
if request_check: return True, f'Element "{str(input_element_qname)}" found'
request = operation.input.deserialize(doc.getroot())
request_check(request)
return False, None return False, None
return matcher return matcher
def add_soap_response(self, mock, operation_name, response_content, status=200, request_check=None): def add_soap_response(self, operation_name, response_content, status=200, request_check=None):
operation = self.binding.get(operation_name) operation = self.binding.get(operation_name)
if not isinstance(response_content, Exception): if isinstance(response_content, dict):
serialized_message = operation.output.serialize(**response_content)
body = ET.tostring(serialized_message.content)
elif not isinstance(response_content, Exception):
doc = ET.parse(io.BytesIO(response_content)) doc = ET.parse(io.BytesIO(response_content))
try: try:
operation.output.deserialize(doc.getroot()) operation.output.deserialize(doc.getroot())
@ -117,25 +123,19 @@ class ResponsesSoap:
raise AssertionError( raise AssertionError(
f'response_content did not match operation "{operation_name}" schema' f'response_content did not match operation "{operation_name}" schema'
) from e ) from e
mock.add( body = response_content
else:
body = response_content
self.requests_mock.add(
responses.POST, responses.POST,
self.address, self.address,
body=response_content, body=body,
status=status, status=status,
match=(self.soap_matcher(operation_name, request_check),), match=(self.soap_matcher(mock, operation_name, request_check),),
) )
@contextlib.contextmanager @contextlib.contextmanager
def __call__(self): def __call__(self):
with responses.RequestsMock() as mock: with self.requests_mock:
mock.add(responses.GET, self.wsdl_url, body=self.wsdl_content, status=200) self.requests_mock.add(responses.GET, self.wsdl_url, body=self.wsdl_content, status=200)
mock.add_soap_response = ( yield self
lambda operation, response_content, status=200, request_check=None: self.add_soap_response(
mock,
operation,
response_content,
status=status,
request_check=request_check,
)
)
yield mock