From caf4899f993c3015fe72d80156ffe4c7d6956f03 Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Wed, 19 Jun 2019 17:47:50 +0200 Subject: [PATCH] atal: use the right soap methods (#34175) --- passerelle/apps/atal/models.py | 198 +++++++++++++++++++------------- passerelle/apps/atal/schemas.py | 189 ++++++++++++++++++++++++++++++ tests/test_atal.py | 84 +++++++------- 3 files changed, 349 insertions(+), 122 deletions(-) create mode 100644 passerelle/apps/atal/schemas.py diff --git a/passerelle/apps/atal/models.py b/passerelle/apps/atal/models.py index 84d5552d..0d51eeb9 100644 --- a/passerelle/apps/atal/models.py +++ b/passerelle/apps/atal/models.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # passerelle - uniform access to multiple data sources and services # Copyright (C) 2019 Entr'ouvert # @@ -16,74 +14,25 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import base64 from django.db import models from django.utils.six.moves import urllib from django.utils.translation import ugettext_lazy as _ import lxml.etree from zeep import helpers +from zeep.exceptions import Fault from passerelle.base.models import BaseResource from passerelle.utils.api import endpoint from passerelle.utils.jsonresponse import APIError +from . import schemas -INSERT_DEMANDE_BY_TYPE_SCHEMA = { - "$schema": "http://json-schema.org/draft-03/schema#", - "title": "", - "description": "", - "type": "object", - "properties": { - "contact_nom": { - "description": "Nom du contact", - "required": True - }, - "contact_tel": { - "description": "Téléphone du contact", - "type": "string", - }, - "contact_email": { - "description": "Email du contact", - "type": "string", - }, - "contact_adresse": { - "description": "Adresse du contact", - "type": "string", - }, - "demande_objet": { - "description": "Objet de la demande", - "type": "string", - }, - "demande_lieu": { - "description": "Lieu de la demande", - "type": "string", - }, - "demande_description": { - "description": "Description de la demande", - "type": "string", - }, - "remote_adresse": { - "description": "", - "type": "string" - }, - "code_equipement": { - "description": "Code de l'équipement", - "type": "string" - }, - "code_service_demandeur": { - "description": "Code du service demandeur", - "type": "string" - }, - "date_souhaite": { - "description": "Date souhaitée", - "type": "string" - }, - "type_demande": { - "description": "Type demande", - "type": "string" - } - } -} +def process_response(demande_number): + if not demande_number.startswith('DIT'): + raise APIError(demande_number) + return {'data': {'demande_number': demande_number}} class ATALConnector(BaseResource): @@ -98,7 +47,10 @@ class ATALConnector(BaseResource): def _soap_call(self, wsdl, method, **kwargs): wsdl_url = urllib.parse.urljoin(self.base_soap_url, '%s?wsdl' % wsdl) client = self.soap_client(wsdl_url=wsdl_url) - return getattr(client.service, method)(**kwargs) + try: + return getattr(client.service, method)(**kwargs) + except Fault as e: + raise APIError(unicode(e)) def _basic_ref(self, wsdl, method): soap_res = self._soap_call(wsdl=wsdl, method=method) @@ -107,15 +59,15 @@ class ATALConnector(BaseResource): res.append({'id': elem.code, 'text': elem.libelle}) return {'data': res} - @endpoint(methods=['get'], perm='can_access') + @endpoint(methods=['get'], perm='can_access', name='get-type-activite') def get_type_activite(self, request): return self._basic_ref('VilleAgileService', 'getTypeActivite') - @endpoint(methods=['get'], perm='can_access') + @endpoint(methods=['get'], perm='can_access', name='get-type-de-voie') def get_type_de_voie(self, request): return self._basic_ref('VilleAgileService', 'getTypeDeVoie') - @endpoint(methods=['get'], perm='can_access') + @endpoint(methods=['get'], perm='can_access', name='get-types-equipement') def get_types_equipement(self, request): soap_res = self._soap_call(wsdl='VilleAgileService', method='getTypesEquipement') tree = lxml.etree.fromstring(soap_res.encode('utf-8')).getroottree() @@ -126,44 +78,124 @@ class ATALConnector(BaseResource): return {'data': res} @endpoint( - perm='can_access', + perm='can_access', name='insert-action-comment', post={ - 'description': _('Insert Demande By Type'), + 'description': 'Insert action comment', 'request_body': { 'schema': { - 'application/json': INSERT_DEMANDE_BY_TYPE_SCHEMA + 'application/json': schemas.INSERT_ACTION_COMMENT } } } ) - def insert_demande_by_type(self, request, post_data): + def insert_action_comment(self, request, post_data): demande_number = self._soap_call( - wsdl='DemandeService', method='insertDemandeByType', - contactNom=post_data['contact_nom'], - contactTelephone=post_data['contact_telephone'], - contactCourriel=post_data['contact_email'], - contactAdresse=post_data['contact_adresse'], demandeObjet=post_data['demande_objet'], - demandeLieu=post_data['demande_lieu'], - demandeDescription=post_data['demande_description'], - remoteAddress=post_data['remote_adresse'], codeEquipement=post_data['code_equipement'], - codeServiceDemandeur=post_data['code_service_demandeur'], - dateSouhaitee=post_data['date_souhaite'], typeDemande=post_data['type_demande'] + wsdl='DemandeService', method='insertActionComment', + numeroDemande=post_data['numero_demande'], + commentaire=post_data['commentaire'] ) - return {'data': {'demande_number': demande_number}} + return process_response(demande_number) + + @endpoint( + perm='can_access', name='insert-demande-complet-by-type', + post={ + 'description': 'Insert demande complet by type', + 'request_body': { + 'schema': { + 'application/json': schemas.INSERT_DEMANDE_COMPLET_BY_TYPE + } + } + } + ) + def insert_demande_complet_by_type(self, request, post_data): + data = {} + for recv, send in [ + ('type_demande', 'typeDemande'), + ('code_service_demandeur', 'codeServiceDemandeur'), + ('date_saisie', 'dateSaisie'), + ('date_demande', 'dateDemande'), + ('date_souhaite', 'dateSouhaitee'), + ('date_butoir', 'dateButoir'), + ('contact_civilite', 'contactCivilite'), + ('contact_nom', 'contactNom'), + ('contact_prenom', 'contactPrenom'), + ('contact_tel', 'contactTelephone'), + ('contact_mobile', 'contactMobile'), + ('contact_email', 'contactCourriel'), + ('contact_info_compl', 'contactInfoCompl'), + ('demande_type_support', 'demandeTypeDeSupport'), + ('contact_adresse', 'contactAdresse'), + ('contact_adresse_compl', 'contactAdresseCompl'), + ('contact_code_postal', 'contactCodePostal'), + ('contact_ville', 'contactVille'), + ('contact_organisme', 'contactOrganisme'), + ('contact_titre', 'contactTitre'), + ('code_equipement', 'codeEquipement'), + ('code_mairie_equipement', 'codeMairieEquipement'), + ('code_sig_equipement', 'codeSIGEquipement'), + ('code_collectivite_equipement', 'codeCollectiviteEquipement'), + ('code_quartier_equipement', 'codeQuartierEquipement'), + ('code_type_equipement', 'codeTypeEquipement'), + ('demande_lieu', 'demandeLieu'), + ('coord_x', 'coordX'), + ('coord_y', 'coordY'), + ('demande_priorite', 'demandePriorite'), + ('demande_objet', 'demandeObjet'), + ('demande_description', 'demandeDescription'), + ('demande_commentaire', 'demandeCommentaire'), + ('demande_mots_cles', 'demandeMotsCles'), + ('code_thematique', 'codeThematiqueATAL'), + ('code_priorite', 'codePrioriteATAL'), + ('demande_thematique', 'demandeThematique'), + ('code_projet', 'codeProjetATAL'), + ]: + if recv in post_data: + data[send] = post_data[recv] + + demande_number = self._soap_call( + wsdl='DemandeService', method='insertDemandeCompletByType', **data + ) + return process_response(demande_number) @endpoint( methods=['get'], perm='can_access', example_pattern='{demande_number}/', - pattern='^(?P\w+)/$', + pattern='^(?P\w+)/$', name='retrieve-etat-travaux', parameters={ 'demande_number': { 'description': _('Demande number'), 'example_value': 'DIT18050001' } } ) - def retrieve_details_demande(self, request, demande_number, **kwargs): - if not demande_number: - raise APIError('A demande_number parameter must be specified') + def retrieve_etat_travaux(self, request, demande_number): soap_res = self._soap_call( - wsdl='DemandeService', method='retrieveDetailsDemande', - demandeNumberParam=demande_number) + wsdl='DemandeService', method='retrieveEtatTravaux', + numero=demande_number) return {'data': helpers.serialize_object(soap_res)} + + @endpoint( + perm='can_access', + post={ + 'description': 'Upload a file', + 'request_body': { + 'schema': { + 'application/json': schemas.UPLOAD + } + } + } + ) + def upload(self, request, post_data): + try: + content = base64.b64decode(post_data['file']['content']) + except TypeError: + raise APIError('Invalid base64 string') + + data = { + 'donneesFichier': content, + 'numeroDemande': post_data['numero_demande'], + 'nomFichier': post_data['nom_fichier'] + } + self._soap_call( + wsdl='ChargementPiecesJointesService', method='upload', + **data + ) + return {} diff --git a/passerelle/apps/atal/schemas.py b/passerelle/apps/atal/schemas.py new file mode 100644 index 00000000..7dea8346 --- /dev/null +++ b/passerelle/apps/atal/schemas.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- + +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2019 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 . + + +INSERT_DEMANDE_COMPLET_BY_TYPE = { + '$schema': 'http://json-schema.org/draft-03/schema#', + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'type_demande': { + 'type': 'string', + 'required': True, + }, + 'code_service_demandeur': { + 'type': 'string', + }, + 'date_saisie': { + 'type': 'string', + }, + 'date_demande': { + 'type': 'string', + }, + 'date_souhaite': { + 'type': 'string', + }, + 'date_butoir': { + 'type': 'string', + }, + 'contact_civilite': { + 'type': 'string', + }, + 'contact_nom': { + 'type': 'string', + }, + 'contact_prenom': { + 'type': 'string', + }, + 'contact_tel': { + 'type': 'string', + }, + 'contact_mobile': { + 'type': 'string', + }, + 'contact_email': { + 'type': 'string', + }, + 'contact_info_compl': { + 'type': 'string', + }, + 'demande_type_support': { + 'type': 'string', + }, + 'contact_adresse': { + 'type': 'string', + }, + 'contact_adresse_compl': { + 'type': 'string', + }, + 'contact_code_postal': { + 'type': 'string', + }, + 'contact_ville': { + 'type': 'string', + }, + 'contact_organisme': { + 'type': 'string', + }, + 'contact_titre': { + 'type': 'string', + }, + 'code_equipement': { + 'type': 'string', + }, + 'code_mairie_equipement': { + 'type': 'string', + }, + 'code_sig_equipement': { + 'type': 'string', + }, + 'code_collectivite_equipement': { + 'type': 'string', + }, + 'code_quartier_equipement': { + 'type': 'string', + }, + 'code_type_equipement': { + 'type': 'string', + }, + 'demande_lieu': { + 'type': 'string', + }, + 'coord_x': { + 'type': 'string', + }, + 'coord_y': { + 'type': 'string', + }, + 'demande_priorite': { + 'type': 'string', + }, + 'demande_objet': { + 'type': 'string', + }, + 'demande_description': { + 'type': 'string', + }, + 'demande_commentaire': { + 'type': 'string', + }, + 'remote_adresse': { + 'type': 'string' + }, + 'demande_mots_cles': { + 'type': 'string' + }, + 'code_thematique': { + 'type': 'string', + }, + 'code_priorite': { + 'type': 'string' + }, + 'demande_thematique': { + 'type': 'string' + }, + 'code_projet': { + 'type': 'string' + } + } +} + +INSERT_ACTION_COMMENT = { + '$schema': 'http://json-schema.org/draft-03/schema#', + 'type': 'object', + 'properties': { + 'numero_demande': { + 'type': 'string', + 'required': True, + }, + 'commentaire': { + 'type': 'string', + 'required': True, + } + } +} + +UPLOAD = { + '$schema': 'http://json-schema.org/draft-03/schema#', + 'definitions': { + 'file': { + 'type': 'object', + 'properties': { + 'content': { + 'type': 'string', + 'required': True + }, + }, + 'required': True + } + }, + 'type': 'object', + 'properties': { + 'file': { + '$ref': '#/definitions/file' + }, + 'numero_demande': { + 'type': 'string', + 'required': True, + }, + 'nom_fichier': { + 'type': 'string', + 'required': True, + } + } +} diff --git a/tests/test_atal.py b/tests/test_atal.py index 5b049351..c88ed36f 100644 --- a/tests/test_atal.py +++ b/tests/test_atal.py @@ -1,3 +1,5 @@ +import base64 + from django.contrib.contenttypes.models import ContentType import mock import pytest @@ -42,7 +44,7 @@ REFS = [ def test_get_type_activite(app, connector, monkeypatch): mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS) - response = app.get('/atal/slug-atal/get_type_activite') + response = app.get('/atal/slug-atal/get-type-activite') assert response.json == { 'err': 0, 'data': [ @@ -56,7 +58,7 @@ def test_get_type_activite(app, connector, monkeypatch): def test_get_type_de_voie(app, connector, monkeypatch): mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS) - response = app.get('/atal/slug-atal/get_type_de_voie') + response = app.get('/atal/slug-atal/get-type-de-voie') assert response.json == { 'err': 0, 'data': [ @@ -77,7 +79,7 @@ def test_get_types_equipement(app, connector, monkeypatch): """ mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=return_value) - response = app.get('/atal/slug-atal/get_types_equipement') + response = app.get('/atal/slug-atal/get-types-equipement') assert response.json == { 'err': 0, 'data': [ @@ -89,53 +91,57 @@ def test_get_types_equipement(app, connector, monkeypatch): assert call_params['method'] == 'getTypesEquipement' -def test_insert_demande_by_type(app, connector, monkeypatch): +def test_insert_demande_complet_by_type(app, connector, monkeypatch): mock_soap_call = mock_atal_soap_call(monkeypatch, return_value='DIT19050001') params = { - 'contact_nom': 'John Doe', - 'contact_telephone': '0101010101', - 'contact_email': 'john@doe.com', - 'contact_adresse': '1 doe street', - 'demande_objet': 'sarah connor', - 'demande_lieu': 'LA', - 'demande_description': 'poker face', - 'remote_adresse': 'hollywood bd', - 'code_equipement': 'MAC10', - 'code_service_demandeur': 'skynet', - 'date_souhaite': 'now', - 'type_demande': 'scary' + 'numero_demande': 'DIT19050001', + 'commentaire': 'aaa' } - response = app.post_json('/atal/slug-atal/insert_demande_by_type', params=params) + response = app.post_json('/atal/slug-atal/insert-action-comment', params=params) assert response.json == { 'err': 0, 'data': {'demande_number': 'DIT19050001'} } call_params = mock_soap_call.call_args.kwargs assert call_params['wsdl'] == 'DemandeService' - assert call_params['method'] == 'insertDemandeByType' - assert call_params['contactNom'] == 'John Doe' - assert call_params['contactTelephone'] == '0101010101' - assert call_params['contactCourriel'] == 'john@doe.com' - assert call_params['contactAdresse'] == '1 doe street' - assert call_params['demandeObjet'] == 'sarah connor' - assert call_params['demandeLieu'] == 'LA' - assert call_params['demandeDescription'] == 'poker face' - assert call_params['remoteAddress'] == 'hollywood bd' - assert call_params['codeEquipement'] == 'MAC10' - assert call_params['codeServiceDemandeur'] == 'skynet' - assert call_params['dateSouhaitee'] == 'now' - assert call_params['typeDemande'] == 'scary' + assert call_params['method'] == 'insertActionComment' + assert call_params['numeroDemande'] == 'DIT19050001' + assert call_params['commentaire'] == 'aaa' -def test_retrieve_details_demande(app, connector, monkeypatch): - mock_soap_call = mock_atal_soap_call( - monkeypatch, return_value=dict(code='code1', libelle='elem1')) - response = app.get('/atal/slug-atal/retrieve_details_demande/DIT19050001/') +def test_upload(app, connector, monkeypatch): + mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=None) + base64_str = 'eyJsYXN0X2NoZWNrIjoiMjAxOS0wNC0xMFQxMjowODoyOVoiL' + \ + 'CJweXBpX3ZlcnNpb24iOiIxOS4wLjMifQ==' + params = { + 'numero_demande': 'DIT19050001', + 'nom_fichier': 'data.json', + 'file': { + 'content': base64_str + } + } + response = app.post_json('/atal/slug-atal/upload', params=params) assert response.json == { - 'err': 0, - 'data': {'code': 'code1', 'libelle': 'elem1'} + 'err': 0 } call_params = mock_soap_call.call_args.kwargs - assert call_params['wsdl'] == 'DemandeService' - assert call_params['method'] == 'retrieveDetailsDemande' - assert call_params['demandeNumberParam'] == 'DIT19050001' + assert call_params['wsdl'] == 'ChargementPiecesJointesService' + assert call_params['method'] == 'upload' + assert call_params['numeroDemande'] == 'DIT19050001' + assert call_params['nomFichier'] == 'data.json' + assert call_params['donneesFichier'] == base64.b64decode(base64_str) + + params = { + 'numero_demande': 'DIT19050001', + 'nom_fichier': 'data.json', + 'file': { + 'content': 'invalidbase64' + } + } + response = app.post_json('/atal/slug-atal/upload', params=params) + assert response.json == { + 'data': None, + 'err': 1, + 'err_class': 'passerelle.utils.jsonresponse.APIError', + 'err_desc': 'Invalid base64 string' + }