From caf4899f993c3015fe72d80156ffe4c7d6956f03 Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
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              |  88 +++++++-------
 3 files changed, 351 insertions(+), 124 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 <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 {}
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 <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,
+        }
+    }
+}
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):
 </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'
-
-
-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/')
+    assert call_params['method'] == 'insertActionComment'
+    assert call_params['numeroDemande'] == 'DIT19050001'
+    assert call_params['commentaire'] == 'aaa'
+
+
+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'
+    }
-- 
2.20.1

