Projet

Général

Profil

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

Nicolas Roche, 27 juillet 2021 17:48

Télécharger (28 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   | 133 ++++++++-
 passerelle/contrib/toulouse_smart/schemas.py  | 126 +++++++++
 .../toulouse_smart/create_intervention.json   |  54 ++++
 tests/test_toulouse_smart.py                  | 255 +++++++++++++++++-
 5 files changed, 627 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
22
from django.core import serializers
21 23
from django.db import models
22 24
from django.utils.text import slugify
23 25
from django.utils.timezone import now
24 26
from django.utils.translation import ugettext_lazy as _
25 27
from requests import RequestException
26 28

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

  
34
from . import schemas
35

  
32 36

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

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

  
38 42
    log_requests_errors = False
39 43

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

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

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

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

  
139 246

  
140 247
class Cache(models.Model):
141 248
    resource = models.ForeignKey(
142 249
        verbose_name=_('Resource'),
143 250
        to=ToulouseSmartResource,
144 251
        on_delete=models.CASCADE,
145 252
        related_name='cache_entries',
146 253
    )
147 254

  
148 255
    key = models.CharField(_('Key'), max_length=64)
149 256

  
150 257
    timestamp = models.DateTimeField(_('Timestamp'), auto_now=True)
151 258

  
152 259
    value = JSONField(_('Value'), default=dict)
260

  
261

  
262
class WcsRequest(models.Model):
263
    resource = models.ForeignKey(
264
        verbose_name=_('WcsRequest'),
265
        to=ToulouseSmartResource,
266
        on_delete=models.CASCADE,
267
        related_name='wcs_requests',
268
    )
269
    wcs_form_url = models.CharField(max_length=256, primary_key=True)
270
    wcs_form_number = models.CharField(max_length=16)
271
    uuid = models.UUIDField(default=uuid4, unique=True, editable=False)
272
    payload = JSONField(default=dict)
273
    result = JSONField(default=dict)
274
    status = models.CharField(
275
        max_length=20,
276
        default='registered',
277
        choices=(
278
            ('registered', _('Registered')),
279
            ('sent', _('Sent')),
280
            ('service-error', _('Service error')),
281
            ('wcs-error', _('WCS error')),
282
        ),
283
    )
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>
......
224 234
@mock_response(
225 235
    ['/v1/intervention', None, None, 404],
226 236
)
227 237
def test_get_intervention_wrond_id(app, smart):
228 238
    resp = app.get(URL + 'get-intervention?id=3f0558bd-7d85-49a8-97e4-d07bc7f8dc9b')
229 239
    assert resp.json['err']
230 240
    assert 'failed to get' in resp.json['err_desc']
231 241
    assert '404' in resp.json['err_desc']
242

  
243

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

  
264

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

  
275

  
276
CREATE_INTERVENTION_PAYLOAD = {
277
    'fields': FIELDS_PAYLOAD,
278
    'extra': CREATE_INTERVENTION_PAYLOAD_EXTRA,
279
}
280

  
281
UUID = uuid.UUID('12345678123456781234567812345678')
282

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

  
303

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

  
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']['payload']['form_url'] == 'https://wcs.example.com/foo/2/'
318
    assert resp.json['data']['wcs_request']['fields']['uuid'] == str(UUID)
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

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

  
334

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

  
341

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

  
352

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

  
363

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

  
374

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

  
394

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

  
413

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

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

  
427

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

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

  
452

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

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

  
472

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