Projet

Général

Profil

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

Nicolas Roche, 02 août 2021 16:18

Télécharger (29,8 ko)

Voir les différences:

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

 .../migrations/0002_auto_20210731_0031.py     |  56 ++++
 passerelle/contrib/toulouse_smart/models.py   | 148 ++++++++-
 passerelle/contrib/toulouse_smart/schemas.py  | 126 ++++++++
 .../toulouse_smart/create_intervention.json   |  54 ++++
 tests/test_toulouse_smart.py                  | 289 +++++++++++++++++-
 5 files changed, 671 insertions(+), 2 deletions(-)
 create mode 100644 passerelle/contrib/toulouse_smart/migrations/0002_auto_20210731_0031.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_20210731_0031.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.29 on 2021-07-30 22:31
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_api_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=[('registered', 'Registered'), ('sent', 'Sent')],
31
                        default='registered',
32
                        max_length=20,
33
                    ),
34
                ),
35
                (
36
                    'resource',
37
                    models.ForeignKey(
38
                        on_delete=django.db.models.deletion.CASCADE,
39
                        related_name='wcs_requests',
40
                        to='toulouse_smart.ToulouseSmartResource',
41
                        verbose_name='WcsRequest',
42
                    ),
43
                ),
44
            ],
45
        ),
46
        migrations.AlterField(
47
            model_name='cache',
48
            name='resource',
49
            field=models.ForeignKey(
50
                on_delete=django.db.models.deletion.CASCADE,
51
                related_name='cache_entries',
52
                to='toulouse_smart.ToulouseSmartResource',
53
                verbose_name='Resource',
54
            ),
55
        ),
56
    ]
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
23
from django.db.transaction import atomic
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
            'id': {'description': _('Intervention identifier')},
132 136
        },
133 137
    )
134 138
    def get_intervention(self, request, id):
135 139
        url = self.webservice_base_url + 'v1/intervention/%s' % id
136 140
        response = self.request(url)
137 141
        return {'data': response.json()}
138 142

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

  
181
        wcs_request, created = self.wcs_requests.update_or_create(
182
            defaults={
183
                'wcs_form_api_url': post_data['form_api_url'],
184
                'wcs_form_number': post_data['external_number'],
185
            }
186
        )
187
        if not created:
188
            raise APIError(
189
                "'%s' intervention already created: '%s' status"
190
                % (
191
                    post_data['external_number'],
192
                    wcs_request.status,
193
                ),
194
                http_status=400,
195
            )
196
        wcs_request = self.wcs_requests.get(wcs_form_number=wcs_form_number)
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': 'update-intervention?uuid=%s' % wcs_request.uuid,
219
        }
220
        wcs_request.save()
221
        if not wcs_request.push():
222
            self.add_job(
223
                'create_intervention_job',
224
                pk=wcs_request.pk,
225
                natural_id='wcs-request-%s' % wcs_request.pk,
226
            )
227
        wcs_request = self.wcs_requests.get(wcs_form_number=wcs_form_number)
228
        return {
229
            'data': {
230
                'wcs_form_api_url': wcs_request.wcs_form_api_url,
231
                'wcs_form_number': wcs_request.wcs_form_number,
232
                'uuid': wcs_request.uuid,
233
                'payload': wcs_request.payload,
234
                'result': wcs_request.result,
235
                'status': wcs_request.status,
236
            }
237
        }
238

  
239
    def create_intervention_job(self, *args, **kwargs):
240
        wcs_request = self.wcs_requests.get(pk=kwargs['pk'])
241
        if not wcs_request.push():
242
            raise SkipJob()
243

  
139 244

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

  
148 253
    key = models.CharField(_('Key'), max_length=64)
149 254

  
150 255
    timestamp = models.DateTimeField(_('Timestamp'), auto_now=True)
151 256

  
152 257
    value = JSONField(_('Value'), default=dict)
258

  
259

  
260
class WcsRequest(models.Model):
261
    resource = models.ForeignKey(
262
        verbose_name=_('WcsRequest'),
263
        to=ToulouseSmartResource,
264
        on_delete=models.CASCADE,
265
        related_name='wcs_requests',
266
    )
267
    wcs_form_api_url = models.CharField(max_length=256, primary_key=True)
268
    wcs_form_number = models.CharField(max_length=16)
269
    uuid = models.UUIDField(default=uuid4, unique=True, editable=False)
270
    payload = JSONField(default=dict)
271
    result = JSONField(default=dict)
272
    status = models.CharField(
273
        max_length=20,
274
        default='registered',
275
        choices=(
276
            ('registered', _('Registered')),
277
            ('sent', _('Sent')),
278
        ),
279
    )
280

  
281
    def push(self):
282
        url = self.resource.webservice_base_url + 'v1/intervention'
283
        try:
284
            response = self.resource.request(url, json=self.payload)
285
        except APIError as e:
286
            self.result = str(e)
287
            self.save()
288
            return False
289
        try:
290
            self.result = response.json()
291
        except ValueError:
292
            err_desc = 'invalid json, got: %s' % response.text
293
            self.result = err_desc
294
            self.save()
295
            return False
296
        self.status = 'sent'
297
        self.save()
298
        return True
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_api_url': {
102
            'description': "L'adresse vers la vue API du formulaire : {{ form_api_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_api_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_json_file(filename):
77 82
    with open(os.path.join(TEST_BASE_DIR, "%s.json" % filename)) 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>
......
232 242
@mock_response(
233 243
    ['/v1/intervention', None, None, 404],
234 244
)
235 245
def test_get_intervention_wrond_id(app, smart):
236 246
    resp = app.get(URL + 'get-intervention?id=3f0558bd-7d85-49a8-97e4-d07bc7f8dc9b')
237 247
    assert resp.json['err']
238 248
    assert 'failed to get' in resp.json['err_desc']
239 249
    assert '404' in resp.json['err_desc']
250

  
251

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

  
272

  
273
FIELDS_PAYLOAD = {
274
    'coin_raw': [
275
        {
276
            'field1': 'Candélabre',
277
            'field1_raw': 'Candélabre',
278
            'field2': '42',
279
        },
280
    ],
281
}
282

  
283

  
284
CREATE_INTERVENTION_PAYLOAD = {
285
    'fields': FIELDS_PAYLOAD,
286
    'extra': CREATE_INTERVENTION_PAYLOAD_EXTRA,
287
}
288

  
289
UUID = uuid.UUID('12345678123456781234567812345678')
290

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

  
311

  
312
@mock_response(
313
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
314
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
315
)
316
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
317
def test_create_intervention(mocked_uuid4, app, smart):
318
    with pytest.raises(WcsRequest.DoesNotExist):
319
        smart.wcs_requests.get(uuid=UUID)
320

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

  
333

  
334
@mock_response(
335
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
336
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
337
)
338
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
339
def test_create_intervention_async(mocked_uuid4, app, smart):
340
    mocked_push = mock.patch(
341
        "passerelle.contrib.toulouse_smart.models.WcsRequest.push",
342
        return_value=False,
343
    )
344
    mocked_push.start()
345

  
346
    assert Job.objects.count() == 0
347
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
348
    assert str(UUID) in CREATE_INTERVENTION_QUERY['notificationUrl']
349
    assert not resp.json['err']
350
    assert resp.json['data']['uuid'] == str(UUID)
351
    assert resp.json['data']['wcs_form_api_url'] == 'https://wcs.example.com/api/forms/foo/2/'
352
    wcs_request = smart.wcs_requests.get(uuid=UUID)
353
    assert wcs_request.wcs_form_api_url == 'https://wcs.example.com/api/forms/foo/2/'
354
    assert wcs_request.wcs_form_number == '42-2'
355
    assert wcs_request.payload == CREATE_INTERVENTION_QUERY
356
    assert wcs_request.status == 'registered'
357

  
358
    mocked_push.stop()
359
    assert Job.objects.count() == 1
360
    job = Job.objects.get(method_name='create_intervention_job')
361
    assert job.status == 'registered'
362
    smart.jobs()
363
    job = Job.objects.get(method_name='create_intervention_job')
364
    assert job.status == 'completed'
365
    wcs_request = smart.wcs_requests.get(uuid=UUID)
366
    assert wcs_request.result == json.loads(get_json_file('create_intervention'))
367
    assert wcs_request.status == 'sent'
368

  
369

  
370
def test_create_intervention_wrong_payload(app, smart):
371
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
372
    del payload['extra']['slug']
373
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
374
    assert resp.json['err']
375
    assert "'slug' is a required property" in resp.json['err_desc']
376

  
377

  
378
@mock_response()
379
def test_create_intervention_types_unavailable(app, smart):
380
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
381
    assert resp.json['err']
382
    assert 'Service is unavailable' in resp.json['err_desc']
383

  
384

  
385
@mock_response(
386
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
387
)
388
def test_create_intervention_wrong_block_slug(app, smart):
389
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
390
    payload['extra']['slug'] = 'coin-coin'
391
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
392
    assert resp.json['err']
393
    assert "unknown 'coin-coin' block slug" in resp.json['err_desc']
394

  
395

  
396
@mock_response(
397
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
398
)
399
def test_create_intervention_no_block(app, smart):
400
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
401
    del payload['fields']['coin_raw']
402
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
403
    assert resp.json['err']
404
    assert "cannot find 'coin' block field content" in resp.json['err_desc']
405

  
406

  
407
@mock_response(
408
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
409
)
410
def test_create_intervention_cast_error(app, smart):
411
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
412
    payload['fields']['coin_raw'][0]['field2'] = 'not-an-integer'
413
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
414
    assert resp.json['err']
415
    assert "cannot cast 'field2' field to <class 'int'>" in resp.json['err_desc']
416

  
417

  
418
@mock_response(
419
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
420
)
421
def test_create_intervention_missing_value(app, smart):
422
    field_payload = {
423
        'coin_raw': [
424
            {
425
                'field1': 'Candélabre',
426
                'field1_raw': 'Candélabre',
427
                'field2': None,
428
            },
429
        ],
430
    }
431
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
432
    payload['fields'] = field_payload
433
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
434
    assert resp.json['err']
435
    assert "field is required on 'coin' block" in resp.json['err_desc']
436

  
437

  
438
@mock_response(
439
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
440
)
441
def test_create_intervention_missing_field(app, smart):
442
    field_payload = {
443
        'coin_raw': [
444
            {
445
                'field1': 'Candélabre',
446
                'field1_raw': 'Candélabre',
447
            },
448
        ],
449
    }
450
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
451
    payload['fields'] = field_payload
452
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
453
    assert resp.json['err']
454
    assert "field is required on 'coin' block" in resp.json['err_desc']
455

  
456

  
457
@mock_response(
458
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
459
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, None, 500],
460
)
461
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
462
def test_create_intervention_twice_error(mocked_uuid4, app, smart):
463
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
464
    assert not resp.json['err']
465

  
466
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD, status=400)
467
    assert resp.json['err']
468
    assert 'already created' in resp.json['err_desc']
469

  
470

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

  
486
    freezer.move_to('2021-07-08 00:00:03')
487
    smart.jobs()
488
    job = Job.objects.get(method_name='create_intervention_job')
489
    assert job.status == 'registered'
490
    assert job.update_timestamp > job.creation_timestamp
491
    wcs_request = smart.wcs_requests.get(uuid=UUID)
492
    assert wcs_request.status == 'registered'
493
    assert 'failed to post' in wcs_request.result
494

  
495

  
496
@mock_response(
497
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
498
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, None, 500],
499
)
500
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
501
def test_create_intervention_inconsistency_id_error(mocked_uuid4, app, freezer, smart):
502
    freezer.move_to('2021-07-08 00:00:00')
503
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
504
    wcs_request = smart.wcs_requests.get(uuid=UUID)
505
    assert wcs_request.status == 'registered'
506
    job = Job.objects.get(method_name='create_intervention_job')
507
    assert job.status == 'registered'
508

  
509
    freezer.move_to('2021-07-08 00:00:03')
510
    wcs_request.delete()
511
    smart.jobs()
512
    job = Job.objects.get(method_name='create_intervention_job')
513
    assert job.status == 'failed'
514

  
515

  
516
@mock_response(
517
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
518
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, 'not json content'],
519
)
520
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
521
def test_create_intervention_content_error(mocked_uuid, app, freezer, smart):
522
    freezer.move_to('2021-07-08 00:00:00')
523
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
524
    wcs_request = smart.wcs_requests.get(uuid=UUID)
525
    assert wcs_request.status == 'registered'
526
    assert 'invalid json' in wcs_request.result
240
-