add contrib.nantes_scrib connector (#83360)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Thomas NOËL 2023-11-10 17:36:00 +01:00 committed by Thomas NOËL
parent 40c3c6affb
commit b8fc9716a4
7 changed files with 420 additions and 0 deletions

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.18 on 2023-12-14 16:45
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('soap', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Scrib',
fields=[
(
'soapconnector_ptr',
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='soap.soapconnector',
),
),
],
options={
'verbose_name': 'Scrib SOAP API',
},
bases=('soap.soapconnector',),
),
]

View File

@ -0,0 +1,90 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 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/>.
from django.utils.translation import gettext_lazy as _
from passerelle.apps.soap.models import SOAPConnector
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
DICT_SCHEMA = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'additionalProperties': True,
'unflatten': True,
}
def lax_int(value):
try:
return int(value)
except (ValueError, TypeError):
return None
class Scrib(SOAPConnector):
category = _('Business Process Connectors')
class Meta:
verbose_name = _('Scrib SOAP API')
@endpoint(
name='depot',
description=_('Clean payload and sent it to method/depot'),
display_order=-1,
display_category=_('Publik compatible API'),
methods=['post'],
post={'request_body': {'schema': {'application/json': DICT_SCHEMA}}},
)
def depot(self, request, post_data):
# clean "bac" list: keep only bacCollecteXXX + numBacCollecteXXX if
# bacCollecteXXX is True and numBacCollecteXXX is a positive integer
if not isinstance(post_data.get('demandeBacs', {}).get('bac'), list):
raise APIError('missing demandeBacs/bac list', http_status=400)
demandeBacs = post_data['demandeBacs']
cleaned_bacs = []
for bac in demandeBacs['bac']:
if not isinstance(bac, dict):
continue
cleaned_bac = {}
for key in [k for k in bac if k.startswith('bac')]:
if bac[key] not in (True, 'true', 'True', 1, '1'):
continue
num = 'numBac' + key[3:]
if lax_int(bac.get(num)): # is a strict positive integer
cleaned_bac[key] = 'true'
cleaned_bac[num] = bac[num]
if cleaned_bac:
cleaned_bacs.append(cleaned_bac)
demandeBacs['bac'] = cleaned_bacs
# remove numeroVoie if empty or non-integer (xsd:int/xsd:short in wsdl)
for key in ('numeroVoie', 'nombrePersonnesFoyer'):
if key in demandeBacs:
demandeBacs[key] = lax_int(demandeBacs[key])
if demandeBacs[key] is None: # was not an integer, remove it
del demandeBacs[key]
# remove raisonDemande and typeDegradation if empty (or not string)
for key in ('raisonDemande', 'typeDegradation'):
if key in demandeBacs:
if isinstance(demandeBacs[key], str) and demandeBacs[key].strip():
continue
del demandeBacs[key]
return self.method(request, method_name='depot', post_data=post_data)

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://ws.scrib/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://ws.scrib/" name="DemandeBacWS">
<types>
<xsd:schema targetNamespace="http://ws.scrib/">
<xsd:element name="depot" type="tns:depot"/>
<xsd:element name="depotResponse" type="tns:depotResponse"/>
<xsd:complexType name="depot">
<xsd:sequence>
<xsd:element name="demandeBacs" type="tns:demandeBacWsDto" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="demandeBacWsDto">
<xsd:sequence>
<xsd:element name="appartement" type="xsd:string" minOccurs="0"/>
<xsd:element name="bac" type="tns:typeNumBacWsDto" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="batiment" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeCivel" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeCommune" type="xsd:string" minOccurs="0"/>
<xsd:element name="codePostal" type="xsd:string" minOccurs="0"/>
<xsd:element name="codeSecteurTrisac" type="xsd:string" minOccurs="0"/>
<xsd:element name="commentaires" type="xsd:string" minOccurs="0"/>
<xsd:element name="commune" type="xsd:string" minOccurs="0"/>
<xsd:element name="complementVoie" type="xsd:string" minOccurs="0"/>
<xsd:element name="email" type="xsd:string" minOccurs="0"/>
<xsd:element name="etage" type="xsd:string" minOccurs="0"/>
<xsd:element name="jourCollecte" type="xsd:string" minOccurs="0"/>
<xsd:element name="libelleVoie" type="xsd:string" minOccurs="0"/>
<xsd:element name="mention" type="xsd:string" minOccurs="0"/>
<xsd:element name="natureDemande" type="tns:natureDemande" minOccurs="0"/>
<xsd:element name="natureDemandeur" type="tns:natureDemandeur" minOccurs="0"/>
<xsd:element name="nom" type="xsd:string" minOccurs="0"/>
<xsd:element name="nomAssociation" type="xsd:string" minOccurs="0"/>
<xsd:element name="nomCommercial" type="xsd:string" minOccurs="0"/>
<xsd:element name="nombrePersonnesFoyer" type="xsd:short" minOccurs="0"/>
<xsd:element name="numeroVoie" type="xsd:int" minOccurs="0"/>
<xsd:element name="prenom" type="xsd:string" minOccurs="0"/>
<xsd:element name="raisonDemande" type="tns:raisonDemande" minOccurs="0"/>
<xsd:element name="raisonSociale" type="xsd:string" minOccurs="0"/>
<xsd:element name="sigle" type="xsd:string" minOccurs="0"/>
<xsd:element name="telephone" type="xsd:string" minOccurs="0"/>
<xsd:element name="typeDegradation" type="tns:typeDegradation" minOccurs="0"/>
<xsd:element name="typeHabitat" type="tns:typeHabitat" minOccurs="0"/>
<xsd:element name="urlPublik" type="xsd:string" minOccurs="0"/>
<xsd:element name="usageBac" type="tns:usageBac" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="typeNumBacWsDto">
<xsd:sequence>
<xsd:element name="bacCollecteDechetsMenagers" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollectePapierCarton" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollecteSelective" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="bacCollecteVerre" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="numBacCollecteDechetsMenagers" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollectePapierCarton" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollecteSelective" type="xsd:string" minOccurs="0"/>
<xsd:element name="numBacCollecteVerre" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="depotResponse">
<xsd:sequence>
<xsd:element name="return" type="tns:demandeBacWsRetour" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="demandeBacWsRetour">
<xsd:sequence>
<xsd:element name="codeErreur" type="tns:codeErreur" minOccurs="0"/>
<xsd:element name="codeRetour" type="xsd:boolean"/>
<xsd:element name="messageErreur" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="natureDemande">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="DDOT"/>
<xsd:enumeration value="DREN"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="natureDemandeur">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="NPAR"/>
<xsd:enumeration value="NPRO"/>
<xsd:enumeration value="NASS"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="raisonDemande">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="BVOL"/>
<xsd:enumeration value="BDEG"/>
<xsd:enumeration value="BBRU"/>
<xsd:enumeration value="BAJU"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="typeDegradation">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="TDRO"/>
<xsd:enumeration value="TDCU"/>
<xsd:enumeration value="TDCO"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="typeHabitat">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="HCOL"/>
<xsd:enumeration value="HIND"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="usageBac">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="UPRO"/>
<xsd:enumeration value="UPRI"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="codeErreur">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ERR_TECH"/>
<xsd:enumeration value="ERR_FORMAT"/>
<xsd:enumeration value="ERR_FONC"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
</types>
<message name="depot">
<part name="parameters" element="tns:depot"/>
</message>
<message name="depotResponse">
<part name="parameters" element="tns:depotResponse"/>
</message>
<portType name="DemandeBacWS">
<operation name="depot">
<input wsam:Action="http://ws.scrib/DemandeBacWS/depotRequest" message="tns:depot"/>
<output wsam:Action="http://ws.scrib/DemandeBacWS/depotResponse" message="tns:depotResponse"/>
</operation>
</portType>
<binding name="DemandeBacWSPortBinding" type="tns:DemandeBacWS">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="depot">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="DemandeBacWS">
<port name="DemandeBacWSPort" binding="tns:DemandeBacWSPortBinding">
<soap:address location="http://scrib.example.net/scrib2/ws/demandeBac"/>
</port>
</service>
</definitions>

View File

@ -30,6 +30,7 @@ INSTALLED_APPS += ( # noqa pylint: disable=undefined-variable
'passerelle.contrib.lille_urban_card',
'passerelle.contrib.mdph13',
'passerelle.contrib.nancypoll',
'passerelle.contrib.nantes_scrib',
'passerelle.contrib.planitech',
'passerelle.contrib.rsa13',
'passerelle.contrib.sigerly',

145
tests/test_nantes_scrib.py Normal file
View File

@ -0,0 +1,145 @@
# Copyright (C) 2023 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 os
import pytest
import responses
import tests.utils
from passerelle.contrib.nantes_scrib.models import Scrib
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'nantes_scrib')
RSPS_OK = """<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body><ns2:depotResponse xmlns:ns2="http://ws.scrib/">
<return><codeRetour>true</codeRetour></return>
</ns2:depotResponse></S:Body></S:Envelope>"""
RSPS_ERR_TECH = """<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body><ns2:depotResponse xmlns:ns2="http://ws.scrib/">
<return><codeErreur>ERR_TECH</codeErreur><codeRetour>false</codeRetour></return>
</ns2:depotResponse></S:Body></S:Envelope>"""
WCS_WSCALL = {
'demandeBacs/bac/0/bacCollecteDechetsMenagers': 'true',
'demandeBacs/bac/0/bacCollecteSelective': 'false',
'demandeBacs/bac/0/bacCollecteVerre': 'false',
'demandeBacs/bac/0/numBacCollecteDechetsMenagers': '2',
'demandeBacs/bac/0/numBacCollecteSelective': '0',
'demandeBacs/bac/0/numBacCollecteVerre': '0',
'demandeBacs/bac/1': 'not-a-dict',
'demandeBacs/bac/2/bacCollecteDechetsMenagers': 'false',
'demandeBacs/bac/2/bacCollecteSelective': 1,
'demandeBacs/bac/2/bacCollecteVerre': 'false',
'demandeBacs/bac/2/numBacCollecteDechetsMenagers': '2',
'demandeBacs/bac/2/numBacCollecteSelective': '5',
'demandeBacs/bac/2/numBacCollecteVerre': '3',
'demandeBacs/bac/3/bacCollecteDechetsMenagers': 'false',
'demandeBacs/bac/3/bacCollecteSelective': '',
'demandeBacs/bac/3/bacCollecteVerre': 'blah',
'demandeBacs/bac/3/bacCollecteAutre': 'true', # no numBacCollecteAutre
'demandeBacs/bac/3/numBacCollecteDechetsMenagers': '2',
'demandeBacs/bac/3/numBacCollecteSelective': '0',
'demandeBacs/bac/3/numBacCollecteVerre': '0',
'demandeBacs/codeCivel': '1140206',
'demandeBacs/codePostal': '44000',
'demandeBacs/commune': 'TestVille',
'demandeBacs/email': 'test@example.org',
'demandeBacs/libelleVoie': 'Route de Test',
'demandeBacs/natureDemande': 'DREN',
'demandeBacs/natureDemandeur': 'NPAR',
'demandeBacs/nom': 'TESTNOM',
'demandeBacs/nombrePersonnesFoyer': ' 2 ',
'demandeBacs/numeroVoie': ' 42 ',
'demandeBacs/prenom': 'TestPrenom',
'demandeBacs/raisonDemande': 'BBRU',
'demandeBacs/typeDegradation': 'TDRO',
'demandeBacs/telephone': '0707070707',
'demandeBacs/typeHabitat': 'HCOL',
}
@pytest.fixture
def wsdl():
with open(os.path.join(TEST_BASE_DIR, 'scrib.wsdl'), 'rb') as wsdl_file:
return wsdl_file.read()
@pytest.fixture
def connector(db):
return tests.utils.setup_access_rights(
Scrib.objects.create(slug='test', wsdl_url='https://scrib.example.net/scrib.wsdl')
)
def test_scrib(wsdl, connector, app):
with responses.RequestsMock() as rsps:
rsps.get('https://scrib.example.net/scrib.wsdl', status=200, content_type='text/xml', body=wsdl)
rsps.post(
'https://scrib.example.net/scrib2/ws/demandeBac',
status=200,
content_type='text/xml',
body=RSPS_OK,
)
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
xml = rsps.calls[1].request.body.decode('utf-8') # post payload
assert resp.json == {
'data': {'codeErreur': None, 'codeRetour': True, 'messageErreur': None},
'err': 0,
}
# cleaned "bac" list, only two remains
assert xml.count('<bac>') == 2
assert (
'<bac><bacCollecteDechetsMenagers>true</bacCollecteDechetsMenagers>'
'<numBacCollecteDechetsMenagers>2</numBacCollecteDechetsMenagers></bac>' in xml
)
assert (
'<bac><bacCollecteSelective>true</bacCollecteSelective>'
'<numBacCollecteSelective>5</numBacCollecteSelective></bac>' in xml
)
assert '<nombrePersonnesFoyer>2</nombrePersonnesFoyer>' in xml
assert '<numeroVoie>42</numeroVoie>' in xml
assert '<raisonDemande>BBRU</raisonDemande>' in xml
assert '<typeDegradation>TDRO</typeDegradation>' in xml
# remove empty or non-integers fields
WCS_WSCALL['demandeBacs/nombrePersonnesFoyer'] = '0'
WCS_WSCALL['demandeBacs/numeroVoie'] = ' '
WCS_WSCALL['demandeBacs/raisonDemande'] = ' '
WCS_WSCALL['demandeBacs/typeDegradation'] = None
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
xml = rsps.calls[2].request.body.decode('utf-8') # post payload
assert xml.count('<bac>') == 2
assert '<nombrePersonnesFoyer>0</nombrePersonnesFoyer>' in xml
assert '<numeroVoie>' not in xml
assert '<raisonDemande>' not in xml
assert '<typeDegradation>' not in xml
with responses.RequestsMock() as rsps:
rsps.post(
'https://scrib.example.net/scrib2/ws/demandeBac', status=200, content_type='text/xml', body='booo'
)
resp = app.post_json('/nantes-scrib/test/depot', params=WCS_WSCALL)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'passerelle.utils.soap.SOAPInvalidContent'
with responses.RequestsMock() as rsps:
resp = app.post_json('/nantes-scrib/test/depot', params={}, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'missing demandeBacs/bac list'