Projet

Général

Profil

0001-atal-use-the-right-soap-methods-34175.patch

Emmanuel Cazenave, 25 juin 2019 16:21

Télécharger (21,2 ko)

Voir les différences:

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
passerelle/apps/atal/models.py
1
# -*- coding: utf-8 -*-
2

  
3 1
# passerelle - uniform access to multiple data sources and services
4 2
# Copyright (C) 2019  Entr'ouvert
5 3
#
......
16 14
# You should have received a copy of the GNU Affero General Public License
17 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 16

  
17
import base64
19 18

  
20 19
from django.db import models
21 20
from django.utils.six.moves import urllib
22 21
from django.utils.translation import ugettext_lazy as _
23 22
import lxml.etree
24 23
from zeep import helpers
24
from zeep.exceptions import Fault
25 25

  
26 26
from passerelle.base.models import BaseResource
27 27
from passerelle.utils.api import endpoint
28 28
from passerelle.utils.jsonresponse import APIError
29
from . import schemas
29 30

  
30 31

  
31
INSERT_DEMANDE_BY_TYPE_SCHEMA = {
32
    "$schema": "http://json-schema.org/draft-03/schema#",
33
    "title": "",
34
    "description": "",
35
    "type": "object",
36
    "properties": {
37
        "contact_nom": {
38
            "description": "Nom du contact",
39
            "required": True
40
        },
41
        "contact_tel": {
42
            "description": "Téléphone du contact",
43
            "type": "string",
44
        },
45
        "contact_email": {
46
            "description": "Email du contact",
47
            "type": "string",
48
        },
49
        "contact_adresse": {
50
            "description": "Adresse du contact",
51
            "type": "string",
52
        },
53
        "demande_objet": {
54
            "description": "Objet de la demande",
55
            "type": "string",
56
        },
57
        "demande_lieu": {
58
            "description": "Lieu de la demande",
59
            "type": "string",
60
        },
61
        "demande_description": {
62
            "description": "Description de la demande",
63
            "type": "string",
64
        },
65
        "remote_adresse": {
66
            "description": "",
67
            "type": "string"
68
        },
69
        "code_equipement": {
70
            "description": "Code de l'équipement",
71
            "type": "string"
72
        },
73
        "code_service_demandeur": {
74
            "description": "Code du service demandeur",
75
            "type": "string"
76
        },
77
        "date_souhaite": {
78
            "description": "Date souhaitée",
79
            "type": "string"
80
        },
81
        "type_demande": {
82
            "description": "Type demande",
83
            "type": "string"
84
        }
85
    }
86
}
32
def process_response(demande_number):
33
    if not demande_number.startswith('DIT'):
34
        raise APIError(demande_number)
35
    return {'data': {'demande_number': demande_number}}
87 36

  
88 37

  
89 38
class ATALConnector(BaseResource):
......
98 47
    def _soap_call(self, wsdl, method, **kwargs):
99 48
        wsdl_url = urllib.parse.urljoin(self.base_soap_url, '%s?wsdl' % wsdl)
100 49
        client = self.soap_client(wsdl_url=wsdl_url)
101
        return getattr(client.service, method)(**kwargs)
50
        try:
51
            return getattr(client.service, method)(**kwargs)
52
        except Fault as e:
53
            raise APIError(unicode(e))
102 54

  
103 55
    def _basic_ref(self, wsdl, method):
104 56
        soap_res = self._soap_call(wsdl=wsdl, method=method)
......
107 59
            res.append({'id': elem.code, 'text': elem.libelle})
108 60
        return {'data': res}
109 61

  
110
    @endpoint(methods=['get'], perm='can_access')
62
    @endpoint(methods=['get'], perm='can_access', name='get-type-activite')
111 63
    def get_type_activite(self, request):
112 64
        return self._basic_ref('VilleAgileService', 'getTypeActivite')
113 65

  
114
    @endpoint(methods=['get'], perm='can_access')
66
    @endpoint(methods=['get'], perm='can_access', name='get-type-de-voie')
115 67
    def get_type_de_voie(self, request):
116 68
        return self._basic_ref('VilleAgileService', 'getTypeDeVoie')
117 69

  
118
    @endpoint(methods=['get'], perm='can_access')
70
    @endpoint(methods=['get'], perm='can_access', name='get-types-equipement')
119 71
    def get_types_equipement(self, request):
120 72
        soap_res = self._soap_call(wsdl='VilleAgileService', method='getTypesEquipement')
121 73
        tree = lxml.etree.fromstring(soap_res.encode('utf-8')).getroottree()
......
126 78
        return {'data': res}
127 79

  
128 80
    @endpoint(
129
        perm='can_access',
81
        perm='can_access', name='insert-action-comment',
130 82
        post={
131
            'description': _('Insert Demande By Type'),
83
            'description': 'Insert action comment',
132 84
            'request_body': {
133 85
                'schema': {
134
                    'application/json': INSERT_DEMANDE_BY_TYPE_SCHEMA
86
                    'application/json': schemas.INSERT_ACTION_COMMENT
135 87
                }
136 88
            }
137 89
        }
138 90
    )
139
    def insert_demande_by_type(self, request, post_data):
91
    def insert_action_comment(self, request, post_data):
140 92
        demande_number = self._soap_call(
141
            wsdl='DemandeService', method='insertDemandeByType',
142
            contactNom=post_data['contact_nom'],
143
            contactTelephone=post_data['contact_telephone'],
144
            contactCourriel=post_data['contact_email'],
145
            contactAdresse=post_data['contact_adresse'], demandeObjet=post_data['demande_objet'],
146
            demandeLieu=post_data['demande_lieu'],
147
            demandeDescription=post_data['demande_description'],
148
            remoteAddress=post_data['remote_adresse'], codeEquipement=post_data['code_equipement'],
149
            codeServiceDemandeur=post_data['code_service_demandeur'],
150
            dateSouhaitee=post_data['date_souhaite'], typeDemande=post_data['type_demande']
93
            wsdl='DemandeService', method='insertActionComment',
94
            numeroDemande=post_data['numero_demande'],
95
            commentaire=post_data['commentaire']
151 96
        )
152
        return {'data': {'demande_number': demande_number}}
97
        return process_response(demande_number)
98

  
99
    @endpoint(
100
        perm='can_access', name='insert-demande-complet-by-type',
101
        post={
102
            'description': 'Insert demande complet by type',
103
            'request_body': {
104
                'schema': {
105
                    'application/json': schemas.INSERT_DEMANDE_COMPLET_BY_TYPE
106
                }
107
            }
108
        }
109
    )
110
    def insert_demande_complet_by_type(self, request, post_data):
111
        data = {}
112
        for recv, send in [
113
            ('type_demande', 'typeDemande'),
114
            ('code_service_demandeur', 'codeServiceDemandeur'),
115
            ('date_saisie', 'dateSaisie'),
116
            ('date_demande', 'dateDemande'),
117
            ('date_souhaite', 'dateSouhaitee'),
118
            ('date_butoir', 'dateButoir'),
119
            ('contact_civilite', 'contactCivilite'),
120
            ('contact_nom', 'contactNom'),
121
            ('contact_prenom', 'contactPrenom'),
122
            ('contact_tel', 'contactTelephone'),
123
            ('contact_mobile', 'contactMobile'),
124
            ('contact_email', 'contactCourriel'),
125
            ('contact_info_compl', 'contactInfoCompl'),
126
            ('demande_type_support', 'demandeTypeDeSupport'),
127
            ('contact_adresse', 'contactAdresse'),
128
            ('contact_adresse_compl', 'contactAdresseCompl'),
129
            ('contact_code_postal', 'contactCodePostal'),
130
            ('contact_ville', 'contactVille'),
131
            ('contact_organisme', 'contactOrganisme'),
132
            ('contact_titre', 'contactTitre'),
133
            ('code_equipement', 'codeEquipement'),
134
            ('code_mairie_equipement', 'codeMairieEquipement'),
135
            ('code_sig_equipement', 'codeSIGEquipement'),
136
            ('code_collectivite_equipement', 'codeCollectiviteEquipement'),
137
            ('code_quartier_equipement', 'codeQuartierEquipement'),
138
            ('code_type_equipement', 'codeTypeEquipement'),
139
            ('demande_lieu', 'demandeLieu'),
140
            ('coord_x', 'coordX'),
141
            ('coord_y', 'coordY'),
142
            ('demande_priorite', 'demandePriorite'),
143
            ('demande_objet', 'demandeObjet'),
144
            ('demande_description', 'demandeDescription'),
145
            ('demande_commentaire', 'demandeCommentaire'),
146
            ('demande_mots_cles', 'demandeMotsCles'),
147
            ('code_thematique', 'codeThematiqueATAL'),
148
            ('code_priorite', 'codePrioriteATAL'),
149
            ('demande_thematique', 'demandeThematique'),
150
            ('code_projet', 'codeProjetATAL'),
151
        ]:
152
            if recv in post_data:
153
                data[send] = post_data[recv]
154

  
155
        demande_number = self._soap_call(
156
            wsdl='DemandeService', method='insertDemandeCompletByType', **data
157
        )
158
        return process_response(demande_number)
153 159

  
154 160
    @endpoint(
155 161
        methods=['get'], perm='can_access', example_pattern='{demande_number}/',
156
        pattern='^(?P<demande_number>\w+)/$',
162
        pattern='^(?P<demande_number>\w+)/$', name='retrieve-etat-travaux',
157 163
        parameters={
158 164
            'demande_number': {
159 165
                'description': _('Demande number'), 'example_value': 'DIT18050001'
160 166
            }
161 167
        }
162 168
    )
163
    def retrieve_details_demande(self, request, demande_number, **kwargs):
164
        if not demande_number:
165
            raise APIError('A demande_number parameter must be specified')
169
    def retrieve_etat_travaux(self, request, demande_number):
166 170
        soap_res = self._soap_call(
167
            wsdl='DemandeService', method='retrieveDetailsDemande',
168
            demandeNumberParam=demande_number)
171
            wsdl='DemandeService', method='retrieveEtatTravaux',
172
            numero=demande_number)
169 173
        return {'data': helpers.serialize_object(soap_res)}
174

  
175
    @endpoint(
176
        perm='can_access',
177
        post={
178
            'description': 'Upload a file',
179
            'request_body': {
180
                'schema': {
181
                    'application/json': schemas.UPLOAD
182
                }
183
            }
184
        }
185
    )
186
    def upload(self, request, post_data):
187
        try:
188
            content = base64.b64decode(post_data['file']['content'])
189
        except TypeError:
190
            raise APIError('Invalid base64 string')
191

  
192
        data = {
193
            'donneesFichier': content,
194
            'numeroDemande': post_data['numero_demande'],
195
            'nomFichier': post_data['nom_fichier']
196
        }
197
        self._soap_call(
198
            wsdl='ChargementPiecesJointesService', method='upload',
199
            **data
200
        )
201
        return {}
passerelle/apps/atal/schemas.py
1
# -*- coding: utf-8 -*-
2

  
3
# passerelle - uniform access to multiple data sources and services
4
# Copyright (C) 2019  Entr'ouvert
5
#
6
# This program is free software: you can redistribute it and/or modify it
7
# under the terms of the GNU Affero General Public License as published
8
# by the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18

  
19

  
20
INSERT_DEMANDE_COMPLET_BY_TYPE = {
21
    '$schema': 'http://json-schema.org/draft-03/schema#',
22
    'type': 'object',
23
    'additionalProperties': False,
24
    'properties': {
25
        'type_demande': {
26
            'type': 'string',
27
            'required': True,
28
        },
29
        'code_service_demandeur': {
30
            'type': 'string',
31
        },
32
        'date_saisie': {
33
            'type': 'string',
34
        },
35
        'date_demande': {
36
            'type': 'string',
37
        },
38
        'date_souhaite': {
39
            'type': 'string',
40
        },
41
        'date_butoir': {
42
            'type': 'string',
43
        },
44
        'contact_civilite': {
45
            'type': 'string',
46
        },
47
        'contact_nom': {
48
            'type': 'string',
49
        },
50
        'contact_prenom': {
51
            'type': 'string',
52
        },
53
        'contact_tel': {
54
            'type': 'string',
55
        },
56
        'contact_mobile': {
57
            'type': 'string',
58
        },
59
        'contact_email': {
60
            'type': 'string',
61
        },
62
        'contact_info_compl': {
63
            'type': 'string',
64
        },
65
        'demande_type_support': {
66
            'type': 'string',
67
        },
68
        'contact_adresse': {
69
            'type': 'string',
70
        },
71
        'contact_adresse_compl': {
72
            'type': 'string',
73
        },
74
        'contact_code_postal': {
75
            'type': 'string',
76
        },
77
        'contact_ville': {
78
            'type': 'string',
79
        },
80
        'contact_organisme': {
81
            'type': 'string',
82
        },
83
        'contact_titre': {
84
            'type': 'string',
85
        },
86
        'code_equipement': {
87
            'type': 'string',
88
        },
89
        'code_mairie_equipement': {
90
            'type': 'string',
91
        },
92
        'code_sig_equipement': {
93
            'type': 'string',
94
        },
95
        'code_collectivite_equipement': {
96
            'type': 'string',
97
        },
98
        'code_quartier_equipement': {
99
            'type': 'string',
100
        },
101
        'code_type_equipement': {
102
            'type': 'string',
103
        },
104
        'demande_lieu': {
105
            'type': 'string',
106
        },
107
        'coord_x': {
108
            'type': 'string',
109
        },
110
        'coord_y': {
111
            'type': 'string',
112
        },
113
        'demande_priorite': {
114
            'type': 'string',
115
        },
116
        'demande_objet': {
117
            'type': 'string',
118
        },
119
        'demande_description': {
120
            'type': 'string',
121
        },
122
        'demande_commentaire': {
123
            'type': 'string',
124
        },
125
        'remote_adresse': {
126
            'type': 'string'
127
        },
128
        'demande_mots_cles': {
129
            'type': 'string'
130
        },
131
        'code_thematique': {
132
            'type': 'string',
133
        },
134
        'code_priorite': {
135
            'type': 'string'
136
        },
137
        'demande_thematique': {
138
            'type': 'string'
139
        },
140
        'code_projet': {
141
            'type': 'string'
142
        }
143
    }
144
}
145

  
146
INSERT_ACTION_COMMENT = {
147
    '$schema': 'http://json-schema.org/draft-03/schema#',
148
    'type': 'object',
149
    'properties': {
150
        'numero_demande': {
151
            'type': 'string',
152
            'required': True,
153
        },
154
        'commentaire': {
155
            'type': 'string',
156
            'required': True,
157
        }
158
    }
159
}
160

  
161
UPLOAD = {
162
    '$schema': 'http://json-schema.org/draft-03/schema#',
163
    'definitions': {
164
        'file': {
165
            'type': 'object',
166
            'properties': {
167
                'content': {
168
                    'type': 'string',
169
                    'required': True
170
                },
171
            },
172
            'required': True
173
        }
174
    },
175
    'type': 'object',
176
    'properties': {
177
        'file': {
178
            '$ref': '#/definitions/file'
179
        },
180
        'numero_demande': {
181
            'type': 'string',
182
            'required': True,
183
        },
184
        'nom_fichier': {
185
            'type': 'string',
186
            'required': True,
187
        }
188
    }
189
}
tests/test_atal.py
1
import base64
2

  
1 3
from django.contrib.contenttypes.models import ContentType
2 4
import mock
3 5
import pytest
......
42 44

  
43 45
def test_get_type_activite(app, connector, monkeypatch):
44 46
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS)
45
    response = app.get('/atal/slug-atal/get_type_activite')
47
    response = app.get('/atal/slug-atal/get-type-activite')
46 48
    assert response.json == {
47 49
        'err': 0,
48 50
        'data': [
......
56 58

  
57 59
def test_get_type_de_voie(app, connector, monkeypatch):
58 60
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=REFS)
59
    response = app.get('/atal/slug-atal/get_type_de_voie')
61
    response = app.get('/atal/slug-atal/get-type-de-voie')
60 62
    assert response.json == {
61 63
        'err': 0,
62 64
        'data': [
......
77 79
</types>
78 80
"""
79 81
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=return_value)
80
    response = app.get('/atal/slug-atal/get_types_equipement')
82
    response = app.get('/atal/slug-atal/get-types-equipement')
81 83
    assert response.json == {
82 84
        'err': 0,
83 85
        'data': [
......
89 91
    assert call_params['method'] == 'getTypesEquipement'
90 92

  
91 93

  
92
def test_insert_demande_by_type(app, connector, monkeypatch):
94
def test_insert_demande_complet_by_type(app, connector, monkeypatch):
93 95
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value='DIT19050001')
94 96
    params = {
95
        'contact_nom': 'John Doe',
96
        'contact_telephone': '0101010101',
97
        'contact_email': 'john@doe.com',
98
        'contact_adresse': '1 doe street',
99
        'demande_objet': 'sarah connor',
100
        'demande_lieu': 'LA',
101
        'demande_description': 'poker face',
102
        'remote_adresse': 'hollywood bd',
103
        'code_equipement': 'MAC10',
104
        'code_service_demandeur': 'skynet',
105
        'date_souhaite': 'now',
106
        'type_demande': 'scary'
97
        'numero_demande': 'DIT19050001',
98
        'commentaire': 'aaa'
107 99
    }
108
    response = app.post_json('/atal/slug-atal/insert_demande_by_type', params=params)
100
    response = app.post_json('/atal/slug-atal/insert-action-comment', params=params)
109 101
    assert response.json == {
110 102
        'err': 0,
111 103
        'data': {'demande_number': 'DIT19050001'}
112 104
    }
113 105
    call_params = mock_soap_call.call_args.kwargs
114 106
    assert call_params['wsdl'] == 'DemandeService'
115
    assert call_params['method'] == 'insertDemandeByType'
116
    assert call_params['contactNom'] == 'John Doe'
117
    assert call_params['contactTelephone'] == '0101010101'
118
    assert call_params['contactCourriel'] == 'john@doe.com'
119
    assert call_params['contactAdresse'] == '1 doe street'
120
    assert call_params['demandeObjet'] == 'sarah connor'
121
    assert call_params['demandeLieu'] == 'LA'
122
    assert call_params['demandeDescription'] == 'poker face'
123
    assert call_params['remoteAddress'] == 'hollywood bd'
124
    assert call_params['codeEquipement'] == 'MAC10'
125
    assert call_params['codeServiceDemandeur'] == 'skynet'
126
    assert call_params['dateSouhaitee'] == 'now'
127
    assert call_params['typeDemande'] == 'scary'
128

  
129

  
130
def test_retrieve_details_demande(app, connector, monkeypatch):
131
    mock_soap_call = mock_atal_soap_call(
132
        monkeypatch, return_value=dict(code='code1', libelle='elem1'))
133
    response = app.get('/atal/slug-atal/retrieve_details_demande/DIT19050001/')
107
    assert call_params['method'] == 'insertActionComment'
108
    assert call_params['numeroDemande'] == 'DIT19050001'
109
    assert call_params['commentaire'] == 'aaa'
110

  
111

  
112
def test_upload(app, connector, monkeypatch):
113
    mock_soap_call = mock_atal_soap_call(monkeypatch, return_value=None)
114
    base64_str = 'eyJsYXN0X2NoZWNrIjoiMjAxOS0wNC0xMFQxMjowODoyOVoiL' + \
115
        'CJweXBpX3ZlcnNpb24iOiIxOS4wLjMifQ=='
116
    params = {
117
        'numero_demande': 'DIT19050001',
118
        'nom_fichier': 'data.json',
119
        'file': {
120
            'content': base64_str
121
        }
122
    }
123
    response = app.post_json('/atal/slug-atal/upload', params=params)
134 124
    assert response.json == {
135
        'err': 0,
136
        'data': {'code': 'code1', 'libelle': 'elem1'}
125
        'err': 0
137 126
    }
138 127
    call_params = mock_soap_call.call_args.kwargs
139
    assert call_params['wsdl'] == 'DemandeService'
140
    assert call_params['method'] == 'retrieveDetailsDemande'
141
    assert call_params['demandeNumberParam'] == 'DIT19050001'
128
    assert call_params['wsdl'] == 'ChargementPiecesJointesService'
129
    assert call_params['method'] == 'upload'
130
    assert call_params['numeroDemande'] == 'DIT19050001'
131
    assert call_params['nomFichier'] == 'data.json'
132
    assert call_params['donneesFichier'] == base64.b64decode(base64_str)
133

  
134
    params = {
135
        'numero_demande': 'DIT19050001',
136
        'nom_fichier': 'data.json',
137
        'file': {
138
            'content': 'invalidbase64'
139
        }
140
    }
141
    response = app.post_json('/atal/slug-atal/upload', params=params)
142
    assert response.json == {
143
        'data': None,
144
        'err': 1,
145
        'err_class': 'passerelle.utils.jsonresponse.APIError',
146
        'err_desc': 'Invalid base64 string'
147
    }
142
-