Projet

Général

Profil

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

Nicolas Roche, 27 juillet 2021 19:25

Télécharger (28,2 ko)

Voir les différences:

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

 .../migrations/0002_auto_20210726_1648.py     |  61 +++++
 passerelle/contrib/toulouse_smart/models.py   | 140 +++++++++-
 passerelle/contrib/toulouse_smart/schemas.py  | 126 +++++++++
 .../toulouse_smart/create_intervention.json   |  54 ++++
 tests/test_toulouse_smart.py                  | 255 +++++++++++++++++-
 5 files changed, 634 insertions(+), 2 deletions(-)
 create mode 100644 passerelle/contrib/toulouse_smart/migrations/0002_auto_20210726_1648.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_20210726_1648.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2021-07-26 14:48
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
                            ('service-error', 'Service error'),
34
                            ('wcs-error', 'WCS error'),
35
                        ],
36
                        default='registered',
37
                        max_length=20,
38
                    ),
39
                ),
40
                (
41
                    'resource',
42
                    models.ForeignKey(
43
                        on_delete=django.db.models.deletion.CASCADE,
44
                        related_name='wcs_requests',
45
                        to='toulouse_smart.ToulouseSmartResource',
46
                        verbose_name='WcsRequest',
47
                    ),
48
                ),
49
            ],
50
        ),
51
        migrations.AlterField(
52
            model_name='cache',
53
            name='resource',
54
            field=models.ForeignKey(
55
                on_delete=django.db.models.deletion.CASCADE,
56
                related_name='cache_entries',
57
                to='toulouse_smart.ToulouseSmartResource',
58
                verbose_name='Resource',
59
            ),
60
        ),
61
    ]
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
from passerelle.base.models import BaseResource, HTTPResource
28
from passerelle.base.models import BaseResource, HTTPResource, SkipJob
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
        wcs_form_number = post_data['external_number']
152
        try:
153
            types = [x for x in self.get_intervention_types() if slugify(x['name']) == slug]
154
        except KeyError:
155
            raise APIError('Service is unavailable')
156
        if len(types) == 0:
157
            raise APIError("unknown '%s' block slug" % slug, http_status=400)
158
        intervention_type = types[0]
159
        wcs_block_varname = slugify(intervention_type['name']).replace('-', '_')
160
        try:
161
            block = post_data['fields']['%s_raw' % wcs_block_varname][0]
162
        except:
163
            raise APIError("cannot find '%s' block field content" % slug, http_status=400)
164
        data = {}
165
        cast = {'string': str, 'int': int, 'boolean': bool, 'item': str}
166
        for prop in intervention_type['properties']:
167
            name = prop['name'].lower()
168
            if block.get(name):
169
                try:
170
                    data[prop['name']] = cast[prop['type']](block[name])
171
                except ValueError:
172
                    raise APIError(
173
                        "cannot cast '%s' field to %s : '%s'" % (name, cast[prop['type']], block[name]),
174
                        http_status=400,
175
                    )
176
            elif prop['required']:
177
                raise APIError("'%s' field is required on '%s' block" % (name, slug), http_status=400)
178

  
179
        wcs_request, created = self.wcs_requests.update_or_create(
180
            defaults={
181
                'wcs_form_url': post_data['form_url'],
182
                'wcs_form_number': post_data['external_number'],
183
            }
184
        )
185
        if not created:
186
            raise APIError(
187
                "'%s' intervention already created: '%s' status"
188
                % (
189
                    post_data['external_number'],
190
                    wcs_request.status,
191
                ),
192
                http_status=400,
193
            )
194
        wcs_request = self.wcs_requests.get(wcs_form_number=wcs_form_number)
195
        notificationUrl = 'update-intervention?uuid=%s' % wcs_request.uuid
196
        wcs_request.payload = {
197
            'description': post_data['description'],
198
            'cityId': post_data['cityId'],
199
            'interventionCreated': post_data['interventionCreated'] + 'Z',
200
            'interventionDesired': post_data['interventionDesired'] + 'Z',
201
            'submitterFirstName': post_data['submitterFirstName'],
202
            'submitterLastName': post_data['submitterLastName'],
203
            'submitterMail': post_data['submitterMail'],
204
            'submitterPhone': post_data['submitterPhone'],
205
            'submitterAddress': post_data['submitterAddress'],
206
            'submitterType': post_data['submitterType'],
207
            'external_number': post_data['external_number'],
208
            'external_status': post_data['external_status'],
209
            'address': post_data['address'],
210
            'interventionData': data,
211
            'geom': {
212
                'type': 'Point',
213
                'coordinates': [post_data['lon'], post_data['lat']],
214
                'crs': 'EPSG:4326',
215
            },
216
            'interventionTypeId': intervention_type['id'],
217
            'notificationUrl': notificationUrl,
218
        }
219
        wcs_request.save()
220
        try:
221
            self.create_intervention_job(pk=wcs_request.pk)
222
        except SkipJob:
223
            self.add_job('create_intervention_job', pk=wcs_request.pk)
224
        return {
225
            'data': {
226
                'wcs_form_url': wcs_request.wcs_form_url,
227
                'wcs_form_number': wcs_request.wcs_form_number,
228
                'uuid': wcs_request.uuid,
229
                'payload': wcs_request.payload,
230
                'result': wcs_request.result,
231
                'status': wcs_request.status,
232
            }
233
        }
234

  
235
    def create_intervention_job(self, *args, **kwargs):
236
        wcs_request = self.wcs_requests.get(pk=kwargs['pk'])
237
        url = self.webservice_base_url + 'v1/intervention'
238
        try:
239
            response = self.request(url, json=wcs_request.payload)
240
        except APIError as e:
241
            raise SkipJob()
242
        try:
243
            wcs_request.result = response.json()
244
        except ValueError:
245
            wcs_request.status = 'error'
246
            err_desc = 'invalid json, got: %s' % response.text
247
            wcs_request.result = err_desc
248
            wcs_request.save()
249
            raise APIError(err_desc)
250
        wcs_request.status = 'sent'
251
        wcs_request.save()
252

  
139 253

  
140 254
class Cache(models.Model):
141 255
    resource = models.ForeignKey(
142 256
        verbose_name=_('Resource'),
143 257
        to=ToulouseSmartResource,
144 258
        on_delete=models.CASCADE,
145 259
        related_name='cache_entries',
146 260
    )
147 261

  
148 262
    key = models.CharField(_('Key'), max_length=64)
149 263

  
150 264
    timestamp = models.DateTimeField(_('Timestamp'), auto_now=True)
151 265

  
152 266
    value = JSONField(_('Value'), default=dict)
267

  
268

  
269
class WcsRequest(models.Model):
270
    resource = models.ForeignKey(
271
        verbose_name=_('WcsRequest'),
272
        to=ToulouseSmartResource,
273
        on_delete=models.CASCADE,
274
        related_name='wcs_requests',
275
    )
276
    wcs_form_url = models.CharField(max_length=256, primary_key=True)
277
    wcs_form_number = models.CharField(max_length=16)
278
    uuid = models.UUIDField(default=uuid4, unique=True, editable=False)
279
    payload = JSONField(default=dict)
280
    result = JSONField(default=dict)
281
    status = models.CharField(
282
        max_length=20,
283
        default='registered',
284
        choices=(
285
            ('registered', _('Registered')),
286
            ('sent', _('Sent')),
287
            ('service-error', _('Service error')),
288
            ('wcs-error', _('WCS error')),
289
        ),
290
    )
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
        'cityId',
112
        'interventionCreated',
113
        'interventionDesired',
114
        'submitterFirstName',
115
        'submitterLastName',
116
        'submitterMail',
117
        'submitterPhone',
118
        'submitterAddress',
119
        'submitterType',
120
        'external_number',
121
        'external_status',
122
        'address',
123
        'form_url',
124
    ],
125
    'merge_extra': True,
126
}
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.base.models import Job
33
from passerelle.contrib.toulouse_smart.models import ToulouseSmartResource, WcsRequest
29 34

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

  
32 37

  
33 38
@pytest.fixture
34 39
def smart(db):
35 40
    return utils.make_resource(
36 41
        ToulouseSmartResource,
......
73 78
    return decorator
74 79

  
75 80

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

  
80 85

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

  
90

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

  
85 95

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

  
246

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

  
267

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

  
278

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

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

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

  
306

  
307
@mock_response(
308
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
309
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
310
)
311
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
312
def test_create_intervention(mocked_uuid4, app, smart):
313
    assert Job.objects.count() == 0
314
    with pytest.raises(WcsRequest.DoesNotExist):
315
        smart.wcs_requests.get(uuid=UUID)
316

  
317
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
318
    assert str(UUID) in CREATE_INTERVENTION_QUERY['notificationUrl']
319
    assert not resp.json['err']
320
    assert resp.json['data']['uuid'] == str(UUID)
321
    assert resp.json['data']['wcs_form_url'] == 'https://wcs.example.com/foo/2/'
322
    wcs_request = smart.wcs_requests.get(uuid=UUID)
323
    assert wcs_request.wcs_form_url == 'https://wcs.example.com/foo/2/'
324
    assert wcs_request.wcs_form_number == '42-2'
325
    assert wcs_request.payload == CREATE_INTERVENTION_QUERY
326
    assert wcs_request.result == json.loads(get_json_file('create_intervention'))
327
    assert wcs_request.status == 'sent'
328

  
329

  
330
def test_create_intervention_wrong_payload(app, smart):
331
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
332
    del payload['extra']['slug']
333
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
334
    assert resp.json['err']
335
    assert "'slug' is a required property" in resp.json['err_desc']
336

  
337

  
338
@mock_response()
339
def test_create_intervention_types_unavailable(app, smart):
340
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
341
    assert resp.json['err']
342
    assert 'Service is unavailable' in resp.json['err_desc']
343

  
344

  
345
@mock_response(
346
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
347
)
348
def test_create_intervention_wrong_block_slug(app, smart):
349
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
350
    payload['extra']['slug'] = 'coin-coin'
351
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
352
    assert resp.json['err']
353
    assert "unknown 'coin-coin' block slug" in resp.json['err_desc']
354

  
355

  
356
@mock_response(
357
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
358
)
359
def test_create_intervention_no_block(app, smart):
360
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
361
    del payload['fields']['coin_raw']
362
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
363
    assert resp.json['err']
364
    assert "cannot find 'coin' block field content" in resp.json['err_desc']
365

  
366

  
367
@mock_response(
368
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
369
)
370
def test_create_intervention_cast_error(app, smart):
371
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
372
    payload['fields']['coin_raw'][0]['field2'] = 'not-an-integer'
373
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
374
    assert resp.json['err']
375
    assert "cannot cast 'field2' field to <class 'int'>" in resp.json['err_desc']
376

  
377

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

  
397

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

  
416

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

  
426
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD, status=400)
427
    assert resp.json['err']
428
    assert 'already created' in resp.json['err_desc']
429

  
430

  
431
@mock_response(
432
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
433
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, None, 500],
434
)
435
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
436
def test_create_intervention_transport_error(mocked_uuid, app, freezer, smart):
437
    freezer.move_to('2021-07-08 00:00:00')
438
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
439
    assert not resp.json['err']
440
    job = Job.objects.get(method_name='create_intervention_job')
441
    assert job.status == 'registered'
442
    wcs_request = smart.wcs_requests.get(uuid=UUID)
443
    assert wcs_request.status == 'registered'
444
    assert wcs_request.result == {}
445

  
446
    freezer.move_to('2021-07-08 00:00:03')
447
    smart.jobs()
448
    job = Job.objects.get(method_name='create_intervention_job')
449
    assert job.status == 'registered'
450
    assert job.update_timestamp > job.creation_timestamp
451
    wcs_request = smart.wcs_requests.get(uuid=UUID)
452
    assert wcs_request.status == 'registered'
453
    assert wcs_request.result == {}
454

  
455

  
456
@mock_response(
457
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
458
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, None, 500],
459
)
460
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
461
def test_create_intervention_inconsistency_id_error(mocked_uuid4, app, freezer, smart):
462
    freezer.move_to('2021-07-08 00:00:00')
463
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
464
    wcs_request = smart.wcs_requests.get(uuid=UUID)
465
    assert wcs_request.status == 'registered'
466
    job = Job.objects.get(method_name='create_intervention_job')
467
    assert job.status == 'registered'
468

  
469
    freezer.move_to('2021-07-08 00:00:03')
470
    wcs_request.delete()
471
    smart.jobs()
472
    job = Job.objects.get(method_name='create_intervention_job')
473
    assert job.status == 'failed'
474

  
475

  
476
@mock_response(
477
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
478
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, 'not json content'],
479
)
480
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
481
def test_create_intervention_content_error(mocked_uuid, app, freezer, smart):
482
    freezer.move_to('2021-07-08 00:00:00')
483
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
484
    assert resp.json['err']
485
    wcs_request = smart.wcs_requests.get(uuid=UUID)
486
    assert wcs_request.status == 'error'
487
    assert 'invalid json' in wcs_request.result
235
-