Projet

Général

Profil

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

Nicolas Roche (absent jusqu'au 3 avril), 26 juillet 2021 18:08

Télécharger (27,9 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   | 128 ++++++++-
 passerelle/contrib/toulouse_smart/schemas.py  | 126 +++++++++
 .../toulouse_smart/create_intervention.json   |  54 ++++
 tests/test_toulouse_smart.py                  | 254 +++++++++++++++++-
 5 files changed, 621 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
        self.add_job('create_intervention_job', try_now=True, pk=wcs_request.pk)
222
        data = serializers.serialize('python', [wcs_request])[0]
223
        return {'data': {'payload': post_data, 'wcs_request': data}}
224

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

  
139 241

  
140 242
class Cache(models.Model):
141 243
    resource = models.ForeignKey(
142 244
        verbose_name=_('Resource'),
143 245
        to=ToulouseSmartResource,
144 246
        on_delete=models.CASCADE,
145 247
        related_name='cache_entries',
146 248
    )
147 249

  
148 250
    key = models.CharField(_('Key'), max_length=64)
149 251

  
150 252
    timestamp = models.DateTimeField(_('Timestamp'), auto_now=True)
151 253

  
152 254
    value = JSONField(_('Value'), default=dict)
255

  
256

  
257
class WcsRequest(models.Model):
258
    resource = models.ForeignKey(
259
        verbose_name=_('WcsRequest'),
260
        to=ToulouseSmartResource,
261
        on_delete=models.CASCADE,
262
        related_name='wcs_requests',
263
    )
264
    wcs_form_url = models.CharField(max_length=256, primary_key=True)
265
    wcs_form_number = models.CharField(max_length=16)
266
    uuid = models.UUIDField(default=uuid4, unique=True, editable=False)
267
    payload = JSONField(default=dict)
268
    result = JSONField(default=dict)
269
    status = models.CharField(
270
        max_length=20,
271
        default='registered',
272
        choices=(
273
            ('registered', _('Registered')),
274
            ('sent', _('Sent')),
275
            ('service-error', _('Service error')),
276
            ('wcs-error', _('WCS error')),
277
        ),
278
    )
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
    job = Job.objects.get(method_name='create_intervention_job')
326
    assert job.status == 'completed'
327

  
328

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

  
336

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

  
343

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

  
354

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

  
365

  
366
@mock_response(
367
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
368
)
369
def test_create_intervention_cast_error(app, smart):
370
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
371
    payload['fields']['coin_raw'][0]['field2'] = 'not-an-integer'
372
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
373
    assert resp.json['err']
374
    assert "cannot cast 'field2' field to <class 'int'>" 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
)
419
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
420
def test_create_intervention_twice_error(mocked_uuid4, app, smart):
421
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
422
    assert not resp.json['err']
423

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

  
428

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

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

  
449

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

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

  
469

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