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-30 22:37 |
|
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_20210731_0031'), |
|
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 |
('payload', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), |
|
25 |
('result', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), |
|
26 |
( |
|
27 |
'status', |
|
28 |
models.CharField( |
|
29 |
choices=[('registered', 'Registered'), ('sent', 'Sent')], |
|
30 |
default='registered', |
|
31 |
max_length=20, |
|
32 |
), |
|
33 |
), |
|
34 |
( |
|
35 |
'resource', |
|
36 |
models.ForeignKey( |
|
37 |
on_delete=django.db.models.deletion.CASCADE, |
|
38 |
related_name='smart_requests', |
|
39 |
to='toulouse_smart.WcsRequest', |
|
40 |
verbose_name='SmartRequest', |
|
41 |
), |
|
42 |
), |
|
43 |
], |
|
44 |
options={ |
|
45 |
'ordering': ['pk', 'status'], |
|
46 |
}, |
|
47 |
), |
|
48 |
] |
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.db import models, transaction |
25 |
from django.utils.six.moves.urllib import parse as urlparse |
|
23 | 26 |
from django.utils.text import slugify |
24 | 27 |
from django.utils.timezone import now |
25 | 28 |
from django.utils.translation import ugettext_lazy as _ |
26 | 29 |
from requests import RequestException |
27 | 30 | |
28 | 31 |
from passerelle.base.models import BaseResource, HTTPResource, SkipJob |
29 | 32 |
from passerelle.utils import xml |
30 | 33 |
from passerelle.utils.api import endpoint |
31 | 34 |
from passerelle.utils.jsonresponse import APIError |
35 |
from passerelle.utils.wcs import WcsApi, WcsApiError |
|
32 | 36 | |
33 | 37 |
from . import schemas |
34 | 38 | |
35 | 39 | |
36 | 40 |
class ToulouseSmartResource(BaseResource, HTTPResource): |
37 | 41 |
category = _('Business Process Connectors') |
38 | 42 | |
39 | 43 |
webservice_base_url = models.URLField(_('Webservice Base URL')) |
... | ... | |
235 | 239 |
} |
236 | 240 |
} |
237 | 241 | |
238 | 242 |
def create_intervention_job(self, *args, **kwargs): |
239 | 243 |
wcs_request = self.wcs_requests.get(pk=kwargs['pk']) |
240 | 244 |
if not wcs_request.push(): |
241 | 245 |
raise SkipJob() |
242 | 246 | |
247 |
@endpoint( |
|
248 |
name='update-intervention', |
|
249 |
methods=['post'], |
|
250 |
description=_('Update an intervention status'), |
|
251 |
parameters={ |
|
252 |
'id': {'description': _('Intervention identifier')}, |
|
253 |
}, |
|
254 |
post={'request_body': {'schema': {'application/json': schemas.UPDATE_SCHEMA}}}, |
|
255 |
) |
|
256 |
def update_intervention(self, request, uuid, post_data): |
|
257 |
with transaction.atomic(): |
|
258 |
try: |
|
259 |
wcs_request = self.wcs_requests.get(uuid=uuid) |
|
260 |
except WcsRequest.DoesNotExist: |
|
261 |
raise APIError("Cannot find intervention '%s'" % uuid, http_status=400) |
|
262 |
smart_request = wcs_request.smart_requests.create(payload=post_data) |
|
263 |
smart_request.status = 'registered' |
|
264 |
smart_request.save() |
|
265 |
if smart_request.push(): |
|
266 |
smart_request = SmartRequest.objects.get(id=smart_request.id) |
|
267 |
if smart_request.status != 'sent': |
|
268 |
raise APIError(smart_request.result) |
|
269 |
else: |
|
270 |
self.add_job( |
|
271 |
'update_intervention_job', |
|
272 |
id=smart_request.id, |
|
273 |
natural_id='smart-request-%s' % smart_request.id, |
|
274 |
) |
|
275 |
smart_request = SmartRequest.objects.get(id=smart_request.id) |
|
276 |
return { |
|
277 |
'data': { |
|
278 |
'wcs_form_api_url': wcs_request.wcs_form_api_url, |
|
279 |
'wcs_form_number': wcs_request.wcs_form_number, |
|
280 |
'uuid': wcs_request.uuid, |
|
281 |
'payload': smart_request.payload, |
|
282 |
'result': smart_request.result, |
|
283 |
'status': smart_request.status, |
|
284 |
} |
|
285 |
} |
|
286 | ||
287 |
def update_intervention_job(self, *args, **kwargs): |
|
288 |
smart_request = SmartRequest.objects.get(id=kwargs['id']) |
|
289 |
if not smart_request.push(): |
|
290 |
raise SkipJob() |
|
291 | ||
243 | 292 | |
244 | 293 |
class Cache(models.Model): |
245 | 294 |
resource = models.ForeignKey( |
246 | 295 |
verbose_name=_('Resource'), |
247 | 296 |
to=ToulouseSmartResource, |
248 | 297 |
on_delete=models.CASCADE, |
249 | 298 |
related_name='cache_entries', |
250 | 299 |
) |
... | ... | |
290 | 339 |
except ValueError: |
291 | 340 |
err_desc = 'invalid json, got: %s' % response.text |
292 | 341 |
self.result = err_desc |
293 | 342 |
self.save() |
294 | 343 |
return False |
295 | 344 |
self.status = 'sent' |
296 | 345 |
self.save() |
297 | 346 |
return True |
347 | ||
348 | ||
349 |
class SmartRequest(models.Model): |
|
350 |
resource = models.ForeignKey( |
|
351 |
verbose_name=_('SmartRequest'), |
|
352 |
to=WcsRequest, |
|
353 |
on_delete=models.CASCADE, |
|
354 |
related_name='smart_requests', |
|
355 |
) |
|
356 |
payload = JSONField(default=dict) |
|
357 |
result = JSONField(default=dict) |
|
358 |
status = models.CharField( |
|
359 |
max_length=20, |
|
360 |
default='registered', |
|
361 |
choices=( |
|
362 |
('registered', _('Registered')), |
|
363 |
('sent', _('Sent')), |
|
364 |
), |
|
365 |
) |
|
366 | ||
367 |
class Meta: |
|
368 |
ordering = ['pk', 'status'] |
|
369 | ||
370 |
def push(self): |
|
371 |
base_url = '%shooks/update-intervention/' % (self.resource.wcs_form_api_url) |
|
372 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(base_url) |
|
373 |
services = settings.KNOWN_SERVICES.get('wcs', {}) |
|
374 |
service = None |
|
375 |
for service in services.values(): |
|
376 |
remote_url = service.get('url') |
|
377 |
r_scheme, r_netloc, r_path, r_params, r_query, r_fragment = urlparse.urlparse(remote_url) |
|
378 |
if r_scheme == scheme and r_netloc == netloc: |
|
379 |
break |
|
380 |
else: |
|
381 |
err_desc = 'Cannot find wcs service for %s' % base_url |
|
382 |
self.result = err_desc |
|
383 |
self.save() |
|
384 |
return True |
|
385 | ||
386 |
wcs_api = WcsApi(base_url, orig=service.get('orig'), key=service.get('secret')) |
|
387 |
headers = { |
|
388 |
'Content-Type': 'application/json', |
|
389 |
'Accept': 'application/json', |
|
390 |
} |
|
391 |
try: |
|
392 |
result = wcs_api.post_json(self.payload, [], headers=headers) |
|
393 |
except WcsApiError as e: |
|
394 |
try: |
|
395 |
result = json.loads(e.args[3]) |
|
396 |
except (ValueError): |
|
397 |
return False |
|
398 |
self.result = result |
|
399 |
if result['err']: |
|
400 |
self.save() |
|
401 |
return False |
|
402 |
self.status = 'sent' |
|
403 |
self.save() |
|
404 |
return True |
passerelle/contrib/toulouse_smart/schemas.py | ||
---|---|---|
119 | 119 |
'submitterType', |
120 | 120 |
'external_number', |
121 | 121 |
'external_status', |
122 | 122 |
'address', |
123 | 123 |
'form_api_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) |
... | ... | |
514 | 528 |
) |
515 | 529 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
516 | 530 |
def test_create_intervention_content_error(mocked_uuid, app, freezer, smart): |
517 | 531 |
freezer.move_to('2021-07-08 00:00:00') |
518 | 532 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
519 | 533 |
wcs_request = smart.wcs_requests.get(uuid=UUID) |
520 | 534 |
assert wcs_request.status == 'registered' |
521 | 535 |
assert 'invalid json' in wcs_request.result |
536 | ||
537 | ||
538 |
UPDATE_INTERVENTION_PAYLOAD = { |
|
539 |
'data': { |
|
540 |
'status': 'close manque info', |
|
541 |
'type_retour_cloture': 'Smart non Fait', |
|
542 |
'libelle_cloture': "rien à l'adresse indiquée", |
|
543 |
'commentaire_cloture': 'le commentaire', |
|
544 |
} |
|
545 |
} |
|
546 |
UPDATE_INTERVENTION_QUERY = UPDATE_INTERVENTION_PAYLOAD |
|
547 |
WCS_RESPONSE_SUCCESS = '{"err": 0, "url": null}' |
|
548 |
WCS_RESPONSE_ERROR = '{"err": 1, "err_class": "Access denied", "err_desc": null}' |
|
549 | ||
550 | ||
551 |
@mock_response( |
|
552 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
553 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
554 |
['/api/forms/foo/2/hooks/update-intervention/', UPDATE_INTERVENTION_QUERY, WCS_RESPONSE_SUCCESS], |
|
555 |
) |
|
556 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
557 |
def test_update_intervention(mocked_uuid, app, smart, wcs_service): |
|
558 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
559 |
assert not resp.json['err'] |
|
560 |
assert CREATE_INTERVENTION_QUERY['notificationUrl'] == 'update-intervention?uuid=%s' % str(UUID) |
|
561 |
wcs_request = smart.wcs_requests.get(uuid=UUID) |
|
562 |
assert wcs_request.status == 'sent' |
|
563 | ||
564 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
565 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
566 |
assert not resp.json['err'] |
|
567 |
assert resp.json['data']['uuid'] == str(UUID) |
|
568 |
assert resp.json['data']['payload']['data']['type_retour_cloture'] == 'Smart non Fait' |
|
569 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
570 |
assert smart_request.status == 'sent' |
|
571 |
assert smart_request.result == {'err': 0, 'url': None} |
|
572 | ||
573 | ||
574 |
@mock_response( |
|
575 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
576 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
577 |
['/api/forms/foo/2/hooks/update-intervention/', UPDATE_INTERVENTION_QUERY, WCS_RESPONSE_SUCCESS], |
|
578 |
) |
|
579 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
580 |
def test_update_intervention_async(mocked_uuid, app, smart, wcs_service): |
|
581 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
582 |
assert not resp.json['err'] |
|
583 |
assert CREATE_INTERVENTION_QUERY['notificationUrl'] == 'update-intervention?uuid=%s' % str(UUID) |
|
584 |
wcs_request = smart.wcs_requests.get(uuid=UUID) |
|
585 |
assert wcs_request.status == 'sent' |
|
586 | ||
587 |
mocked_push = mock.patch( |
|
588 |
"passerelle.contrib.toulouse_smart.models.SmartRequest.push", |
|
589 |
return_value=False, |
|
590 |
) |
|
591 |
mocked_push.start() |
|
592 | ||
593 |
assert Job.objects.count() == 0 |
|
594 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
595 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
596 |
assert not resp.json['err'] |
|
597 |
assert resp.json['data']['uuid'] == str(UUID) |
|
598 |
assert resp.json['data']['payload']['data']['type_retour_cloture'] == 'Smart non Fait' |
|
599 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
600 |
assert smart_request.status == 'registered' |
|
601 | ||
602 |
mocked_push.stop() |
|
603 |
assert Job.objects.count() == 1 |
|
604 |
job = Job.objects.get(method_name='update_intervention_job') |
|
605 |
assert job.status == 'registered' |
|
606 |
smart.jobs() |
|
607 |
job = Job.objects.get(method_name='update_intervention_job') |
|
608 |
assert job.status == 'completed' |
|
609 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
610 |
assert smart_request.status == 'sent' |
|
611 |
assert smart_request.result == {'err': 0, 'url': None} |
|
612 | ||
613 | ||
614 |
def test_update_intervention_wrong_uuid(app, smart): |
|
615 |
with pytest.raises(WcsRequest.DoesNotExist): |
|
616 |
smart.wcs_requests.get(uuid=UUID) |
|
617 | ||
618 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
619 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD, status=400) |
|
620 |
assert resp.json['err'] |
|
621 |
assert 'Cannot find intervention' in resp.json['err_desc'] |
|
622 |
assert SmartRequest.objects.count() == 0 |
|
623 | ||
624 | ||
625 |
@mock_response( |
|
626 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
627 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
628 |
) |
|
629 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
630 |
def test_update_intervention_job_wrong_service(mocked_uuid, app, smart, wcs_service): |
|
631 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
632 |
assert not resp.json['err'] |
|
633 | ||
634 |
wcs_service['default']['url'] = 'http://wrong.example.com' |
|
635 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
636 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
637 |
assert resp.json['err'] |
|
638 |
assert 'Cannot find wcs service' in resp.json['err_desc'] |
|
639 |
assert SmartRequest.objects.count() == 0 |
|
640 | ||
641 | ||
642 |
@mock_response( |
|
643 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
644 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
645 |
['/api/forms/foo/2/hooks/update-intervention/', UPDATE_INTERVENTION_QUERY, WCS_RESPONSE_ERROR, 403], |
|
646 |
) |
|
647 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
648 |
def test_update_intervention_job_wcs_error(mocked_uuid, app, smart, wcs_service): |
|
649 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
650 |
assert not resp.json['err'] |
|
651 | ||
652 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
653 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
654 |
assert not resp.json['err'] |
|
655 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
656 |
assert smart_request.status == 'registered' |
|
657 |
assert smart_request.result == {'err': 1, 'err_class': 'Access denied', 'err_desc': None} |
|
658 | ||
659 | ||
660 |
@mock_response( |
|
661 |
['/v1/type-intervention', None, INTERVENTION_TYPES], |
|
662 |
['/v1/intervention', CREATE_INTERVENTION_QUERY, get_json_file('create_intervention')], |
|
663 |
['/api/forms/foo/2/hooks/update-intervention/', UPDATE_INTERVENTION_QUERY, 'bla', 500], |
|
664 |
) |
|
665 |
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID) |
|
666 |
def test_update_intervention_job_transport_error(mocked_uuid, app, freezer, smart, wcs_service): |
|
667 |
freezer.move_to('2021-07-08 00:00:00') |
|
668 |
resp = app.post_json(URL + 'create-intervention/', params=CREATE_INTERVENTION_PAYLOAD) |
|
669 |
assert not resp.json['err'] |
|
670 | ||
671 |
url = URL + 'update-intervention?uuid=%s' % str(UUID) |
|
672 |
resp = app.post_json(url, params=UPDATE_INTERVENTION_PAYLOAD) |
|
673 |
assert not resp.json['err'] |
|
674 |
job = Job.objects.get(method_name='update_intervention_job') |
|
675 |
assert job.status == 'registered' |
|
676 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
677 |
assert smart_request.status == 'registered' |
|
678 |
assert smart_request.result == {} |
|
679 | ||
680 |
freezer.move_to('2021-07-08 00:00:03') |
|
681 |
smart.jobs() |
|
682 |
job = Job.objects.get(method_name='update_intervention_job') |
|
683 |
assert job.status == 'registered' |
|
684 |
assert job.update_timestamp > job.creation_timestamp |
|
685 |
smart_request = smart.wcs_requests.get(uuid=UUID).smart_requests.get() |
|
686 |
assert smart_request.status == 'registered' |
|
687 |
assert smart_request.result == {} |
|
522 |
- |