Projet

Général

Profil

0005-toulouse_smart-add-create-intervention-endpoint-5523.patch

Nicolas Roche, 19 juillet 2021 10:22

Télécharger (26,2 ko)

Voir les différences:

Subject: [PATCH 5/7] toulouse_smart: add create-intervention endpoint (#55230)

 .../migrations/0002_auto_20210718_1258.py     |  63 +++++
 passerelle/contrib/toulouse_smart/models.py   | 113 +++++++++
 passerelle/contrib/toulouse_smart/schemas.py  | 125 ++++++++++
 .../toulouse_smart/create_intervention.json   |  54 +++++
 tests/test_toulouse_smart.py                  | 218 +++++++++++++++++-
 5 files changed, 572 insertions(+), 1 deletion(-)
 create mode 100644 passerelle/contrib/toulouse_smart/migrations/0002_auto_20210718_1258.py
 create mode 100644 passerelle/contrib/toulouse_smart/schemas.py
 create mode 100644 tests/data/toulouse_smart/create_intervention.json
passerelle/contrib/toulouse_smart/migrations/0002_auto_20210718_1258.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2021-07-18 10:58
3
from __future__ import unicode_literals
4

  
5
import uuid
6

  
7
import django.contrib.postgres.fields.jsonb
8
import django.db.models.deletion
9
from django.db import migrations, models
10

  
11

  
12
class Migration(migrations.Migration):
13

  
14
    dependencies = [
15
        ('toulouse_smart', '0001_initial'),
16
    ]
17

  
18
    operations = [
19
        migrations.CreateModel(
20
            name='WcsRequest',
21
            fields=[
22
                ('wcs_form_url', models.CharField(max_length=256, primary_key=True, serialize=False)),
23
                ('wcs_form_number', models.CharField(max_length=16)),
24
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
25
                ('payload', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
26
                ('result', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
27
                (
28
                    'status',
29
                    models.CharField(
30
                        choices=[
31
                            ('registered', 'Registered'),
32
                            ('sent', 'Sent'),
33
                            ('updating', 'Updating'),
34
                            ('failed', 'Failed'),
35
                            ('updated', 'Updated'),
36
                        ],
37
                        default='received',
38
                        max_length=20,
39
                    ),
40
                ),
41
                ('trigger', models.CharField(blank=True, max_length=64, null=True, verbose_name='Trigger')),
42
                (
43
                    'resource',
44
                    models.ForeignKey(
45
                        on_delete=django.db.models.deletion.CASCADE,
46
                        related_name='wcs_requests',
47
                        to='toulouse_smart.ToulouseSmartResource',
48
                        verbose_name='WcsRequest',
49
                    ),
50
                ),
51
            ],
52
        ),
53
        migrations.AlterField(
54
            model_name='cache',
55
            name='resource',
56
            field=models.ForeignKey(
57
                on_delete=django.db.models.deletion.CASCADE,
58
                related_name='cache_entries',
59
                to='toulouse_smart.ToulouseSmartResource',
60
                verbose_name='Resource',
61
            ),
62
        ),
63
    ]
passerelle/contrib/toulouse_smart/models.py
10 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU Affero General Public License for more details.
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import datetime
18
from uuid import uuid4
18 19

  
19 20
import lxml.etree as ET
20 21
from django.contrib.postgres.fields import JSONField
21 22
from django.db import models
22 23
from django.utils.text import slugify
23 24
from django.utils.timezone import now
24 25
from django.utils.translation import ugettext_lazy as _
25 26
from requests import RequestException
26 27

  
27 28
from passerelle.base.models import BaseResource, HTTPResource
28 29
from passerelle.utils import xml
29 30
from passerelle.utils.api import endpoint
30 31
from passerelle.utils.jsonresponse import APIError
31 32

  
33
from . import schemas
34

  
32 35

  
33 36
class ToulouseSmartResource(BaseResource, HTTPResource):
34 37
    category = _('Business Process Connectors')
35 38

  
36 39
    webservice_base_url = models.URLField(_('Webservice Base URL'))
37 40

  
38 41
    log_requests_errors = False
39 42

  
......
131 134
        },
132 135
    )
133 136
    def get_intervention(self, request, id):
134 137
        url = self.webservice_base_url + 'v1/intervention/%s' % id
135 138
        response = self.request(url)
136 139
        doc = ET.fromstring(response.content)
137 140
        return {'data': xml.to_json(doc)}
138 141

  
142
    @endpoint(
143
        name='create-intervention',
144
        methods=['post'],
145
        description=_('Create an intervention'),
146
        perm='can_access',
147
        post={'request_body': {'schema': {'application/json': schemas.CREATE_SCHEMA}}},
148
    )
149
    def create_intervention(self, request, post_data):
150
        slug = post_data['slug']
151
        try:
152
            types = [x for x in self.get_intervention_types() if slugify(x['name']) == slug]
153
        except KeyError:
154
            raise APIError('Service is unavailable')
155
        if len(types) == 0:
156
            raise APIError("unknown '%s' block slug" % slug, http_status=400)
157
        intervention_type = types[0]
158
        wcs_block_varname = slugify(intervention_type['name']).replace('-', '_')
159
        try:
160
            block = post_data['fields']['%s_raw' % wcs_block_varname][0]
161
        except:
162
            raise APIError("cannot find '%s' block field content" % slug, http_status=400)
163
        data = {}
164
        cast = {'string': str, 'int': int, 'boolean': bool, 'item': str}
165
        for prop in intervention_type['properties']:
166
            name = prop['name'].lower()
167
            if block.get(name):
168
                data[prop['name']] = cast[prop['type']](block[name])
169
            elif prop['required']:
170
                raise APIError("'%s' field is required on '%s' block" % (name, slug), http_status=400)
171

  
172
        wcs_request, created = self.wcs_requests.update_or_create(
173
            defaults={
174
                'wcs_form_url': post_data['form_url'],
175
                'wcs_form_number': post_data['external_number'],
176
            }
177
        )
178
        if wcs_request.status != 'received':
179
            raise APIError(
180
                "'%s' intervention already created: '%s' status"
181
                % (
182
                    post_data['external_number'],
183
                    wcs_request.status,
184
                )
185
            )
186
        notificationUrl = 'update-intervention?uuid=%s' % wcs_request.uuid
187
        payload = {
188
            'description': post_data['description'],
189
            'cityId': post_data['cityId'],
190
            'interventionCreated': post_data['interventionCreated'] + 'Z',
191
            'interventionDesired': post_data['interventionDesired'] + 'Z',
192
            'submitterFirstName': post_data['submitterFirstName'],
193
            'submitterLastName': post_data['submitterLastName'],
194
            'submitterMail': post_data['submitterMail'],
195
            'submitterPhone': post_data['submitterPhone'],
196
            'submitterAddress': post_data['submitterAddress'],
197
            'submitterType': post_data['submitterType'],
198
            'external_number': post_data['external_number'],
199
            'external_status': post_data['external_status'],
200
            'address': post_data['address'],
201
            'interventionData': data,
202
            'geom': {
203
                'type': 'Point',
204
                'coordinates': [post_data['lon'], post_data['lat']],
205
                'crs': 'EPSG:4326',
206
            },
207
            'interventionTypeId': intervention_type['id'],
208
            'notificationUrl': notificationUrl,
209
        }
210

  
211
        url = self.webservice_base_url + 'v1/intervention'
212
        response = self.request(url, json=payload)
213
        try:
214
            result = response.json()
215
        except ValueError:
216
            raise APIError('invalid json, got: %s' % response.text)
217

  
218
        wcs_request.payload = payload
219
        wcs_request.result = result
220
        wcs_request.status = 'sent'
221
        wcs_request.save()
222
        return {'data': result, 'notificationUrl': notificationUrl}
223

  
139 224

  
140 225
class Cache(models.Model):
141 226
    resource = models.ForeignKey(
142 227
        verbose_name=_('Resource'),
143 228
        to=ToulouseSmartResource,
144 229
        on_delete=models.CASCADE,
145 230
        related_name='cache_entries',
146 231
    )
147 232

  
148 233
    key = models.CharField(_('Key'), max_length=64)
149 234

  
150 235
    timestamp = models.DateTimeField(_('Timestamp'), auto_now=True)
151 236

  
152 237
    value = JSONField(_('Value'), default=dict)
238

  
239

  
240
class WcsRequest(models.Model):
241
    resource = models.ForeignKey(
242
        verbose_name=_('WcsRequest'),
243
        to=ToulouseSmartResource,
244
        on_delete=models.CASCADE,
245
        related_name='wcs_requests',
246
    )
247

  
248
    wcs_form_url = models.CharField(max_length=256, primary_key=True)
249
    wcs_form_number = models.CharField(max_length=16)
250
    uuid = models.UUIDField(default=uuid4, unique=True, editable=False)
251
    payload = JSONField(default=dict)
252
    result = JSONField(default=dict)
253
    status = models.CharField(_('Status'), default='received', max_length=64)
254
    status = models.CharField(
255
        max_length=20,
256
        default='received',
257
        choices=(
258
            ('registered', _('Registered')),
259
            ('sent', _('Sent')),
260
            ('updating', _('Updating')),
261
            ('failed', _('Failed')),
262
            ('updated', _('Updated')),
263
        ),
264
    )
265
    trigger = models.CharField(_('Trigger'), blank=True, null=True, max_length=64)
passerelle/contrib/toulouse_smart/schemas.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2021 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
CREATE_SCHEMA = {
19
    '$schema': 'http://json-schema.org/draft-04/schema#',
20
    "type": "object",
21
    'properties': {
22
        'slug': {
23
            'description': "slug du block de champs intervention",
24
            'type': 'string',
25
        },
26
        'description': {
27
            'description': "Description de la demande",
28
            'type': 'string',
29
        },
30
        'lat': {
31
            'description': 'Latitude',
32
            'type': 'number',
33
        },
34
        'lon': {
35
            'description': 'Longitude',
36
            'type': 'number',
37
        },
38
        'cityId': {
39
            'description': 'Code INSEE de la commune',
40
            'type': 'string',
41
        },
42
        'safeguardRequired': {
43
            'description': 'Présence d’un danger ?',
44
            'type': 'boolean',
45
        },
46
        'interventionCreated': {
47
            'description': 'Date de création de la demande',
48
            'type': 'string',
49
            'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}',
50
        },
51
        'interventionDesired': {
52
            'description': 'Date d’intervention souhaitée',
53
            'type': 'string',
54
            'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}',
55
        },
56
        'submitterFirstName': {
57
            'description': 'Prénom du demandeur',
58
            'type': 'string',
59
        },
60
        'submitterLastName': {
61
            'description': 'Nom du demandeur',
62
            'type': 'string',
63
        },
64
        'submitterMail': {
65
            'description': 'Adresse mail du demandeur',
66
            'type': 'string',
67
        },
68
        'submitterPhone': {
69
            'description': 'Numéro de téléphone du demandeur',
70
            'type': 'string',
71
        },
72
        'submitterAddress': {
73
            'description': 'Adresse du demandeur',
74
            'type': 'string',
75
        },
76
        'submitterType': {
77
            'description': 'Type de demandeur (Commune, élu, usager…)',
78
            'type': 'string',
79
        },
80
        'onPrivateLand': {
81
            'description': 'Intervention sur le domaine privé ?',
82
            'type': 'boolean',
83
        },
84
        'checkDuplicated': {
85
            'description': 'Activation de la détection de doublon (laisser à false pour le moment)',
86
            'type': 'boolean',
87
        },
88
        'external_number': {
89
            'description': 'Numéro externe de la demande (numéro Publik : {{ form_number }})',
90
            'type': 'string',
91
            'pattern': '[0-9]+[0-9]+',
92
        },
93
        'external_status': {
94
            'description': 'Statut externe de la demande (statut Publik : {{ form_status }})',
95
            'type': 'string',
96
        },
97
        'address': {
98
            'description': 'Adresse de la demande (démarche dans Publik : {{ form_backoffice_url }})',
99
            'type': 'string',
100
        },
101
        'form_url': {
102
            'description': "L'adresse vers la vue (front-office) du formulaire : {{ form_url }}",
103
            'type': 'string',
104
        },
105
    },
106
    'required': [
107
        'slug',
108
        'description',
109
        'lat',
110
        'lon',
111
        'interventionCreated',
112
        'interventionDesired',
113
        'submitterFirstName',
114
        'submitterLastName',
115
        'submitterMail',
116
        'submitterPhone',
117
        'submitterAddress',
118
        'submitterType',
119
        'external_number',
120
        'external_status',
121
        'cityId',
122
        'address',
123
    ],
124
    'merge_extra': True,
125
}
tests/data/toulouse_smart/create_intervention.json
1
{
2
  "id": "96bc8712-21a6-4fba-a511-740d6f1bd0bc",
3
  "name": "DI-20210707-0031",
4
  "description": "truc",
5
  "geom": {
6
    "type": "Point",
7
    "coordinates": [
8
      4.8546695709228525,
9
      45.75129501117154
10
    ],
11
    "crs": "EPSG:3943"
12
  },
13
  "interventionData": {},
14
  "safeguardRequired": false,
15
  "media": null,
16
  "safeguardDone": null,
17
  "interventionCreated": "2021-07-07T12:19:31.302Z",
18
  "interventionDesired": "2021-06-30T16:08:05Z",
19
  "interventionDone": null,
20
  "submitter": {
21
    "id": null,
22
    "lastName": "admin",
23
    "firstName": "admin12",
24
    "mail": "admin@localhost",
25
    "furtherInformation": null,
26
    "phoneNumber": "0699999999",
27
    "address": ""
28
  },
29
  "submitterType": "\u00e9lu",
30
  "organizations": [
31
    {
32
      "id": "f1378d8a-12bf-4c14-913f-22624b0ecab8",
33
      "name": "Direction des P\u00f4les"
34
    },
35
    {
36
      "id": "8ad4af63-70b5-416f-a75d-c510d83ce1bd",
37
      "name": "Transport Logistique"
38
    }
39
  ],
40
  "domain": null,
41
  "state": {
42
    "id": "e844e67f-5382-4c0f-94d8-56f618263485",
43
    "table": null,
44
    "stateLabel": "Nouveau",
45
    "closes": false
46
  },
47
  "interventionTypeId": "f72d370c-4d25-489f-956e-2a0d48433789",
48
  "onPrivateLand": false,
49
  "duplicates": null,
50
  "external_number": "174-4",
51
  "external_status": "statut1",
52
  "cityId": "12345",
53
  "address": "https://wcs.example.com/backoffice/management/foo/2/"
54
}
tests/test_toulouse_smart.py
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU Affero General Public License for more details.
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import functools
18 18
import io
19
import json
19 20
import os
21
import uuid
20 22
import zipfile
23
from copy import deepcopy
21 24

  
22 25
import httmock
23 26
import lxml.etree as ET
27
import mock
24 28
import pytest
25 29
import utils
26 30
from test_manager import login
27 31

  
28
from passerelle.contrib.toulouse_smart.models import ToulouseSmartResource
32
from passerelle.contrib.toulouse_smart.models import ToulouseSmartResource, WcsRequest
29 33

  
30 34
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'toulouse_smart')
31 35

  
32 36

  
33 37
@pytest.fixture
34 38
def smart(db):
35 39
    return utils.make_resource(
36 40
        ToulouseSmartResource,
......
48 52
        @httmock.urlmatch()
49 53
        def error(url, request):
50 54
            assert False, 'request to %s' % url.geturl()
51 55

  
52 56
        def register(path, payload, content, status_code=200):
53 57
            @httmock.urlmatch(path=path)
54 58
            def handler(url, request):
55 59
                if payload and json.loads(request.body) != payload:
60
                    import pdb
61

  
62
                    pdb.set_trace()
56 63
                    assert False, 'wrong payload sent to request to %s' % url.geturl()
57 64
                return httmock.response(status_code, content)
58 65

  
59 66
            return handler
60 67

  
61 68
        @functools.wraps(func)
62 69
        def wrapper(*args, **kwargs):
63 70
            handlers = []
......
73 80
    return decorator
74 81

  
75 82

  
76 83
def get_xml_file(filename):
77 84
    with open(os.path.join(TEST_BASE_DIR, "%s.xml" % filename), 'rb') as desc:
78 85
        return desc.read()
79 86

  
80 87

  
88
def get_json_file(filename):
89
    with open(os.path.join(TEST_BASE_DIR, "%s.json" % filename)) as desc:
90
        return desc.read()
91

  
92

  
81 93
@mock_response(['/v1/type-intervention', None, b'<List></List>'])
82 94
def test_empty_intervention_types(smart):
83 95
    assert smart.get_intervention_types() == []
84 96

  
85 97

  
86 98
INTERVENTION_TYPES = '''<List>
87 99
   <item>
88 100
       <id>1234</id>
......
224 236
@mock_response(
225 237
    ['/v1/intervention', None, None, 404],
226 238
)
227 239
def test_get_intervention_wrond_id(app, smart):
228 240
    resp = app.get(URL + 'get-intervention?id=3f0558bd-7d85-49a8-97e4-d07bc7f8dc9b')
229 241
    assert resp.json['err']
230 242
    assert 'failed to get' in resp.json['err_desc']
231 243
    assert '404' in resp.json['err_desc']
244

  
245

  
246
CREATE_INTERVENTION_PAYLOAD_EXTRA = {
247
    'slug': 'coin',
248
    'description': 'coin coin',
249
    'lat': 48.833708,
250
    'lon': 2.323349,
251
    'cityId': '12345',
252
    'interventionCreated': '2021-06-30T16:08:05',
253
    'interventionDesired': '2021-06-30T16:08:05',
254
    'submitterFirstName': 'John',
255
    'submitterLastName': 'Doe',
256
    'submitterMail': 'john.doe@example.com',
257
    'submitterPhone': '0123456789',
258
    'submitterAddress': '3 rue des champs de blés',
259
    'submitterType': 'usager',
260
    'external_number': '42-2',
261
    'external_status': 'statut-1-wcs',
262
    'address': 'https://wcs.example.com/backoffice/management/foo/2/',
263
    'form_url': 'https://wcs.example.com/foo/2/',
264
}
265

  
266

  
267
FIELDS_PAYLOAD = {
268
    'coin_raw': [
269
        {
270
            'field1': 'Candélabre',
271
            'field1_raw': 'Candélabre',
272
            'field2': '42',
273
        },
274
    ],
275
}
276

  
277

  
278
CREATE_INTERVENTION_PAYLOAD = {
279
    'fields': FIELDS_PAYLOAD,
280
    'extra': CREATE_INTERVENTION_PAYLOAD_EXTRA,
281
}
282

  
283
UUID = uuid.UUID('12345678123456781234567812345678')
284

  
285
CREATE_INTERVENTION_QUERY = {
286
    'description': 'coin coin',
287
    'cityId': '12345',
288
    'interventionCreated': '2021-06-30T16:08:05Z',
289
    'interventionDesired': '2021-06-30T16:08:05Z',
290
    'submitterFirstName': 'John',
291
    'submitterLastName': 'Doe',
292
    'submitterMail': 'john.doe@example.com',
293
    'submitterPhone': '0123456789',
294
    'submitterAddress': '3 rue des champs de bl\u00e9s',
295
    'submitterType': 'usager',
296
    'external_number': '42-2',
297
    'external_status': 'statut-1-wcs',
298
    'address': 'https://wcs.example.com/backoffice/management/foo/2/',
299
    'interventionData': {'FIELD1': 'Candélabre', 'FIELD2': 42},
300
    'geom': {'type': 'Point', 'coordinates': [2.323349, 48.833708], 'crs': 'EPSG:4326'},
301
    'interventionTypeId': '1234',
302
    'notificationUrl': 'update-intervention?uuid=%s' % str(UUID),
303
}
304

  
305

  
306
@mock_response(
307
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
308
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
309
)
310
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
311
def test_create_intervention(mocked_uuid4, app, smart):
312
    with pytest.raises(WcsRequest.DoesNotExist):
313
        smart.wcs_requests.get(uuid=UUID)
314
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
315
    assert str(UUID) in CREATE_INTERVENTION_QUERY['notificationUrl']
316
    assert not resp.json['err']
317
    assert resp.json['data']['id'] == '96bc8712-21a6-4fba-a511-740d6f1bd0bc'
318

  
319
    wcs_request = smart.wcs_requests.get(uuid=UUID)
320
    assert wcs_request.wcs_form_url == 'https://wcs.example.com/foo/2/'
321
    assert wcs_request.wcs_form_number == '42-2'
322
    assert wcs_request.payload == CREATE_INTERVENTION_QUERY
323
    assert wcs_request.result == json.loads(get_json_file('create_intervention'))
324
    assert wcs_request.status == 'sent'
325

  
326
    # cannot be done twice
327
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
328
    assert resp.json['err']
329
    assert 'already created' in resp.json['err_desc']
330

  
331
    # except for error recovery
332
    wcs_request.status = 'received'
333
    wcs_request.save()
334
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
335
    assert not resp.json['err']
336
    wcs_request = smart.wcs_requests.get(uuid=UUID)
337
    assert wcs_request.status == 'sent'
338

  
339

  
340
def test_create_intervention_wrong_payload(app, smart):
341
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
342
    del payload['extra']['slug']
343
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
344
    assert resp.json['err']
345
    assert "'slug' is a required property" in resp.json['err_desc']
346

  
347

  
348
@mock_response()
349
def test_create_intervention_types_unavailable(app, smart):
350
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
351
    assert resp.json['err']
352
    assert 'Service is unavailable' in resp.json['err_desc']
353

  
354

  
355
@mock_response(
356
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
357
)
358
def test_create_intervention_wrong_block_slug(app, smart):
359
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
360
    payload['extra']['slug'] = 'coin-coin'
361
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
362
    assert resp.json['err']
363
    assert "unknown 'coin-coin' block slug" in resp.json['err_desc']
364

  
365

  
366
@mock_response(
367
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
368
)
369
def test_create_intervention_no_block(app, smart):
370
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
371
    del payload['fields']['coin_raw']
372
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
373
    assert resp.json['err']
374
    assert "cannot find 'coin' block field content" in resp.json['err_desc']
375

  
376

  
377
@mock_response(
378
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
379
)
380
def test_create_intervention_missing_value(app, smart):
381
    field_payload = {
382
        'coin_raw': [
383
            {
384
                'field1': 'Candélabre',
385
                'field1_raw': 'Candélabre',
386
                'field2': None,
387
            },
388
        ],
389
    }
390
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
391
    payload['fields'] = field_payload
392
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
393
    assert resp.json['err']
394
    assert "field is required on 'coin' block" in resp.json['err_desc']
395

  
396

  
397
@mock_response(
398
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
399
)
400
def test_create_intervention_missing_field(app, smart):
401
    field_payload = {
402
        'coin_raw': [
403
            {
404
                'field1': 'Candélabre',
405
                'field1_raw': 'Candélabre',
406
            },
407
        ],
408
    }
409
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
410
    payload['fields'] = field_payload
411
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
412
    assert resp.json['err']
413
    assert "field is required on 'coin' block" in resp.json['err_desc']
414

  
415

  
416
@mock_response(
417
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
418
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, None, 500],
419
)
420
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
421
def test_create_intervention_error_status(mocked_uuid, app, smart):
422
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
423
    assert resp.json['err']
424
    assert 'failed to post' in resp.json['err_desc']
425

  
426
    assert smart.wcs_requests.count() == 1
427
    wcs_request = smart.wcs_requests.get(uuid=UUID)
428
    assert wcs_request.status == 'received'
429

  
430

  
431
@mock_response(
432
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
433
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, 'not json content'],
434
)
435
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
436
def test_create_intervention_error_content(mocked_uuid, app, smart):
437
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
438
    assert resp.json['err']
439
    assert 'invalid json' in resp.json['err_desc']
440

  
441
    assert smart.wcs_requests.count() == 1
442
    wcs_request = smart.wcs_requests.get(uuid=UUID)
443
    assert wcs_request.status == 'received'
444

  
445
    # same uuid is re-used after an error
446
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
447
    assert smart.wcs_requests.count() == 1
232
-