0007-toulouse_smart-add-update-intervention-endpoint-5523.patch
passerelle/contrib/toulouse_smart/migrations/0003_smartrequest.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.29 on 2021-07-26 14:50 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields.jsonb |
|
6 |
import django.db.models.deletion |
|
7 |
from django.db import migrations, models |
|
8 | ||
9 | ||
10 |
class Migration(migrations.Migration): |
|
11 | ||
12 |
dependencies = [ |
|
13 |
('toulouse_smart', '0002_auto_20210726_1648'), |
|
14 |
] |
|
15 | ||
16 |
operations = [ |
|
17 |
migrations.CreateModel( |
|
18 |
name='SmartRequest', |
|
19 |
fields=[ |
|
20 |
( |
|
21 |
'id', |
|
22 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
23 |
), |
|
24 |
('trigger', models.CharField(blank=True, max_length=64, null=True, verbose_name='Trigger')), |
|
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='smart_requests', |
|
45 |
to='toulouse_smart.WcsRequest', |
|
46 |
verbose_name='SmartRequest', |
|
47 |
), |
|
48 |
), |
|
49 |
], |
|
50 |
options={ |
|
51 |
'ordering': ['pk', 'status'], |
|
52 |
}, |
|
53 |
), |
|
54 |
] |
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 |
import json |
|
18 | 19 |
from uuid import uuid4 |
19 | 20 | |
20 | 21 |
import lxml.etree as ET |
22 |
from django.conf import settings |
|
21 | 23 |
from django.contrib.postgres.fields import JSONField |
22 | 24 |
from django.core import serializers |
23 |
from django.db import models |
|
25 |
from django.db import models, transaction |
|
26 |
from django.utils.six.moves.urllib import parse as urlparse |
|
24 | 27 |
from django.utils.text import slugify |
25 | 28 |
from django.utils.timezone import now |
26 | 29 |
from django.utils.translation import ugettext_lazy as _ |
27 | 30 |
from requests import RequestException |
28 | 31 | |
29 | 32 |
from passerelle.base.models import BaseResource, HTTPResource, SkipJob |
30 | 33 |
from passerelle.utils import xml |
31 | 34 |
from passerelle.utils.api import endpoint |
32 | 35 |
from passerelle.utils.jsonresponse import APIError |
36 |
from passerelle.utils.wcs import WcsApi, WcsApiError |
|
33 | 37 | |
34 | 38 |
from . import schemas |
35 | 39 | |
36 | 40 | |
37 | 41 |
class ToulouseSmartResource(BaseResource, HTTPResource): |
38 | 42 |
category = _('Business Process Connectors') |
39 | 43 | |
40 | 44 |
webservice_base_url = models.URLField(_('Webservice Base URL')) |
... | ... | |
233 | 237 |
wcs_request.result = response.json() |
234 | 238 |
except ValueError: |
235 | 239 |
wcs_request.status = 'error' |
236 | 240 |
wcs_request.save() |
237 | 241 |
raise APIError('invalid json, got: %s' % response.text) |
238 | 242 |
wcs_request.status = 'sent' |
239 | 243 |
wcs_request.save() |
240 | 244 | |
245 |
@endpoint( |
|
246 |
name='update-intervention', |
|
247 |
methods=['post'], |
|
248 |
description=_('Update an intervention status'), |
|
249 |
perm='can_access', |
|
250 |
parameters={ |
|
251 |
'id': {'description': _('Intervention identifier')}, |
|
252 |
'trigger': {'description': _('W.C.S trigger')}, |
|
253 |
}, |
|
254 |
post={'request_body': {'schema': {'application/json': schemas.UPDATE_SCHEMA}}}, |
|
255 |
) |
|
256 |
def update_intervention(self, request, uuid, trigger, post_data): |
|
257 |
try: |
|
258 |
wcs_request = self.wcs_requests.get(uuid=uuid) |
|
259 |
except WcsRequest.DoesNotExist: |
|
260 |
raise APIError("Cannot find intervention '%s'" % uuid, http_status=400) |
|
261 |
smart_request = wcs_request.smart_requests.create( |
|
262 |
trigger=trigger, |
|
263 |
payload=post_data, |
|
264 |
) |
|
265 |
smart_request.status = 'registered' |
|
266 |
smart_request.save() |
|
267 |
self.add_job( |
|
268 |
'update_intervention_job', |
|
269 |
try_now=True, |
|
270 |
wcs_request_pk=wcs_request.pk, |
|
271 |
smart_request_id=smart_request.id, |
|
272 |
) |
|
273 |
wcs_data = serializers.serialize('python', [wcs_request])[0] |
|
274 |
smart_data = serializers.serialize('python', [smart_request])[0] |
|
275 |
return {'data': {'payload': post_data, 'wcs_request': wcs_data, 'smart_request': smart_data}} |
|
276 | ||
277 |
def update_intervention_job(self, *args, **kwargs): |
|
278 |
smarts_requests = SmartRequest.objects.select_for_update().filter( |
|
279 |
resource_id=kwargs['wcs_request_pk'], |
|
280 |
status='registered', |
|
281 |
) |
|
282 |
exception = None |
|
283 |
with transaction.atomic(): |
|
284 |
smart_request = smarts_requests[0] |
|
285 |
if smart_request.id != kwargs['smart_request_id']: |
|
286 |
# wait previous triggers registered are sent |
|
287 |
raise SkipJob() |
|
288 | ||
289 |
base_url = '%sjump/trigger/%s' % (smart_request.resource.wcs_form_url, smart_request.trigger) |
|
290 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(base_url) |
|
291 |
services = settings.KNOWN_SERVICES.get('wcs', {}) |
|
292 |
service = None |
|
293 |
for service in services.values(): |
|
294 |
remote_url = service.get('url') |
|
295 |
r_scheme, r_netloc, r_path, r_params, r_query, r_fragment = urlparse.urlparse(remote_url) |
|
296 |
if r_scheme == scheme and r_netloc == netloc: |
|
297 |
break |
|
298 |
else: |
|
299 |
smart_request.status = 'service-error' |
|
300 |
smart_request.save() |
|
301 |
raise APIError('Cannot find wcs service for %s' % base_url) |
|
302 | ||
303 |
wcs_api = WcsApi(base_url, orig=service.get('orig'), key=service.get('secret')) |
|
304 |
headers = { |
|
305 |
'Content-Type': 'application/json', |
|
306 |
'Accept': 'application/json', |
|
307 |
} |
|
308 |
try: |
|
309 |
result = wcs_api.post_json(smart_request.payload, [], headers=headers) |
|
310 |
except WcsApiError as e: |
|
311 |
try: |
|
312 |
result = json.loads(e.args[3]) |
|
313 |
except TypeError: |
|
314 |
# retry on transport error |
|
315 |
raise SkipJob() |
|
316 |
smart_request.result = result |
|
317 |
if result['err']: |
|
318 |
smart_request.status = 'wcs-error' |
|
319 |
smart_request.save() |
|
320 |
raise APIError(result.get('err_class', 'wcs error')) |
|
321 |
smart_request.status = 'sent' |
|
322 |
smart_request.save() |
|
323 | ||
241 | 324 | |
242 | 325 |
class Cache(models.Model): |
243 | 326 |
resource = models.ForeignKey( |
244 | 327 |
verbose_name=_('Resource'), |
245 | 328 |
to=ToulouseSmartResource, |
246 | 329 |
on_delete=models.CASCADE, |
247 | 330 |
related_name='cache_entries', |
248 | 331 |
) |
... | ... | |
271 | 354 |
default='registered', |
272 | 355 |
choices=( |
273 | 356 |
('registered', _('Registered')), |
274 | 357 |
('sent', _('Sent')), |
275 | 358 |
('service-error', _('Service error')), |
276 | 359 |
('wcs-error', _('WCS error')), |
277 | 360 |
), |
278 | 361 |
) |
362 | ||
363 | ||
364 |
class SmartRequest(models.Model): |
|
365 |
resource = models.ForeignKey( |
|
366 |
verbose_name=_('SmartRequest'), |
|
367 |
to=WcsRequest, |
|
368 |
on_delete=models.CASCADE, |
|
369 |
related_name='smart_requests', |
|
370 |
) |
|
371 |
trigger = models.CharField(_('Trigger'), blank=True, null=True, max_length=64) |
|
372 |
payload = JSONField(default=dict) |
|
373 |
result = JSONField(default=dict) |
|
374 |
status = models.CharField( |
|
375 |
max_length=20, |
|
376 |
default='registered', |
|
377 |
choices=( |
|
378 |
('registered', _('Registered')), |
|
379 |
('sent', _('Sent')), |
|
380 |
('service-error', _('Service error')), |
|
381 |
('wcs-error', _('WCS error')), |
|
382 |
), |
|
383 |
) |
|
384 | ||
385 |
class Meta: |
|
386 |
ordering = ['pk', 'status'] |
passerelle/contrib/toulouse_smart/schemas.py | ||
---|---|---|
119 | 119 |
'submitterType', |
120 | 120 |
'external_number', |
121 | 121 |
'external_status', |
122 | 122 |
'address', |
123 | 123 |
'form_url', |
124 | 124 |
], |
125 | 125 |
'merge_extra': True, |
126 | 126 |
} |
127 | ||
128 |
UPDATE_SCHEMA = { |
|
129 |
'$schema': 'http://json-schema.org/draft-04/schema#', |
|
130 |
'type': 'object', |
|
131 |
'properties': { |
|
132 |
'data': { |
|
133 |
'type': 'object', |
|
134 |
'properties': { |
|
135 |
'status': { |
|
136 |
'description': "Nouveau Statut de l'intervention", |
|
137 |
'type': 'string', |
|
138 |
'maxLength': 20, |
|
139 |
}, |
|
140 |
'date_planification': { |
|
141 |
'description': "Date de planification de l'intervention", |
|
142 |
'type': 'string', |
|
143 |
'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}', |
|
144 |
}, |
|
145 |
'date_mise_en_securite': { |
|
146 |
'description': "Date de mise en securite de l'intervention", |
|
147 |
'type': 'string', |
|
148 |
'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}', |
|
149 |
}, |
|
150 |
'type_retour': { |
|
151 |
'description': "Type du retour", |
|
152 |
'type': 'string', |
|
153 |
}, |
|
154 |
'type_retour_cloture': { |
|
155 |
'description': "Type du retour de la clôture", |
|
156 |
'type': 'string', |
|
157 |
}, |
|
158 |
'libelle_cloture': { |
|
159 |
'description': "Libellé de la clôture", |
|
160 |
'type': 'string', |
|
161 |
}, |
|
162 |
'commentaire_cloture': { |
|
163 |
'description': "Commentaire de la clôture", |
|
164 |
'type': 'string', |
|
165 |
}, |
|
166 |
}, |
|
167 |
'required': ['status'], |
|
168 |
}, |
|
169 |
}, |
|
170 |
'required': ['data'], |
|
171 |
} |
tests/test_toulouse_smart.py | ||
---|---|---|
25 | 25 |
import httmock |
26 | 26 |
import lxml.etree as ET |
27 | 27 |
import mock |
28 | 28 |
import pytest |
29 | 29 |
import utils |
30 | 30 |
from test_manager import login |
31 | 31 | |
32 | 32 |
from passerelle.base.models import Job |
33 |
from passerelle.contrib.toulouse_smart.models import ToulouseSmartResource, WcsRequest |
|
33 |
from passerelle.contrib.toulouse_smart.models import SmartRequest, ToulouseSmartResource, WcsRequest
|
|
34 | 34 | |
35 | 35 |
TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'toulouse_smart') |
36 | 36 | |
37 | 37 | |
38 | 38 |
@pytest.fixture |
39 | 39 |
def smart(db): |
40 | 40 |
return utils.make_resource( |
41 | 41 |
ToulouseSmartResource, |
... | ... | |
43 | 43 |
slug='test', |
44 | 44 |
description='Test', |
45 | 45 |
webservice_base_url='https://smart.example.com/', |
46 | 46 |
basic_auth_username='username', |
47 | 47 |
basic_auth_password='password', |
48 | 48 |
) |
49 | 49 | |
50 | 50 | |
51 |
@pytest.fixture |
|
52 |
def wcs_service(settings): |
|
53 |
wcs_service = { |
|
54 |
'default': { |
|
55 |
'title': 'test', |
|
56 |
'url': 'https://wcs.example.com', |
|
57 |
'secret': 'xxx', |
|
58 |
'orig': 'passerelle', |
|
59 |
}, |
|
60 |
} |
|
61 |
settings.KNOWN_SERVICES = {'wcs': wcs_service} |
|
62 |
return wcs_service |
|
63 | ||
64 | ||
51 | 65 |
def mock_response(*path_contents): |
52 | 66 |
def decorator(func): |
53 | 67 |
@httmock.urlmatch() |
54 | 68 |
def error(url, request): |
55 | 69 |
assert False, 'request to %s' % url.geturl() |
56 | 70 | |
57 | 71 |
def register(path, payload, content, status_code=200): |
58 | 72 |
@httmock.urlmatch(path=path) |
... | ... | |
476 | 490 |
freezer.move_to('2021-07-08 00:00:00') |
477 | 491 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
478 | 492 |
assert not resp.json['err'] |
479 | 493 |
job = Job.objects.get(method_name='create_intervention_job') |
480 | 494 |
assert job.status == 'failed' |
481 | 495 |
assert 'invalid json' in job.status_details['error_summary'] |
482 | 496 |
wcs_request = smart.wcs_requests.get(uuid=UUID) |
483 | 497 |
assert wcs_request.status == 'error' |
498 | ||
499 | ||
500 |
UPDATE_INTERVENTION_PAYLOAD = { |
|
501 |
'data': { |
|
502 |
'status': 'close manque info', |
|
503 |
'type_retour_cloture': 'Smart non Fait', |
|
504 |
'libelle_cloture': "rien à l'adresse indiquée", |
|
505 |
'commentaire_cloture': 'le commentaire', |
|
506 |
} |
|
507 |
} |
|
508 |
UPDATE_INTERVENTION_QUERY = UPDATE_INTERVENTION_PAYLOAD |
|
509 |
WCS_RESPONSE_SUCCESS = '{"err": 0, "url": null}' |
|
510 |
WCS_RESPONSE_ERROR = '{"err": 1, "err_class": "Access denied", "err_desc": null}' |
|
511 | ||
512 | ||
513 |
@mock_response( |
|
514 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
515 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
516 |
['/foo/2/jump/trigger/my-trigger', UPDATE_INTERVENTION_QUERY, WCS_RESPONSE_SUCCESS], |
|
517 |
) |
|
518 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
519 |
def test_update_intervention(mocked_uuid, app, smart, wcs_service): |
|
520 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
521 |
assert not resp.json['err'] |
|
522 |
assert CREATE_INTERVENTION_QUERY['notificationUrl'] == 'update-intervention?uuid=%s' % str(UUID) |
|
523 |
wcs_request = smart.wcs_requests.get(uuid=UUID) |
|
524 |
assert wcs_request.status == 'sent' |
|
525 | ||
526 |
assert Job.objects.filter(method_name='update_intervention_job').count() == 0 |
|
527 |
url = URL + 'update-intervention?uuid=%s&trigger=my-trigger' % str(UUID) |
|
528 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
529 |
assert not resp.json['err'] |
|
530 |
assert resp.json['data']['payload']['data']['type_retour_cloture'] == 'Smart non Fait' |
|
531 |
assert resp.json['data']['wcs_request']['fields']['uuid'] == str(UUID) |
|
532 |
assert resp.json['data']['smart_request']['fields']['trigger'] == 'my-trigger' |
|
533 |
job = Job.objects.get(method_name='update_intervention_job') |
|
534 |
assert job.status == 'completed' |
|
535 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
536 |
assert smart_request.trigger == 'my-trigger' |
|
537 |
assert smart_request.status == 'sent' |
|
538 | ||
539 | ||
540 |
def test_update_intervention_wrong_uuid(app, smart): |
|
541 |
with pytest.raises(WcsRequest.DoesNotExist): |
|
542 |
smart.wcs_requests.get(uuid=UUID) |
|
543 | ||
544 |
url = URL + 'update-intervention?uuid=%s&trigger=my-trigger' % str(UUID) |
|
545 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD, status=400) |
|
546 |
assert resp.json['err'] |
|
547 |
assert 'Cannot find intervention' in resp.json['err_desc'] |
|
548 |
assert SmartRequest.objects.count() == 0 |
|
549 | ||
550 | ||
551 |
@mock_response( |
|
552 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
553 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
554 |
) |
|
555 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
556 |
def test_update_intervention_job_wrong_service(mocked_uuid, app, smart, wcs_service): |
|
557 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
558 |
assert not resp.json['err'] |
|
559 | ||
560 |
wcs_service['default']['url'] = 'http://wrong.example.com' |
|
561 |
url = URL + 'update-intervention?uuid=%s&trigger=my-trigger' % str(UUID) |
|
562 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
563 |
assert not resp.json['err'] |
|
564 |
job = Job.objects.get(method_name='update_intervention_job') |
|
565 |
assert job.status == 'failed' |
|
566 |
assert 'Cannot find wcs service' in job.status_details['error_summary'] |
|
567 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
568 |
assert smart_request.status == 'service-error' |
|
569 | ||
570 | ||
571 |
@mock_response( |
|
572 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
573 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
574 |
['/foo/2/jump/trigger/inexistant-trigger', UPDATE_INTERVENTION_QUERY, WCS_RESPONSE_ERROR, 403], |
|
575 |
) |
|
576 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
577 |
def test_update_intervention_job_wcs_error(mocked_uuid, app, smart, wcs_service): |
|
578 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
579 |
assert not resp.json['err'] |
|
580 | ||
581 |
url = URL + 'update-intervention?uuid=%s&trigger=inexistant-trigger' % str(UUID) |
|
582 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
583 |
assert not resp.json['err'] |
|
584 |
job = Job.objects.get(method_name='update_intervention_job') |
|
585 |
assert job.status == 'failed' |
|
586 |
assert 'Access denied' in job.status_details['error_summary'] |
|
587 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
588 |
assert smart_request.status == 'wcs-error' |
|
589 | ||
590 | ||
591 |
@mock_response( |
|
592 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
593 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
594 |
['/foo/2/jump/trigger/my-trigger', UPDATE_INTERVENTION_QUERY, None, 500], |
|
595 |
) |
|
596 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
597 |
def test_update_intervention_job_transport_error(mocked_uuid, app, freezer, smart, wcs_service): |
|
598 |
freezer.move_to('2021-07-08 00:00:00') |
|
599 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
600 |
assert not resp.json['err'] |
|
601 | ||
602 |
url = URL + 'update-intervention?uuid=%s&trigger=my-trigger' % str(UUID) |
|
603 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
604 |
assert not resp.json['err'] |
|
605 |
job = Job.objects.get(method_name='update_intervention_job') |
|
606 |
assert job.status == 'registered' |
|
607 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
608 |
assert smart_request.status == 'registered' |
|
609 | ||
610 |
freezer.move_to('2021-07-08 00:00:03') |
|
611 |
smart.jobs() |
|
612 |
job = Job.objects.get(method_name='update_intervention_job') |
|
613 |
assert job.status == 'registered' |
|
614 |
assert job.update_timestamp > job.creation_timestamp |
|
615 | ||
616 | ||
617 |
@mock_response( |
|
618 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
619 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
620 |
['/foo/2/jump/trigger/trigger-1', UPDATE_INTERVENTION_QUERY, None, 500], |
|
621 |
) |
|
622 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
623 |
def test_update_intervention_job_order(mocked_uuid, app, freezer, smart, wcs_service): |
|
624 |
freezer.move_to('2021-07-08 00:00:00') |
|
625 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
626 |
assert not resp.json['err'] |
|
627 | ||
628 |
url = URL + 'update-intervention?uuid=%s&trigger=trigger-1' % str(UUID) |
|
629 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
630 |
assert not resp.json['err'] |
|
631 |
url = URL + 'update-intervention?uuid=%s&trigger=trigger-2' % str(UUID) |
|
632 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
633 |
assert not resp.json['err'] |
|
634 |
assert smart.wcs_requests.get(uuid=UUID).smart_requests.count() == 2 |
|
635 | ||
636 |
freezer.move_to('2021-07-08 00:00:03') |
|
637 |
jobs = Job.objects.filter(method_name='update_intervention_job') |
|
638 |
jobs[1].run() |
|
639 |
jobs = Job.objects.filter(method_name='update_intervention_job') |
|
640 |
assert jobs[0].status == 'registered' |
|
641 |
assert jobs[0].update_timestamp == jobs[0].creation_timestamp |
|
642 |
assert jobs[1].status == 'registered' |
|
643 |
assert jobs[1].update_timestamp > jobs[1].creation_timestamp |
|
484 |
- |