Projet

Général

Profil

0001-lille_kimoce-initial-connector-33099.patch

Voir les différences:

Subject: [PATCH] lille_kimoce: initial connector (#33099)

 passerelle/contrib/lille_kimoce/__init__.py   |   0
 .../lille_kimoce/migrations/0001_initial.py   |  33 +
 .../lille_kimoce/migrations/__init__.py       |   0
 passerelle/contrib/lille_kimoce/models.py     | 232 +++++++
 passerelle/utils/http_authenticators.py       |  15 +
 tests/settings.py                             |   1 +
 tests/test_lille_kimoce.py                    | 591 ++++++++++++++++++
 7 files changed, 872 insertions(+)
 create mode 100644 passerelle/contrib/lille_kimoce/__init__.py
 create mode 100644 passerelle/contrib/lille_kimoce/migrations/0001_initial.py
 create mode 100644 passerelle/contrib/lille_kimoce/migrations/__init__.py
 create mode 100644 passerelle/contrib/lille_kimoce/models.py
 create mode 100644 tests/test_lille_kimoce.py
passerelle/contrib/lille_kimoce/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.20 on 2019-05-13 08:21
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    initial = True
11

  
12
    dependencies = [
13
        ('base', '0012_job'),
14
    ]
15

  
16
    operations = [
17
        migrations.CreateModel(
18
            name='Kimoce',
19
            fields=[
20
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
                ('title', models.CharField(max_length=50, verbose_name='Title')),
22
                ('description', models.TextField(verbose_name='Description')),
23
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
24
                ('base_url', models.URLField(help_text='API base URL', max_length=256, verbose_name='Base URL')),
25
                ('username', models.CharField(max_length=128, verbose_name='Username')),
26
                ('password', models.CharField(max_length=128, verbose_name='Password')),
27
                ('users', models.ManyToManyField(blank=True, to='base.ApiUser')),
28
            ],
29
            options={
30
                'verbose_name': 'Lille Kimoce',
31
            },
32
        ),
33
    ]
passerelle/contrib/lille_kimoce/models.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2019  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17

  
18
from six.moves.urllib_parse import urljoin
19

  
20
from django.db import models
21
from django.utils.translation import ugettext_lazy as _
22
from django.core.cache import cache
23

  
24
from passerelle.base.models import BaseResource
25
from passerelle.utils.api import endpoint
26
from passerelle.utils.http_authenticators import HttpBearerAuth
27
from passerelle.utils.jsonresponse import APIError
28

  
29

  
30
DEMAND_SCHEMA = {
31
    '$schema': 'http://json-schema.org/draft-03/schema#',
32
    'title': 'KIMOCE',
33
    'description': '',
34
    'type': 'object',
35
    'properties': {
36
        'category': {
37
            'description': 'demand category',
38
            'type': 'string',
39
            'required': True
40
        },
41
        'type': {
42
            'description': 'demand type',
43
            'type': 'string',
44
            'required': True
45
        },
46
        'subtype': {
47
            'description': 'demand sub type',
48
            'type': 'string',
49
            'required': True
50
        },
51
        'form_url': {
52
            'description': 'form url',
53
            'type': 'string',
54
            'required': True
55
        },
56
        'first_name': {
57
            'description': 'user first name',
58
            'type': 'string',
59
            'required': True
60
        },
61
        'last_name': {
62
            'description': 'user last name',
63
            'type': 'string',
64
            'required': True
65
        },
66
        'email': {
67
            'description': 'user email',
68
            'type': 'string',
69
            'required': True
70
        },
71
        'priorityId': {
72
            'description': 'demand priority',
73
            'type': 'integer',
74
            'required': False
75
        },
76
        'city': {
77
            'description': 'demand city',
78
            'type': 'string',
79
            'required': False
80
        },
81
        'zipcode': {
82
            'description': 'demand zipcode',
83
            'type': 'string',
84
            'required': False
85
        },
86
        'street_number': {
87
            'description': 'demand street number',
88
            'type': 'string',
89
            'required': False
90
        },
91
        'street_name': {
92
            'description': 'demand street name',
93
            'type': 'string',
94
            'required': False
95
        },
96
        'lat': {
97
            'description': 'demand latitude',
98
            'type': 'string',
99
            'required': False
100
        },
101
        'lon': {
102
            'description': 'demand longitude',
103
            'type': 'string',
104
            'required': False
105
        },
106
        'picture1': {
107
            'description': 'first picture base64 content',
108
            'type': 'any',
109
            'required': False
110
        },
111
        'picture2': {
112
            'description': 'second picture base64 content',
113
            'type': 'any',
114
            'required': False
115
        }
116
    }
117
}
118

  
119

  
120
class Kimoce(BaseResource):
121
    base_url = models.URLField(max_length=256, blank=False,
122
                               verbose_name=_('Base URL'),
123
                               help_text=_('API base URL'))
124
    username = models.CharField(max_length=128, verbose_name=_('Username'))
125
    password = models.CharField(max_length=128, verbose_name=_('Password'))
126

  
127
    category = _('Business Process Connectors')
128

  
129
    class Meta:
130
        verbose_name = 'Lille Kimoce'
131

  
132
    @classmethod
133
    def get_verbose_name(cls):
134
        return cls._meta.verbose_name
135

  
136
    def get_token(self, renew=False):
137
        token_key = 'lille-kimoce-%s-token' % self.id
138
        if not renew and cache.get(token_key):
139
            return cache.get(token_key)
140
        url = urljoin(self.base_url, 'login_check')
141
        resp = self.requests.post(url, json={'username': self.username, 'password': self.password})
142
        if not resp.ok:
143
            raise APIError(resp.content)
144
        token = resp.json()['token']
145
        cache.set(token_key, token, 3600)
146
        return token
147

  
148
    def request(self, endpoint, params=None):
149
        url = urljoin(self.base_url, endpoint)
150
        data = []
151
        response = self.requests.get(url, params=params,
152
                            auth=HttpBearerAuth(self.get_token()))
153
        if response.status_code == 401:
154
            response = self.requests.get(url, params=params,
155
                                auth=HttpBearerAuth(self.get_token(renew=True)))
156
        if response.ok:
157
            for member in response.json()['hydra:member']:
158
                data.append({'id': member['@id'],
159
                             'text': member['label'],
160
                             'number': member['id']})
161
        return {'data': data}
162

  
163
    def check_status(self):
164
        response = self.requests.get(urljoin(self.base_url, 'categories'))
165
        response.raise_for_status()
166

  
167
    @endpoint(perm='can_access', description=_('List categories'))
168
    def categories(self, request, *args, **kwargs):
169
        return self.request('categories')
170

  
171
    @endpoint(perm='can_access', description=_('List types'))
172
    def types(self, request, category_id):
173
        return self.request('types', {'category.id': category_id})
174

  
175
    @endpoint(perm='can_access', description=_('List subtypes'))
176
    def subtypes(self, request, type_id):
177
        return self.request('sub_types', {'types.id': type_id})
178

  
179
    @endpoint(perm='can_access', description=_('List streets'))
180
    def streets(self, request, q):
181
        url = urljoin(self.base_url, 'company_locations')
182
        data = []
183
        # parentId is a flag to filter street names only
184
        response = self.requests.get(url, params={'streetAddress': q, 'parentId': 0},
185
                                     auth=HttpBearerAuth(self.get_token()))
186
        if response.status_code == 401:
187
            response = self.requests.get(url, params={'streetAddress': q, 'parentId': 0},
188
                                     auth=HttpBearerAuth(self.get_token(renew=True)))
189
        if response.ok:
190
            for member in response.json()['hydra:member']:
191
                data.append({'id': member['@id'],
192
                             'text': member['streetAddress']})
193
        return {'data': data}
194

  
195
    @endpoint(perm='can_access', description=_('Create demand'), post={
196
        'description': _('Create demand into KIMOCE'),
197
        'request_body': {
198
            'schema': {
199
                'application/json': DEMAND_SCHEMA
200
            }
201
        }
202
    })
203
    def create_demand(self, request, post_data):
204
        payload = {'category': post_data['category'],
205
                   'type': post_data['type'],
206
                   'subType': post_data['subtype'],
207
                   'priorityId': post_data.get('priorityId', 3),
208
                   'companyLocation': {
209
                       'number': post_data.get('street_number', ''),
210
                       'road': post_data.get('street_name', ''),
211
                       'city': post_data.get('city', ''),
212
                       'zipCode': post_data.get('zipcode', ''),
213
                   },
214
                   'sourceContact': {'firstname': post_data['first_name'],
215
                                     'lastname': post_data['last_name'],
216
                                     'mail': post_data['email']},
217
                   'pictures': [],
218
                   'GRUResponseLink': post_data['form_url']
219
        }
220
        if post_data.get('lat') and post_data.get('lon'):
221
            payload['coordinate'] = {'latitude': post_data['lat'],
222
                                     'longitude': post_data['lon']}
223
        for param_name in ('picture1', 'picture2'):
224
            if post_data.get(param_name):
225
                payload['pictures'].append({'content': post_data[param_name]['content']})
226
        url = urljoin(self.base_url, 'demands')
227
        result = self.requests.post(url, json=payload, auth=HttpBearerAuth(self.get_token()))
228
        if result.status_code  == 401:
229
            result = self.requests.post(url, json=payload, auth=HttpBearerAuth(self.get_token(renew=True)))
230
        if result.ok:
231
            return {'demand_id': result.json()['@id']}
232
        raise APIError(result.content)
passerelle/utils/http_authenticators.py
68 68
    def __call__(self, r):
69 69
        r.headers['Authorization'] = self.get_authorization_header(r)
70 70
        return r
71

  
72

  
73
class HttpBearerAuth(AuthBase):
74
    def __init__(self, token):
75
        self.token = token
76

  
77
    def __eq__(self, other):
78
        return self.token == getattr(other, 'token', None)
79

  
80
    def __ne__(self, other):
81
        return not self == other
82

  
83
    def __call__(self, r):
84
        r.headers['Authorization'] = 'Bearer ' + self.token
85
        return r
tests/settings.py
38 38
    'passerelle.contrib.teamnet_axel',
39 39
    'passerelle.contrib.tlmcom',
40 40
    'passerelle.contrib.tcl',
41
    'passerelle.contrib.lille_kimoce',
41 42
)
42 43

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

  
18

  
19
import os
20
import mock
21
import pytest
22
import six
23

  
24
import utils
25

  
26
from django.core.urlresolvers import reverse
27

  
28
from passerelle.contrib.lille_kimoce.models import Kimoce
29
from passerelle.utils.jsonresponse import APIError
30

  
31
@pytest.fixture
32
def setup(db):
33
    return utils.setup_access_rights(
34
        Kimoce.objects.create(slug='test',
35
                              base_url='https://kimoce.mairie-lille.fr/api/',
36
                              username='test', password='secret'))
37

  
38

  
39
CATEGORIES_RESPONSE = """{
40
  "@context": "/api/contexts/Category",
41
  "@id": "/api/categories",
42
  "@type": "hydra:Collection",
43
  "hydra:member": [
44
    {"@id": "/api/categories/2",
45
     "id": 2,
46
     "label": "Accessoire répertorié",
47
     "reference": "ARE"
48
    },
49
    {"@id": "/api/categories/3",
50
     "id": 3,
51
     "label": "Appareil photo numérique",
52
     "reference": "PHT"
53
    },
54
    {"@id": "/api/categories/4",
55
     "id": 4,
56
     "label": "Ecran",
57
    "reference": "ECR"
58
    },
59
    {"@id": "/api/categories/5",
60
     "id": 5,
61
     "label": "Imprimante",
62
     "reference": "IMP"
63
    },
64
    {"@id": "/api/categories/6",
65
     "id": 6,
66
     "label": "Progiciel/Logiciel",
67
    "reference": "PRG"
68
    },
69
    {"@id": "/api/categories/11",
70
     "id": 11,
71
     "label": "Périphérique",
72
     "reference": "PHM"
73
    },
74
    {"@id": "/api/categories/12",
75
     "id": 12,
76
     "label": "Périphérique réseau",
77
     "reference": "PHR"
78
    },
79
    {"@id": "/api/categories/13",
80
     "id": 13,
81
     "label": "Ordinateur Portable",
82
     "reference": "POR"
83
    },
84
    {"@id": "/api/categories/14",
85
     "id": 14,
86
     "label": "Scanner",
87
     "reference": "SCA"
88
    },
89
    {"@id": "/api/categories/15",
90
     "id": 15,
91
     "label": "Serveur",
92
     "reference": "SER"
93
    },
94
    {"@id": "/api/categories/16",
95
     "id": 16,
96
     "label": "Station graphique",
97
     "reference": "STG"
98
    },
99
    {"@id": "/api/categories/17",
100
     "id": 17,
101
     "label": "Tablet PC",
102
     "reference": "TPC"
103
    },
104
    {"@id": "/api/categories/18",
105
     "id": 18,
106
     "label": "Unité centrale",
107
     "reference": "UNC"
108
    },
109
    {"@id": "/api/categories/19",
110
     "id": 19,
111
     "label": "Vidéo projecteur",
112
     "reference": "VPJ"
113
    },
114
    {"@id": "/api/categories/20",
115
     "id": 20,
116
     "label": "Achats",
117
     "reference": "PRE"
118
    },
119
    {"@id": "/api/categories/21",
120
     "id": 21,
121
     "label": "Catalogue de Service",
122
     "reference": "GEN"
123
    },
124
    {"@id": "/api/categories/24",
125
     "id": 24,
126
     "label": "GSM",
127
     "reference": "GSM"
128
    },
129
    {"@id": "/api/categories/26",
130
     "id": 26,
131
     "label": "Carte SIM",
132
     "reference": "SIM"
133
    },
134
    {"@id": "/api/categories/31",
135
     "id": 31,
136
     "label": "Intervention / Information",
137
     "reference": "P01"
138
    },
139
    {"@id": "/api/categories/32",
140
     "id": 32,
141
     "label": "Fourniture Sel",
142
     "reference": "P02"
143
    },
144
    {"@id": "/api/categories/33",
145
     "id": 33,
146
     "label": "Fourniture récipients",
147
     "reference": "P03"
148
    },
149
    {"@id": "/api/categories/34",
150
     "id": 34,
151
     "label": "Fourniture support Sac",
152
     "reference": "P04"
153
    },
154
    {"@id": "/api/categories/35",
155
     "id": 35,
156
     "label": "Nettoiement et Enlèvement dépôt",
157
     "reference": "P05"
158
    },
159
    {"@id": "/api/categories/36",
160
     "id": 36,
161
     "label": "Brigade",
162
     "reference": "P06"
163
    },
164
    {"@id": "/api/categories/37",
165
     "id": 37,
166
     "label": "Consommable Bureautique",
167
     "reference": "CONSO1"
168
    },
169
    {"@id": "/api/categories/39",
170
     "id": 39,
171
     "label": "Photocopieurs",
172
     "reference": "PHO"
173
    },
174
    {"@id": "/api/categories/40",
175
     "id": 40,
176
     "label": "Téléphone Fixe",
177
     "reference": "TLF"
178
    },
179
    {"@id": "/api/categories/42",
180
     "id": 42,
181
     "label": "Fax",
182
     "reference": "FAX"
183
    },
184
    {"@id": "/api/categories/43",
185
     "id": 43,
186
     "label": "Radio-PMR",
187
     "reference": "RAD"
188
    },
189
    {"@id": "/api/categories/67",
190
     "id": 67,
191
     "label": "Consomable Réseau",
192
     "reference": "CONSO2"
193
    },
194
    {"@id": "/api/categories/68",
195
     "id": 68,
196
     "label": "Monétique",
197
     "reference": "TPE"
198
    },
199
    {"@id": "/api/categories/69",
200
     "id": 69,
201
     "label": "Borne tactile",
202
     "reference": "BMQ"
203
    },
204
    {"@id": "/api/categories/70",
205
     "id": 70,
206
     "label": "Table surface",
207
     "reference": "TBS"
208
    },
209
    {"@id": "/api/categories/73",
210
     "id": 73,
211
     "label": "Tableau Blanc Interactif",
212
     "reference": "TBI"
213
    },
214
    {"@id": "/api/categories/76",
215
     "id": 76,
216
     "label": "Arbres-Espaces verts",
217
     "reference": "W01"
218
    },
219
    {"@id": "/api/categories/77",
220
     "id": 77,
221
     "label": "Mobilier urbain - Jeux pour enfants",
222
     "reference": "W02"
223
    },
224
    {"@id": "/api/categories/78",
225
     "id": 78,
226
     "label": "Panneau-Signalisation-Stationnement",
227
     "reference": "W03"
228
    },
229
    {"@id": "/api/categories/79",
230
     "id": 79,
231
     "label": "Eclairage public",
232
     "reference": "W04"
233
    },
234
    {"@id": "/api/categories/80",
235
     "id": 80,
236
     "label": "Propreté-Déchets-Tags",
237
     "reference": "W05"
238
    },
239
    {"@id": "/api/categories/81",
240
     "id": 81,
241
     "label": "Etat de la voirie-Assainissement",
242
     "reference": "W06"
243
    },
244
    {"@id": "/api/categories/82",
245
     "id": 82,
246
     "label": "Travaux-Chantiers-Terrasse",
247
     "reference": "W07"
248
    },
249
    {"@id": "/api/categories/83",
250
     "id": 83,
251
     "label": "Mur Ecran",
252
     "reference": "MUR"
253
    },
254
    {"@id": "/api/categories/84",
255
     "id": 84,
256
     "label": "Gestion de conflits en temps réel",
257
     "reference": "MD1"
258
    },
259
    {"@id": "/api/categories/85",
260
     "id": 85,
261
     "label": "Gestion de conflits en temps différ",
262
     "reference": "MD2"
263
    }
264
  ],
265
  "hydra:totalItems": 44
266
}"""
267

  
268
TYPES_RESPONSE = """{
269
  "@context": "/api/contexts/Type",
270
  "@id": "/api/types",
271
  "@type": "hydra:Collection",
272
  "hydra:member": [
273
    {"@id": "/api/types/1825",
274
     "@type": "Type",
275
     "id": 1825,
276
     "label": "Tags"
277
    },
278
    {"@id": "/api/types/1826",
279
     "@type": "Type",
280
     "id": 1826,
281
     "label": "Dépôt sauvage"
282
    },
283
    {"@id": "/api/types/1827",
284
     "@type": "Type",
285
     "id": 1827,
286
     "label": "Poubelle, Corbeille publique de rue"
287
    },
288
    {"@id": "/api/types/1828",
289
     "@type": "Type",
290
     "id": 1828,
291
     "label": "Affichage sauvage"
292
    },
293
    {"@id": "/api/types/1829",
294
     "@type": "Type",
295
     "id": 1829,
296
     "label": "Rue,Espace sales,Déchets éparpillés"
297
    },
298
    {"@id": "/api/types/1830",
299
     "@type": "Type",
300
     "id": 1830,
301
     "label": "Poubelle de particuliers"
302
    },
303
    {"@id": "/api/types/1831",
304
     "@type": "Type",
305
     "id": 1831,
306
     "label": "Mauvaises herbes, Désherbage"
307
    },
308
    {"@id": "/api/types/1832",
309
     "@type": "Type",
310
     "id": 1832,
311
     "label": "Demande exceptionelle de nettoyage"
312
    },
313
    {"@id": "/api/types/1875",
314
     "@type": "Type",
315
     "id": 1875,
316
     "label": "Pigeons, Animaux"
317
    },
318
    {"@id": "/api/types/2013",
319
     "@type": "Type",
320
     "id": 2013,
321
     "label": "Déjections canines"
322
    },
323
    {"@id": "/api/types/2014",
324
     "@type": "Type",
325
     "id": 2014,
326
     "label": "Sol glissant"
327
    },
328
    {"@id": "/api/types/2015",
329
     "@type": "Type",
330
     "id": 2015,
331
     "label": "Mauvaise odeur"
332
    },
333
    {"@id": "/api/types/2088",
334
     "@type": "Type",
335
     "id": 2088,
336
     "label": "Rats"
337
    }
338
  ],
339
  "hydra:totalItems": 13
340
}"""
341

  
342
SUB_TYPES_RESPONSE = """{
343
  "@context": "/api/contexts/SubType",
344
  "@id": "/api/sub_types",
345
  "@type": "hydra:Collection",
346
  "hydra:member": [
347
    {"@id": "/api/sub_types/163",
348
     "@type": "SubType",
349
     "id": 163,
350
     "label": "Tag classique"
351
    },
352
    {"@id": "/api/sub_types/164",
353
     "@type": "SubType",
354
     "id": 164,
355
     "label": "Tag injurieux, tendancieux"
356
    }
357
  ],
358
  "hydra:totalItems": 2
359
}"""
360

  
361
STREETS_RESPONSE = """{
362
  "@context": "/api/contexts/CompanyLocation",
363
  "@id": "/api/company_locations",
364
  "@type": "hydra:Collection",
365
  "hydra:member": [
366
    {"@id": "/api/company_locations/4368",
367
     "@type": "CompanyLocation",
368
     "streetAddress": "PLACE JOSEPH HENTGES, H"
369
    },
370
    {"@id": "/api/company_locations/7550",
371
     "@type": "CompanyLocation",
372
     "streetAddress": "9 PLACE JOSEPH HENTGES"
373
    },
374
    {"@id": "/api/company_locations/7551",
375
     "@type": "CompanyLocation",
376
     "streetAddress": "5 PLACE JOSEPH HENTGES"
377
    },
378
    {"@id": "/api/company_locations/7552",
379
     "@type": "CompanyLocation",
380
     "streetAddress": "3 PLACE JOSEPH HENTGES"
381
    },
382
    {"@id": "/api/company_locations/7553",
383
     "@type": "CompanyLocation",
384
     "streetAddress": "1 PLACE JOSEPH HENTGES"
385
    },
386
    {"@id": "/api/company_locations/7554",
387
     "@type": "CompanyLocation",
388
     "streetAddress": "7 PLACE JOSEPH HENTGES"
389
    },
390
    {"@id": "/api/company_locations/7555",
391
     "@type": "CompanyLocation",
392
     "streetAddress": "181 PLACE JOSEPH HENTGES"
393
    },
394
    {"@id": "/api/company_locations/46455",
395
     "@type": "CompanyLocation",
396
     "streetAddress": "2 PLACE JOSEPH HENTGES"
397
    },
398
    {"@id": "/api/company_locations/46456",
399
     "@type": "CompanyLocation",
400
     "streetAddress": "25 PLACE JOSEPH HENTGES"
401
    }
402
  ],
403
  "hydra:totalItems": 9
404
}"""
405

  
406
DEMAND_CREATION_RESPONSE = """{
407
    "@context": "/api/contexts/Demand",
408
    "@id": "/api/demands/166961",
409
    "@type": "Demand",
410
    "category": "/api/categories/33",
411
    "coordinate": {
412
        "latitude": 3.50895,
413
        "longitude": 50.340892
414
    },
415
    "pictures": [],
416
    "priorityId": 3,
417
    "subType": "/api/sub_types/17",
418
    "type": "/api/types/916",
419
    "sourceContact": {
420
        "firstname": "Adrien",
421
        "lastname": "MILLOT",
422
        "mail": "adrien.millot@smile.fr"
423
    },
424
    "companyLocation": {
425
        "@id": "/api/company_locations/3656",
426
        "@type": "CompanyLocation",
427
        "city": "lille",
428
        "number": "55 bis",
429
        "road": "squares du portugal",
430
        "zipCode": "59000"
431
    }
432
}"""
433

  
434
TOKEN_RESPONSE = """{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"}"""
435

  
436
TOKEN_ERROR_RESPONSE = """{"message": "Bad credentials"}"""
437

  
438
@mock.patch('passerelle.utils.Request.post')
439
def test_get_token(mocked_post, app, setup):
440
    with pytest.raises(APIError):
441
        mocked_post.return_value = utils.FakedResponse(content=TOKEN_ERROR_RESPONSE, ok=False)
442
        setup.get_token()
443
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
444
    setup.get_token()
445
    assert mocked_post.call_count == 2
446
    print mocked_post.call_args
447
    assert "api/login_check" in mocked_post.call_args[0][0]
448
    assert mocked_post.call_args[1]['json']['username'] == 'test'
449
    assert mocked_post.call_args[1]['json']['password'] == 'secret'
450
    # make sure the token from cache is used
451
    setup.get_token()
452
    assert mocked_post.call_count == 2
453
    setup.get_token(True)
454
    assert mocked_post.call_count == 3
455

  
456
@mock.patch('passerelle.utils.Request.post')
457
@mock.patch('passerelle.utils.Request.get')
458
def test_get_categories(mocked_get, mocked_post, app, setup):
459
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
460
    mocked_get.return_value = utils.FakedResponse(ok=True, content=CATEGORIES_RESPONSE)
461
    endpoint = reverse(
462
        'generic-endpoint',
463
        kwargs={
464
            'connector': 'lille-kimoce',
465
            'slug': setup.slug,
466
            'endpoint': 'categories'
467
        }
468
    )
469
    response = app.get(endpoint)
470
    assert 'data' in response.json
471
    for item in response.json['data']:
472
        assert 'id' in item
473
        assert 'text' in item
474
        assert 'number' in item
475

  
476

  
477
@mock.patch('passerelle.utils.Request.post')
478
@mock.patch('passerelle.utils.Request.get')
479
def test_get_types(mocked_get, mocked_post, app, setup):
480
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
481
    mocked_get.return_value = utils.FakedResponse(ok=True, content=TYPES_RESPONSE)
482
    endpoint = reverse('generic-endpoint',
483
        kwargs={'connector': 'lille-kimoce',
484
                'slug': setup.slug,
485
                'endpoint': 'types'
486
        }
487
    )
488
    response = app.get(endpoint, status=400)
489
    assert response.json['err']
490
    assert response.json['err_desc'] == 'missing parameters: \'category_id\'.'
491
    assert response.json['err_class'] == 'passerelle.views.WrongParameter'
492
    response = app.get(endpoint, params={'category_id': 80})
493
    assert 'data' in response.json
494
    for item in response.json['data']:
495
        assert 'id' in item
496
        assert 'text' in item
497
        assert 'number' in item
498

  
499

  
500
@mock.patch('passerelle.utils.Request.post')
501
@mock.patch('passerelle.utils.Request.get')
502
def test_get_sub_types(mocked_get, mocked_post, app, setup):
503
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
504
    mocked_get.return_value = utils.FakedResponse(ok=True, content=SUB_TYPES_RESPONSE)
505
    endpoint = reverse('generic-endpoint',
506
        kwargs={'connector': 'lille-kimoce',
507
                'slug': setup.slug,
508
                'endpoint': 'subtypes'
509
        }
510
    )
511
    response = app.get(endpoint, status=400)
512
    assert response.json['err']
513
    assert response.json['err_desc'] == 'missing parameters: \'type_id\'.'
514
    assert response.json['err_class'] == 'passerelle.views.WrongParameter'
515
    response = app.get(endpoint, params={'type_id': 1825})
516
    assert 'data' in response.json
517
    for item in response.json['data']:
518
        assert 'id' in item
519
        assert 'text' in item
520
        assert 'number' in item
521

  
522

  
523
@mock.patch('passerelle.utils.Request.post')
524
@mock.patch('passerelle.utils.Request.get')
525
def test_get_streets(mocked_get, mocked_post, app, setup):
526
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
527
    mocked_get.return_value = utils.FakedResponse(ok=True, content=STREETS_RESPONSE)
528
    endpoint = reverse('generic-endpoint',
529
        kwargs={'connector': 'lille-kimoce',
530
                'slug': setup.slug,
531
                'endpoint': 'streets'
532
        }
533
    )
534
    response = app.get(endpoint, status=400)
535
    assert response.json['err']
536
    assert response.json['err_desc'] == 'missing parameters: \'q\'.'
537
    assert response.json['err_class'] == 'passerelle.views.WrongParameter'
538
    response = app.get(endpoint, params={'q': 'PLACE JosEPH'})
539
    assert 'data' in response.json
540
    for item in response.json['data']:
541
        assert 'id' in item
542
        assert 'text' in item
543

  
544

  
545
@mock.patch('passerelle.utils.Request.post')
546
def test_create_demand(mocked_post, app, setup):
547
    mocked_post.side_effect = [
548
        utils.FakedResponse(content=TOKEN_RESPONSE, ok=True),
549
        utils.FakedResponse(ok=True, content=DEMAND_CREATION_RESPONSE)]
550
    data = {
551
        'category': '/api/categories/80',
552
        'type': '/api/types/1825',
553
        'subtype': '/api/sub_types/164',
554
        'form_url': 'http://example.com/form/1/',
555
        'first_name': 'Foo',
556
        'last_name': 'Bar',
557
        'email': 'foo@example.net',
558
    }
559
    endpoint = reverse('generic-endpoint',
560
        kwargs={'connector': 'lille-kimoce',
561
                'slug': setup.slug,
562
                'endpoint': 'create_demand'
563
        }
564
    )
565
    response = app.post_json(endpoint, params=data)
566
    assert mocked_post.call_args[0][0] == 'https://kimoce.mairie-lille.fr/api/demands'
567
    assert mocked_post.call_args[1]['json'] == {'GRUResponseLink': 'http://example.com/form/1/',
568
                    'priorityId': 3, 'type': '/api/types/1825',
569
                                               'category': '/api/categories/80',
570
                    'companyLocation': {'number': '',
571
                                'road': '',
572
                                'zipCode': '',
573
                                'city': ''},
574
                    'pictures': [],
575
                    'subType': '/api/sub_types/164',
576
                    'sourceContact': {'firstname': 'Foo',
577
                                      'mail': 'foo@example.net',
578
                                      'lastname': u'Bar'}
579
    }
580
    assert response.json['demand_id'] == '/api/demands/166961'
581
    assert mocked_post.call_count == 2
582
    data['picture1'] = {'content': 'base64encoded_picture'}
583
    data['lat'] = '48.85438994604021'
584
    data['lon'] = '2.3497223854064946'
585
    mocked_post.return_value = utils.FakedResponse(ok=True, content=DEMAND_CREATION_RESPONSE)
586
    mocked_post.side_effect = None
587
    response = app.post_json(endpoint, params=data)
588
    assert mocked_post.call_count == 3
589
    assert mocked_post.call_args[1]['json']['pictures'][0]['content'] == 'base64encoded_picture'
590
    assert mocked_post.call_args[1]['json']['coordinate']['latitude'] == '48.85438994604021'
591
    assert mocked_post.call_args[1]['json']['coordinate']['longitude'] == '2.3497223854064946'
0
-