atal: use the right soap methods (#34175)

This commit is contained in:
Emmanuel Cazenave 2019-06-19 17:47:50 +02:00
parent 223dcd7e5e
commit caf4899f99
3 changed files with 349 additions and 122 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
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<demande_number>\w+)/$',
pattern='^(?P<demande_number>\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 {}

View File

@ -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 <http://www.gnu.org/licenses/>.
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,
}
}
}

View File

@ -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):
</types>
"""
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'
}