Projet

Général

Profil

0001-lille_kimoce-initial-connector-33099.patch

Serghei Mihai, 20 mai 2019 19:30

Télécharger (25 ko)

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     | 238 ++++++++++++
 passerelle/utils/http_authenticators.py       |  15 +
 tests/settings.py                             |   1 +
 tests/test_lille_kimoce.py                    | 344 ++++++++++++++++++
 7 files changed, 631 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 django.db import models
19
from django.utils.translation import ugettext_lazy as _
20
from django.utils.six.moves.urllib_parse import urljoin
21
from django.core.cache import cache
22

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

  
28

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

  
118

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

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

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

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

  
135
    def check_status(self):
136
        url = urljoin(self.base_url, 'login_check')
137
        response = self.requests.post(url, json={'username': self.username,
138
                                                 'password': self.password})
139
        response.raise_for_status()
140

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

  
154
    def get_referential(self, endpoint, params=None):
155
        url = urljoin(self.base_url, endpoint)
156
        data = []
157
        response = self.requests.get(url, params=params,
158
                            auth=HttpBearerAuth(self.get_token()))
159
        if response.status_code == 401:
160
            response = self.requests.get(url, params=params,
161
                                auth=HttpBearerAuth(self.get_token(renew=True)))
162
        if response.ok:
163
            for member in response.json()['hydra:member']:
164
                data.append({'id': member['@id'],
165
                             'text': member['label'],
166
                             'number': member['id']})
167
        return {'data': data}
168

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

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

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

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

  
201
    @endpoint(perm='can_access', description=_('Create demand'), post={
202
        'description': _('Create demand into KIMOCE'),
203
        'request_body': {
204
            'schema': {
205
                'application/json': DEMAND_SCHEMA
206
            }
207
        }
208
    })
209
    def create_demand(self, request, post_data):
210
        payload = {'category': post_data['category'],
211
                   'type': post_data['type'],
212
                   'subType': post_data['subtype'],
213
                   'priorityId': post_data.get('priorityId', 3),
214
                   'companyLocation': {
215
                       'number': post_data.get('street_number', ''),
216
                       'road': post_data.get('street_name', ''),
217
                       'city': post_data.get('city', ''),
218
                       'zipCode': post_data.get('zipcode', ''),
219
                   },
220
                   'sourceContact': {'firstname': post_data['first_name'],
221
                                     'lastname': post_data['last_name'],
222
                                     'mail': post_data['email']},
223
                   'pictures': [],
224
                   'GRUResponseLink': post_data['form_url']
225
        }
226
        if post_data.get('lat') and post_data.get('lon'):
227
            payload['coordinate'] = {'latitude': post_data['lat'],
228
                                     'longitude': post_data['lon']}
229
        for param_name in ('picture1', 'picture2'):
230
            if post_data.get(param_name):
231
                payload['pictures'].append({'content': post_data[param_name]['content']})
232
        url = urljoin(self.base_url, 'demands')
233
        result = self.requests.post(url, json=payload, auth=HttpBearerAuth(self.get_token()))
234
        if result.status_code  == 401:
235
            result = self.requests.post(url, json=payload, auth=HttpBearerAuth(self.get_token(renew=True)))
236
        if result.ok:
237
            return {'data': result.json()}
238
        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 json
20
import os
21
import mock
22
import pytest
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/20",
50
     "id": 20,
51
     "label": "Achats",
52
     "reference": "PRE"
53
    },
54
    {"@id": "/api/categories/70",
55
     "id": 70,
56
     "label": "Table surface",
57
     "reference": "TBS"
58
    },
59
    {"@id": "/api/categories/73",
60
     "id": 73,
61
     "label": "Tableau Blanc Interactif",
62
     "reference": "TBI"
63
    },
64
    {"@id": "/api/categories/80",
65
     "id": 80,
66
     "label": "Propreté-Déchets-Tags",
67
     "reference": "W05"
68
    },
69
    {"@id": "/api/categories/85",
70
     "id": 85,
71
     "label": "Gestion de conflits en temps différ",
72
     "reference": "MD2"
73
    }
74
  ],
75
  "hydra:totalItems": 6
76
}"""
77

  
78
TYPES_RESPONSE = """{
79
  "@context": "/api/contexts/Type",
80
  "@id": "/api/types",
81
  "@type": "hydra:Collection",
82
  "hydra:member": [
83
    {"@id": "/api/types/1825",
84
     "@type": "Type",
85
     "id": 1825,
86
     "label": "Tags"
87
    },
88
    {"@id": "/api/types/1826",
89
     "@type": "Type",
90
     "id": 1826,
91
     "label": "Dépôt sauvage"
92
    },
93
    {"@id": "/api/types/1827",
94
     "@type": "Type",
95
     "id": 1827,
96
     "label": "Poubelle, Corbeille publique de rue"
97
    },
98
    {"@id": "/api/types/1828",
99
     "@type": "Type",
100
     "id": 1828,
101
     "label": "Affichage sauvage"
102
    },
103
    {"@id": "/api/types/1829",
104
     "@type": "Type",
105
     "id": 1829,
106
     "label": "Rue,Espace sales,Déchets éparpillés"
107
    },
108
    {"@id": "/api/types/1830",
109
     "@type": "Type",
110
     "id": 1830,
111
     "label": "Poubelle de particuliers"
112
    }
113
  ],
114
  "hydra:totalItems": 5
115
}"""
116

  
117
SUB_TYPES_RESPONSE = """{
118
  "@context": "/api/contexts/SubType",
119
  "@id": "/api/sub_types",
120
  "@type": "hydra:Collection",
121
  "hydra:member": [
122
    {"@id": "/api/sub_types/163",
123
     "@type": "SubType",
124
     "id": 163,
125
     "label": "Tag classique"
126
    },
127
    {"@id": "/api/sub_types/164",
128
     "@type": "SubType",
129
     "id": 164,
130
     "label": "Tag injurieux, tendancieux"
131
    }
132
  ],
133
  "hydra:totalItems": 2
134
}"""
135

  
136
STREETS_RESPONSE = """{
137
  "@context": "/api/contexts/CompanyLocation",
138
  "@id": "/api/company_locations",
139
  "@type": "hydra:Collection",
140
  "hydra:member": [
141
    {"@id": "/api/company_locations/4368",
142
     "@type": "CompanyLocation",
143
     "streetAddress": "PLACE JOSEPH HENTGES, H"
144
    },
145
    {"@id": "/api/company_locations/7550",
146
     "@type": "CompanyLocation",
147
     "streetAddress": "9 PLACE JOSEPH HENTGES"
148
    },
149
    {"@id": "/api/company_locations/7551",
150
     "@type": "CompanyLocation",
151
     "streetAddress": "5 PLACE JOSEPH HENTGES"
152
    }
153
  ],
154
  "hydra:totalItems": 3
155
}"""
156

  
157
DEMAND_CREATION_RESPONSE = """{
158
    "@context": "/api/contexts/Demand",
159
    "@id": "/api/demands/166961",
160
    "@type": "Demand",
161
    "category": "/api/categories/33",
162
    "coordinate": {
163
        "latitude": 3.50895,
164
        "longitude": 50.340892
165
    },
166
    "pictures": [],
167
    "priorityId": 3,
168
    "subType": "/api/sub_types/17",
169
    "type": "/api/types/916",
170
    "sourceContact": {
171
        "firstname": "Foo",
172
        "lastname": "Bar",
173
        "mail": "foo@example.net"
174
    },
175
    "companyLocation": {
176
        "@id": "/api/company_locations/3656",
177
        "@type": "CompanyLocation",
178
        "city": "lille",
179
        "number": "55 bis",
180
        "road": "squares du portugal",
181
        "zipCode": "59000"
182
    }
183
}"""
184

  
185
TOKEN_RESPONSE = """{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"}"""
186

  
187
TOKEN_ERROR_RESPONSE = """{"message": "Bad credentials"}"""
188

  
189
@mock.patch('passerelle.utils.Request.post')
190
def test_get_token(mocked_post, app, setup):
191
    with pytest.raises(APIError):
192
        mocked_post.return_value = utils.FakedResponse(content=TOKEN_ERROR_RESPONSE, ok=False)
193
        setup.get_token()
194
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
195
    setup.get_token()
196
    assert mocked_post.call_count == 2
197
    print mocked_post.call_args
198
    assert "api/login_check" in mocked_post.call_args[0][0]
199
    assert mocked_post.call_args[1]['json']['username'] == 'test'
200
    assert mocked_post.call_args[1]['json']['password'] == 'secret'
201
    # make sure the token from cache is used
202
    setup.get_token()
203
    assert mocked_post.call_count == 2
204
    setup.get_token(True)
205
    assert mocked_post.call_count == 3
206

  
207
@mock.patch('passerelle.utils.Request.post')
208
@mock.patch('passerelle.utils.Request.get')
209
def test_get_categories(mocked_get, mocked_post, app, setup):
210
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
211
    mocked_get.return_value = utils.FakedResponse(ok=True, content=CATEGORIES_RESPONSE)
212
    endpoint = reverse(
213
        'generic-endpoint',
214
        kwargs={
215
            'connector': 'lille-kimoce',
216
            'slug': setup.slug,
217
            'endpoint': 'categories'
218
        }
219
    )
220
    response = app.get(endpoint)
221
    assert 'data' in response.json
222
    for item in response.json['data']:
223
        assert 'id' in item
224
        assert 'text' in item
225
        assert 'number' in item
226

  
227

  
228
@mock.patch('passerelle.utils.Request.post')
229
@mock.patch('passerelle.utils.Request.get')
230
def test_get_types(mocked_get, mocked_post, app, setup):
231
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
232
    mocked_get.return_value = utils.FakedResponse(ok=True, content=TYPES_RESPONSE)
233
    endpoint = reverse('generic-endpoint',
234
        kwargs={'connector': 'lille-kimoce',
235
                'slug': setup.slug,
236
                'endpoint': 'types'
237
        }
238
    )
239
    response = app.get(endpoint, status=400)
240
    assert response.json['err']
241
    assert response.json['err_desc'] == 'missing parameters: \'category_id\'.'
242
    assert response.json['err_class'] == 'passerelle.views.WrongParameter'
243
    response = app.get(endpoint, params={'category_id': 80})
244
    assert 'data' in response.json
245
    for item in response.json['data']:
246
        assert 'id' in item
247
        assert 'text' in item
248
        assert 'number' in item
249

  
250

  
251
@mock.patch('passerelle.utils.Request.post')
252
@mock.patch('passerelle.utils.Request.get')
253
def test_get_sub_types(mocked_get, mocked_post, app, setup):
254
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
255
    mocked_get.return_value = utils.FakedResponse(ok=True, content=SUB_TYPES_RESPONSE)
256
    endpoint = reverse('generic-endpoint',
257
        kwargs={'connector': 'lille-kimoce',
258
                'slug': setup.slug,
259
                'endpoint': 'subtypes'
260
        }
261
    )
262
    response = app.get(endpoint, status=400)
263
    assert response.json['err']
264
    assert response.json['err_desc'] == 'missing parameters: \'type_id\'.'
265
    assert response.json['err_class'] == 'passerelle.views.WrongParameter'
266
    response = app.get(endpoint, params={'type_id': 1825})
267
    assert 'data' in response.json
268
    for item in response.json['data']:
269
        assert 'id' in item
270
        assert 'text' in item
271
        assert 'number' in item
272

  
273

  
274
@mock.patch('passerelle.utils.Request.post')
275
@mock.patch('passerelle.utils.Request.get')
276
def test_get_streets(mocked_get, mocked_post, app, setup):
277
    mocked_post.return_value = utils.FakedResponse(content=TOKEN_RESPONSE, ok=True)
278
    mocked_get.return_value = utils.FakedResponse(ok=True, content=STREETS_RESPONSE)
279
    endpoint = reverse('generic-endpoint',
280
        kwargs={'connector': 'lille-kimoce',
281
                'slug': setup.slug,
282
                'endpoint': 'streets'
283
        }
284
    )
285
    response = app.get(endpoint)
286
    assert 'data' in response.json
287
    assert len(response.json['data']) == 3
288
    response = app.get(endpoint, params={'q': 'PLACE JosEPH'})
289
    print mocked_get.call_args[1]['params']
290
    assert mocked_get.call_args[1]['params']['streetAddress'] == 'PLACE JosEPH'
291
    response = app.get(endpoint, params={'id': 'PLACE JosEPH'})
292
    assert mocked_get.call_args[1]['params']['streetAddress'] == 'PLACE JosEPH'
293
    for item in response.json['data']:
294
        assert 'id' in item
295
        assert 'text' in item
296

  
297

  
298
@mock.patch('passerelle.utils.Request.post')
299
def test_create_demand(mocked_post, app, setup):
300
    mocked_post.side_effect = [
301
        utils.FakedResponse(content=TOKEN_RESPONSE, ok=True),
302
        utils.FakedResponse(ok=True, content=DEMAND_CREATION_RESPONSE)]
303
    data = {
304
        'category': '/api/categories/80',
305
        'type': '/api/types/1825',
306
        'subtype': '/api/sub_types/164',
307
        'form_url': 'http://example.com/form/1/',
308
        'first_name': 'Foo',
309
        'last_name': 'Bar',
310
        'email': 'foo@example.net',
311
    }
312
    endpoint = reverse('generic-endpoint',
313
        kwargs={'connector': 'lille-kimoce',
314
                'slug': setup.slug,
315
                'endpoint': 'create_demand'
316
        }
317
    )
318
    response = app.post_json(endpoint, params=data)
319
    assert mocked_post.call_args[0][0] == 'https://kimoce.mairie-lille.fr/api/demands'
320
    assert mocked_post.call_args[1]['json'] == {'GRUResponseLink': 'http://example.com/form/1/',
321
                    'priorityId': 3, 'type': '/api/types/1825',
322
                                               'category': '/api/categories/80',
323
                    'companyLocation': {'number': '',
324
                                'road': '',
325
                                'zipCode': '',
326
                                'city': ''},
327
                    'pictures': [],
328
                    'subType': '/api/sub_types/164',
329
                    'sourceContact': {'firstname': 'Foo',
330
                                      'mail': 'foo@example.net',
331
                                      'lastname': u'Bar'}
332
    }
333
    assert response.json['data'] == json.loads(DEMAND_CREATION_RESPONSE)
334
    assert mocked_post.call_count == 2
335
    data['picture1'] = {'content': 'base64encoded_picture'}
336
    data['lat'] = '48.85438994604021'
337
    data['lon'] = '2.3497223854064946'
338
    mocked_post.return_value = utils.FakedResponse(ok=True, content=DEMAND_CREATION_RESPONSE)
339
    mocked_post.side_effect = None
340
    response = app.post_json(endpoint, params=data)
341
    assert mocked_post.call_count == 3
342
    assert mocked_post.call_args[1]['json']['pictures'][0]['content'] == 'base64encoded_picture'
343
    assert mocked_post.call_args[1]['json']['coordinate']['latitude'] == '48.85438994604021'
344
    assert mocked_post.call_args[1]['json']['coordinate']['longitude'] == '2.3497223854064946'
0
-