Projet

Général

Profil

0004-toulouse_smart-add-an-add-media-endpoint-57875.patch

Nicolas Roche, 02 novembre 2021 13:15

Télécharger (25,8 ko)

Voir les différences:

Subject: [PATCH 4/5] toulouse_smart: add an add-media endpoint (#57875)

 .../migrations/0004_wcsrequestfile.py         |  36 ++++
 passerelle/contrib/toulouse_smart/models.py   |  91 ++++++++-
 passerelle/contrib/toulouse_smart/schemas.py  |  38 ++++
 passerelle/views.py                           |   2 -
 tests/data/toulouse_smart/201x201.jpg         | Bin 0 -> 795 bytes
 tests/test_toulouse_smart.py                  | 190 +++++++++++++++++-
 6 files changed, 346 insertions(+), 11 deletions(-)
 create mode 100644 passerelle/contrib/toulouse_smart/migrations/0004_wcsrequestfile.py
 create mode 100644 tests/data/toulouse_smart/201x201.jpg
passerelle/contrib/toulouse_smart/migrations/0004_wcsrequestfile.py
1
# Generated by Django 2.2.19 on 2021-10-29 15:45
2

  
3
import django.db.models.deletion
4
from django.db import migrations, models
5

  
6
import passerelle.contrib.toulouse_smart.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('toulouse_smart', '0003_smartrequest'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='WcsRequestFile',
18
            fields=[
19
                (
20
                    'id',
21
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
22
                ),
23
                ('filename', models.CharField(max_length=256)),
24
                ('content_type', models.CharField(max_length=256)),
25
                ('content', models.FileField(upload_to=passerelle.contrib.toulouse_smart.models.upload_to)),
26
                (
27
                    'resource',
28
                    models.ForeignKey(
29
                        on_delete=django.db.models.deletion.CASCADE,
30
                        related_name='files',
31
                        to='toulouse_smart.WcsRequest',
32
                    ),
33
                ),
34
            ],
35
        ),
36
    ]
passerelle/contrib/toulouse_smart/models.py
9 9
# This program is distributed in the hope that it will be useful,
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
import base64
17 18
import datetime
18 19
import json
19 20
from uuid import uuid4
20 21

  
21 22
import lxml.etree as ET
22 23
from django.conf import settings
23 24
from django.contrib.postgres.fields import JSONField
25
from django.core.files.base import ContentFile
24 26
from django.db import models
25 27
from django.db.transaction import atomic
26 28
from django.urls import reverse
27 29
from django.utils.six.moves.urllib import parse as urlparse
28 30
from django.utils.text import slugify
29 31
from django.utils.timezone import now
30 32
from django.utils.translation import ugettext_lazy as _
31 33
from requests import RequestException
......
185 187
            raise APIError(
186 188
                "'%s' intervention already created" % post_data['external_number'],
187 189
                http_status=400,
188 190
            )
189 191
        wcs_request = self.wcs_requests.create(
190 192
            wcs_form_api_url=post_data['form_api_url'],
191 193
            wcs_form_number=post_data['external_number'],
192 194
        )
193
        update_intervention_endpoint_url = request.build_absolute_uri(
194
            reverse(
195
                'generic-endpoint',
196
                kwargs={'connector': 'toulouse-smart', 'endpoint': 'update-intervention', 'slug': self.slug},
195
        endpoint_url = {}
196
        for endpoint_name in 'update-intervention', 'add-media':
197
            endpoint_url[endpoint_name] = request.build_absolute_uri(
198
                reverse(
199
                    'generic-endpoint',
200
                    kwargs={'connector': 'toulouse-smart', 'endpoint': endpoint_name, 'slug': self.slug},
201
                )
197 202
            )
198
        )
199 203
        wcs_request.payload = {
200 204
            'description': post_data['description'],
201 205
            'cityId': post_data['cityId'],
202 206
            'interventionCreated': post_data['interventionCreated'] + 'Z',
203 207
            'interventionDesired': post_data['interventionDesired'] + 'Z',
204 208
            'submitterFirstName': post_data['submitterFirstName'],
205 209
            'submitterLastName': post_data['submitterLastName'],
206 210
            'submitterMail': post_data['submitterMail'],
......
212 216
            'address': post_data['address'],
213 217
            'interventionData': data,
214 218
            'geom': {
215 219
                'type': 'Point',
216 220
                'coordinates': [post_data['lon'], post_data['lat']],
217 221
                'crs': 'EPSG:4326',
218 222
            },
219 223
            'interventionTypeId': intervention_type['id'],
220
            'notificationUrl': '%s?uuid=%s' % (update_intervention_endpoint_url, wcs_request.uuid),
224
            'notificationUrl': '%s?uuid=%s' % (endpoint_url['update-intervention'], wcs_request.uuid),
225
            'add_media_url': '%s?uuid=%s' % (endpoint_url['add-media'], wcs_request.uuid),
221 226
        }
222 227
        for label in 'checkDuplicated', 'onPrivateLand', 'safeguardRequired':
223 228
            if str(post_data.get(label)).lower() in ['true', 'oui', '1']:
224 229
                wcs_request.payload[label] = 'true'
225 230
        wcs_request.save()
226 231
        if not wcs_request.push():
227 232
            self.add_job(
228 233
                'create_intervention_job',
......
256 261
        }
257 262
        smart_request = wcs_request.smart_requests.create(payload=payload)
258 263
        self.add_job(
259 264
            'update_intervention_job',
260 265
            id=smart_request.id,
261 266
            natural_id='smart-request-%s' % smart_request.id,
262 267
        )
263 268

  
269
    @endpoint(
270
        name='add-media',
271
        methods=['post'],
272
        description=_('Add a media'),
273
        parameters={
274
            'uuid': {'description': _('Notification identifier')},
275
        },
276
        perm='can_access',
277
        post={'request_body': {'schema': {'application/json': schemas.MEDIA_SCHEMA}}},
278
    )
279
    def add_media(self, request, uuid, post_data):
280
        try:
281
            wcs_request = self.wcs_requests.get(uuid=uuid)
282
        except WcsRequest.DoesNotExist:
283
            raise APIError("Cannot find intervention '%s'" % uuid, http_status=400)
284

  
285
        nb_registered = 0
286
        for media in post_data['files']:
287
            if not media:
288
                # silently ignore empty payload value
289
                continue
290
            wcs_request_file = wcs_request.files.create(
291
                filename=media['filename'], content_type=media['content_type']
292
            )
293
            with ContentFile(base64.b64decode(media['content'])) as media_content:
294
                wcs_request_file.content.save(media['filename'], media_content)
295
            self.add_job(
296
                'add_media_job',
297
                id=wcs_request_file.id,
298
                natural_id='wcs-request-file-%s' % wcs_request_file.id,
299
            )
300
            nb_registered += 1
301

  
302
        return {'data': {'uuid': wcs_request.uuid, 'nb_registered': nb_registered}}
303

  
304
    def add_media_job(self, *args, **kwargs):
305
        wcs_request_file = WcsRequestFile.objects.get(id=kwargs['id'])
306
        wcs_request = wcs_request_file.resource
307
        if not wcs_request.result or not wcs_request.result.get('id'):
308
            raise SkipJob(datetime.timedelta(minutes=10))
309

  
310
        if not wcs_request_file.push():
311
            raise SkipJob()
312

  
264 313
    @atomic
265 314
    @endpoint(
266 315
        name='update-intervention',
267 316
        methods=['post'],
268 317
        description=_('Update an intervention status'),
269 318
        parameters={
270 319
            'uuid': {'description': _('Notification identifier')},
271 320
        },
......
347 396
            self.result = err_desc
348 397
            self.save()
349 398
            return False
350 399
        self.status = 'sent'
351 400
        self.save()
352 401
        return True
353 402

  
354 403

  
404
def upload_to(wcs_request_file, filename):
405
    instance = wcs_request_file.resource.resource
406
    uuid = wcs_request_file.resource.uuid
407
    return '%s/%s/%s/%s' % (instance.get_connector_slug(), instance.slug, uuid, filename)
408

  
409

  
410
class WcsRequestFile(models.Model):
411
    resource = models.ForeignKey(
412
        to=WcsRequest,
413
        on_delete=models.CASCADE,
414
        related_name='files',
415
    )
416
    filename = models.CharField(max_length=256)
417
    content_type = models.CharField(max_length=256)
418
    content = models.FileField(upload_to=upload_to)
419

  
420
    def push(self):
421
        wcs_request = self.resource
422
        intervention_id = wcs_request.result.get('id')
423
        instance = wcs_request.resource
424
        url = '%sv1/intervention/%s/media' % (instance.webservice_base_url, intervention_id)
425
        files = {'media': (self.filename, self.content.open('rb'), self.content_type)}
426
        try:
427
            instance.request(url, method='PUT', files=files)
428
        except APIError as e:
429
            return False
430
        self.content.delete()
431
        return True
432

  
433

  
355 434
class SmartRequest(models.Model):
356 435
    resource = models.ForeignKey(
357 436
        to=WcsRequest,
358 437
        on_delete=models.CASCADE,
359 438
        related_name='smart_requests',
360 439
    )
361 440
    payload = JSONField()
362 441
    result = JSONField(null=True)
passerelle/contrib/toulouse_smart/schemas.py
172 172
                    'type': 'string',
173 173
                },
174 174
            },
175 175
            'required': ['status'],
176 176
        },
177 177
    },
178 178
    'required': ['data'],
179 179
}
180

  
181

  
182
MEDIA_SCHEMA = {
183
    '$schema': 'http://json-schema.org/draft-04/schema#',
184
    'type': 'object',
185
    'properties': {
186
        'files': {
187
            'type': 'array',
188
            'items': {
189
                'oneOf': [
190
                    {
191
                        'type': 'null',
192
                    },
193
                    {
194
                        'type': 'object',
195
                        'properties': {
196
                            'filename': {
197
                                'description': "Nom du ficher",
198
                                'type': 'string',
199
                            },
200
                            'content_type': {
201
                                'description': "Type MIME",
202
                                'type': 'string',
203
                            },
204
                            'content': {
205
                                'description': "Contenu",
206
                                'type': 'string',
207
                            },
208
                        },
209
                        'required': ['filename', 'content_type', 'content'],
210
                    },
211
                ],
212
            },
213
        },
214
    },
215
    'required': ['files'],
216
    'unflatten': True,
217
}
passerelle/views.py
381 381
                except ValueError:
382 382
                    raise InvalidParameterValue(parameter)
383 383
            elif parameter_info.get('type') == 'float':
384 384
                d[parameter] = d[parameter].replace(',', '.')
385 385
                try:
386 386
                    d[parameter] = float(d[parameter])
387 387
                except ValueError:
388 388
                    raise InvalidParameterValue(parameter)
389

  
390 389
        if request.method == 'POST' and self.endpoint.endpoint_info.post:
391 390
            request_body = self.endpoint.endpoint_info.post.get('request_body', {})
392 391
            if 'application/json' in request_body.get('schema', {}):
393 392
                json_schema = request_body['schema']['application/json']
394 393
                must_unflatten = hasattr(json_schema, 'items') and json_schema.get('unflatten', False)
395 394
                merge_extra = hasattr(json_schema, 'items') and json_schema.get('merge_extra', False)
396 395
                pre_process = hasattr(json_schema, 'items') and json_schema.get('pre_process')
397 396
                try:
......
404 403
                    data.update(data.pop('extra', {}))
405 404
                if pre_process is not None:
406 405
                    pre_process(self.endpoint.__self__, data)
407 406

  
408 407
                # disable validation on description and title in order to allow lazy translation strings
409 408
                validator = validators.validator_for(json_schema)
410 409
                validator.META_SCHEMA['properties'].pop('description', None)
411 410
                validator.META_SCHEMA['properties'].pop('title', None)
412

  
413 411
                try:
414 412
                    validate(data, json_schema)
415 413
                except ValidationError as e:
416 414
                    error_msg = e.message
417 415
                    if e.path:
418 416
                        error_msg = '%s: %s' % ('/'.join(map(str, e.path)), error_msg)
419 417
                    raise APIError(error_msg, http_status=400)
420 418
                d['post_data'] = data
tests/test_toulouse_smart.py
9 9
# This program is distributed in the hope that it will be useful,
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
import base64
18
import cgi
17 19
import functools
18 20
import io
19 21
import json
20 22
import os
21 23
import uuid
22 24
import zipfile
23 25
from copy import deepcopy
24 26

  
25 27
import httmock
26 28
import lxml.etree as ET
27 29
import mock
28 30
import pytest
29 31
import utils
32
from django.utils.encoding import force_text
30 33
from requests.exceptions import ReadTimeout
31 34
from test_manager import login
32 35

  
33 36
from passerelle.base.models import Job
34
from passerelle.contrib.toulouse_smart.models import SmartRequest, ToulouseSmartResource, WcsRequest
37
from passerelle.contrib.toulouse_smart.models import (
38
    SmartRequest,
39
    ToulouseSmartResource,
40
    WcsRequest,
41
    WcsRequestFile,
42
)
35 43

  
36 44
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'toulouse_smart')
37 45

  
38 46

  
39 47
@pytest.fixture
40 48
def smart(db):
41 49
    return utils.make_resource(
42 50
        ToulouseSmartResource,
......
67 75
    def decorator(func):
68 76
        @httmock.urlmatch()
69 77
        def error(url, request):
70 78
            assert False, 'request to %s' % url.geturl()
71 79

  
72 80
        def register(path, payload, content, status_code=200, exception=None):
73 81
            @httmock.urlmatch(path=path)
74 82
            def handler(url, request):
75
                if payload and json.loads(request.body) != payload:
76
                    assert False, 'wrong payload sent to request to %s' % url.geturl()
83
                if payload:
84
                    ctype, pdict = cgi.parse_header(request.headers["content-type"])
85
                    if ctype == 'multipart/form-data':
86
                        # here payload is an expected multipart contents list
87
                        pdict["boundary"] = bytes(pdict["boundary"], "utf-8")
88
                        pdict['CONTENT-LENGTH'] = request.headers['Content-Length']
89
                        postvars = cgi.parse_multipart(io.BytesIO(request.body), pdict)
90
                        for i, media_content in enumerate(postvars['media']):
91
                            assert media_content == payload[i], (
92
                                'wrong multipart content sent to %s' % url.geturl()
93
                            )
94
                    else:
95
                        assert json.loads(request.body) == payload, (
96
                            'wrong payload sent to request to %s' % url.geturl()
97
                        )
77 98
                if exception:
78 99
                    raise exception
79 100
                return httmock.response(status_code, content)
80 101

  
81 102
            return handler
82 103

  
83 104
        @functools.wraps(func)
84 105
        def wrapper(*args, **kwargs):
......
95 116
    return decorator
96 117

  
97 118

  
98 119
def get_json_file(filename):
99 120
    with open(os.path.join(TEST_BASE_DIR, "%s.json" % filename)) as desc:
100 121
        return desc.read()
101 122

  
102 123

  
124
def get_media_file(filename):
125
    with open(os.path.join(TEST_BASE_DIR, "%s" % filename), 'rb') as desc:
126
        return desc.read()
127

  
128

  
103 129
@mock_response(['/v1/type-intervention', None, b'<List></List>'])
104 130
def test_empty_intervention_types(smart):
105 131
    assert smart.get_intervention_types() == []
106 132

  
107 133

  
108 134
INTERVENTION_TYPES = '''<List>
109 135
   <item>
110 136
       <id>1234</id>
......
302 328
CREATE_INTERVENTION_PAYLOAD = {
303 329
    'fields': FIELDS_PAYLOAD,
304 330
    'extra': CREATE_INTERVENTION_PAYLOAD_EXTRA,
305 331
}
306 332

  
307 333
UUID = uuid.UUID('12345678123456781234567812345678')
308 334

  
309 335
CREATE_INTERVENTION_QUERY = {
336
    'add_media_url': 'http://testserver/toulouse-smart/test/add-media?uuid=%s' % str(UUID),
310 337
    'description': 'coin coin',
311 338
    'cityId': '12345',
312 339
    'interventionCreated': '2021-06-30T16:08:05Z',
313 340
    'interventionDesired': '2021-06-30T16:08:05Z',
314 341
    'submitterFirstName': 'John',
315 342
    'submitterLastName': 'Doe',
316 343
    'submitterMail': 'john.doe@example.com',
317 344
    'submitterPhone': '0123456789',
......
652 679
    smart.jobs()
653 680
    job = Job.objects.get(method_name='update_intervention_job')
654 681
    assert job.status == 'registered'
655 682
    assert job.update_timestamp > job.creation_timestamp
656 683
    smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get()
657 684
    assert smart_request.result == None
658 685

  
659 686

  
687
ADD_MEDIA_PAYLOAD = {
688
    'files/0': {
689
        'filename': '201x201.jpg',
690
        'content_type': 'image/jpeg',
691
        'content': force_text(base64.b64encode(get_media_file('201x201.jpg'))),
692
    },
693
    'files/1': None,
694
}
695

  
696
ADD_MEDIA_QUERY = [get_media_file('201x201.jpg')]
697

  
698

  
699
@mock_response(
700
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
701
    ['/v1/intervention/%s/media' % INTERVENTION_ID, ADD_MEDIA_QUERY, 200],
702
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
703
)
704
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
705
def test_add_media(mocked_uuid, app, smart):
706
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
707
    assert not resp.json['err']
708

  
709
    url = resp.json['data']['payload']['add_media_url']
710
    url = URL + 'add-media?uuid=%s' % str(UUID)
711
    resp = app.post_json(url, params=ADD_MEDIA_PAYLOAD)
712
    assert not resp.json['err']
713
    assert resp.json['data']['uuid'] == str(UUID)
714
    assert resp.json['data']['nb_registered'] == 1
715
    assert Job.objects.count() == 1
716
    job = Job.objects.get(method_name='add_media_job')
717
    assert job.status == 'registered'
718
    wcs_request = smart.wcs_requests.get(uuid=UUID)
719
    wcs_request_file = wcs_request.files.get(**job.parameters)
720
    path = wcs_request_file.content.path
721
    assert os.path.isfile(path)
722
    with wcs_request_file.content.open('rb') as desc:
723
        assert desc.read() == get_media_file('201x201.jpg')
724

  
725
    # smart not responding
726
    mocked_push = mock.patch(
727
        "passerelle.contrib.toulouse_smart.models.WcsRequestFile.push",
728
        return_value=False,
729
    )
730
    mocked_push.start()
731
    job = Job.objects.get(method_name='add_media_job')
732
    assert job.status == 'registered'
733

  
734
    # smart responding
735
    mocked_push.stop()
736
    smart.jobs()
737
    job = Job.objects.get(method_name='add_media_job')
738
    assert job.status == 'completed'
739
    wcs_request_file = wcs_request.files.get(**job.parameters)
740
    with pytest.raises(ValueError, match='no file associated'):
741
        assert not wcs_request_file.content.path
742
    assert not os.path.isfile(path)
743

  
744

  
745
def test_add_media_wrong_uuid(app, smart):
746
    with pytest.raises(WcsRequest.DoesNotExist):
747
        smart.wcs_requests.get(uuid=UUID)
748

  
749
    url = URL + 'add-media?uuid=%s' % str(UUID)
750
    resp = app.post_json(url, params=ADD_MEDIA_PAYLOAD, status=400)
751
    assert resp.json['err']
752
    assert 'Cannot find intervention' in resp.json['err_desc']
753
    assert WcsRequestFile.objects.count() == 0
754

  
755

  
756
@mock_response(
757
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
758
    ['/v1/intervention/%s/media' % json.loads(get_json_file('create_intervention'))['id'], None, None, 500],
759
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
760
)
761
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
762
def test_add_media_error(mocked_uuid, app, freezer, smart):
763
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
764
    assert not resp.json['err']
765

  
766
    freezer.move_to('2021-10-30 00:00:00')
767
    url = resp.json['data']['payload']['add_media_url']
768
    resp = app.post_json(url, params=ADD_MEDIA_PAYLOAD)
769
    job = Job.objects.get(method_name='add_media_job')
770
    assert job.status == 'registered'
771

  
772
    freezer.move_to('2021-10-30 00:00:03')
773
    smart.jobs()
774
    job = Job.objects.get(method_name='add_media_job')
775
    assert job.status == 'registered'
776
    assert job.update_timestamp > job.creation_timestamp
777

  
778

  
779
@mock_response(
780
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
781
    ['/v1/intervention/%s/media' % INTERVENTION_ID, None, None, None, ReadTimeout('timeout')],
782
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
783
)
784
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
785
def test_add_media_timeout_error(mocked_uuid, app, freezer, smart):
786
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
787
    assert not resp.json['err']
788

  
789
    freezer.move_to('2021-10-30 00:00:00')
790
    url = resp.json['data']['payload']['add_media_url']
791
    resp = app.post_json(url, params=ADD_MEDIA_PAYLOAD)
792
    job = Job.objects.get(method_name='add_media_job')
793
    assert job.status == 'registered'
794

  
795
    freezer.move_to('2021-10-30 00:00:03')
796
    smart.jobs()
797
    job = Job.objects.get(method_name='add_media_job')
798
    assert job.status == 'registered'
799
    assert job.update_timestamp > job.creation_timestamp
800

  
801

  
660 802
UPDATE_INTERVENTION_QUERY_ON_ASYNC_CREATION = {
661 803
    'creation_response': {
662 804
        'wcs_form_api_url': CREATE_INTERVENTION_PAYLOAD_EXTRA['form_api_url'],
663 805
        'wcs_form_number': CREATE_INTERVENTION_PAYLOAD_EXTRA['external_number'],
664 806
        'uuid': str(UUID),
665 807
        'payload': CREATE_INTERVENTION_QUERY,
666 808
        'result': json.loads(get_json_file('create_intervention')),
667 809
        'status': 'sent',
......
727 869
    mocked_smart_request_push.stop()
728 870
    smart.jobs()
729 871

  
730 872
    job = Job.objects.get(method_name='update_intervention_job')
731 873
    assert job.status == 'completed'
732 874
    smart_request = wcs_request.smart_requests.get()
733 875
    assert smart_request.payload['creation_response']['uuid'] == str(UUID)
734 876
    assert smart_request.payload['creation_response']['result']['id'] == INTERVENTION_ID
877

  
878

  
879
@mock_response(
880
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
881
    ['/v1/intervention/%s/media' % INTERVENTION_ID, ADD_MEDIA_QUERY, 200],
882
    ['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')],
883
)
884
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
885
def test_add_media_async(mocked_uuid4, app, smart, freezer):
886
    mocked_wcs_request_push = mock.patch(
887
        "passerelle.contrib.toulouse_smart.models.WcsRequest.push",
888
        return_value=False,
889
    )
890

  
891
    # smart is down
892
    freezer.move_to('2021-10-30 00:00:00')
893
    mocked_wcs_request_push.start()
894
    resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD)
895
    assert not resp.json['err']
896
    url = resp.json['data']['payload']['add_media_url']
897
    resp = app.post_json(url, params=ADD_MEDIA_PAYLOAD)
898
    smart.jobs()
899
    job = Job.objects.get(method_name='create_intervention_job')
900
    assert job.status == 'registered'
901
    job = Job.objects.get(method_name='add_media_job')
902
    assert job.status == 'registered'
903
    assert str(job.after_timestamp) == '2021-10-30 00:10:00+00:00'
904

  
905
    # smart is up
906
    freezer.move_to('2021-10-30 00:00:03')
907
    mocked_wcs_request_push.stop()
908
    smart.jobs()
909
    job = Job.objects.get(method_name='create_intervention_job')
910
    assert job.status == 'completed'
911
    job = Job.objects.get(method_name='add_media_job')
912
    assert job.status == 'registered'
913

  
914
    # 10 minutes later
915
    freezer.move_to('2021-10-30 00:10:03')
916
    smart.jobs()
917
    job = Job.objects.get(method_name='add_media_job')
918
    assert job.status == 'completed'
735
-