3
3
import unittest
4
4
import warnings
5
5
from collections import OrderedDict
6
+ from datetime import date
6
7
from distutils .version import LooseVersion
7
8
from typing import TYPE_CHECKING
8
9
9
10
import mock
10
11
import pyramid .testing
11
12
import pytest
13
+ from dateutil import parser as dateparser
12
14
13
15
from tests .utils import (
14
16
get_module_version ,
35
37
from weaver .visibility import VISIBILITY_PRIVATE , VISIBILITY_PUBLIC
36
38
from weaver .warning import TimeZoneInfoAlreadySetWarning
37
39
from weaver .wps_restapi import swagger_definitions as sd
40
+ from weaver .wps_restapi .swagger_definitions import (
41
+ DATETIME_INTERVAL_CLOSED_SYMBOL ,
42
+ DATETIME_INTERVAL_OPEN_END_SYMBOL ,
43
+ DATETIME_INTERVAL_OPEN_START_SYMBOL
44
+ )
38
45
39
46
if TYPE_CHECKING :
40
47
from typing import Iterable , List , Tuple , Union
@@ -51,6 +58,7 @@ def setUpClass(cls):
51
58
cls .config = setup_config_with_mongodb (settings = settings )
52
59
cls .app = get_test_weaver_app (config = cls .config )
53
60
cls .json_headers = {"Accept" : CONTENT_TYPE_APP_JSON , "Content-Type" : CONTENT_TYPE_APP_JSON }
61
+ cls .datetime_interval = cls .generate_test_datetimes ()
54
62
55
63
@classmethod
56
64
def tearDownClass (cls ):
@@ -94,22 +102,25 @@ def setUp(self):
94
102
self .make_job (task_id = "4444-4444-4444-4444" , process = self .process_public .identifier , service = None ,
95
103
user_id = self .user_admin_id , status = STATUS_FAILED , progress = 55 , access = VISIBILITY_PRIVATE )
96
104
# job public/private service/process combinations
97
- self .make_job (task_id = "5555-5555-5555-5555" ,
98
- process = self .process_public . identifier , service = self .service_public . name ,
105
+ self .make_job (task_id = "5555-5555-5555-5555" , process = self . process_public . identifier ,
106
+ service = self .service_public . name , created = self .datetime_interval [ 0 ] ,
99
107
user_id = self .user_editor1_id , status = STATUS_FAILED , progress = 99 , access = VISIBILITY_PUBLIC )
100
- self .make_job (task_id = "6666-6666-6666-6666" ,
101
- process = self .process_private . identifier , service = self .service_public . name ,
108
+ self .make_job (task_id = "6666-6666-6666-6666" , process = self . process_private . identifier ,
109
+ service = self .service_public . name , created = self .datetime_interval [ 1 ] ,
102
110
user_id = self .user_editor1_id , status = STATUS_FAILED , progress = 99 , access = VISIBILITY_PUBLIC )
103
- self .make_job (task_id = "7777-7777-7777-7777" ,
104
- process = self .process_public . identifier , service = self .service_private . name ,
111
+ self .make_job (task_id = "7777-7777-7777-7777" , process = self . process_public . identifier ,
112
+ service = self .service_private . name , created = self .datetime_interval [ 2 ] ,
105
113
user_id = self .user_editor1_id , status = STATUS_FAILED , progress = 99 , access = VISIBILITY_PUBLIC )
106
- self .make_job (task_id = "8888-8888-8888-8888" ,
107
- process = self .process_private . identifier , service = self .service_private . name ,
114
+ self .make_job (task_id = "8888-8888-8888-8888" , process = self . process_private . identifier ,
115
+ service = self .service_private . name , created = self .datetime_interval [ 3 ] ,
108
116
user_id = self .user_editor1_id , status = STATUS_FAILED , progress = 99 , access = VISIBILITY_PUBLIC )
109
117
110
- def make_job (self , task_id , process , service , user_id , status , progress , access ):
118
+ def make_job (self , task_id , process , service , user_id , status , progress , access , created = None ):
119
+
120
+ created = dateparser .parse (created ) if created else None
121
+
111
122
job = self .job_store .save_job (task_id = task_id , process = process , service = service , is_workflow = False ,
112
- user_id = user_id , execute_async = True , access = access )
123
+ user_id = user_id , execute_async = True , access = access , created = created )
113
124
job .status = status
114
125
if status in JOB_STATUS_CATEGORIES [STATUS_CATEGORY_FINISHED ]:
115
126
job .mark_finished ()
@@ -144,6 +155,15 @@ def get_job_request_auth_mock(self, user_id):
144
155
mock .patch ("{}.has_permission" .format (authz_policy_class ), return_value = is_admin ),
145
156
])
146
157
158
+ @staticmethod
159
+ def generate_test_datetimes ():
160
+ # type: () -> List[str]
161
+ """
162
+ Generates a list of dummy datetimes for testing.
163
+ """
164
+ year = date .today ().year + 1
165
+ return ["{}-0{}-02T03:32:38.487000+00:00" .format (year , month ) for month in range (1 , 5 )]
166
+
147
167
@staticmethod
148
168
def check_job_format (job ):
149
169
assert isinstance (job , dict )
@@ -503,3 +523,162 @@ def filter_service(jobs): # type: (Iterable[Job]) -> List[Job]
503
523
job_match = all (job in job_ids for job in resp .json ["jobs" ])
504
524
test_values = dict (path = path , access = access , user_id = user_id )
505
525
assert job_match , self .message_with_jobs_diffs (resp .json ["jobs" ], job_ids , test_values , index = i )
526
+
527
+ def test_jobs_list_with_limit_api (self ):
528
+ """
529
+ .. seealso::
530
+ - `/req/collections/rc-limit-response
531
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-limit-response.adoc>`_
532
+ """
533
+ limit_parameter = 20
534
+ path = get_path_kvp (sd .jobs_service .path , limit = limit_parameter )
535
+ resp = self .app .get (path , headers = self .json_headers )
536
+ assert resp .status_code == 200
537
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
538
+ assert "limit" in resp .json and isinstance (resp .json ["limit" ], int )
539
+ assert resp .json ["limit" ] == limit_parameter
540
+ assert len (resp .json ["jobs" ]) <= limit_parameter
541
+
542
+ def test_jobs_list_with_limit_openapi_schema (self ):
543
+ """
544
+ .. seealso::
545
+ - `/req/collections/rc-limit-response
546
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-limit-response.adoc>`_
547
+ """
548
+ resp = self .app .get (sd .jobs_service .path , headers = self .json_headers )
549
+ assert resp .status_code == 200
550
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
551
+ assert "limit" in resp .json and isinstance (resp .json ["limit" ], int )
552
+ assert len (resp .json ["jobs" ]) <= resp .json ["limit" ]
553
+
554
+ def test_not_required_fields (self ):
555
+ uri = sd .openapi_json_service .path
556
+ resp = self .app .get (uri , headers = self .json_headers )
557
+ assert not resp .json ["parameters" ]["page" ]["required" ]
558
+ assert not resp .json ["parameters" ]["limit" ]["required" ]
559
+
560
+ def test_jobs_datetime_before (self ):
561
+ """
562
+ .. seealso::
563
+ - `/req/collections/rc-time-collections-response
564
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
565
+ """
566
+ datetime_before = DATETIME_INTERVAL_OPEN_START_SYMBOL + self .datetime_interval [0 ]
567
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_before )
568
+ resp = self .app .get (path , headers = self .json_headers )
569
+ assert resp .status_code == 200
570
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
571
+ assert len (resp .json ["jobs" ]) == 4
572
+ for job in resp .json ["jobs" ]:
573
+ base_uri = sd .jobs_service .path + "/{}" .format (job )
574
+ path = get_path_kvp (base_uri )
575
+ resp = self .app .get (path , headers = self .json_headers )
576
+ assert resp .status_code == 200
577
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
578
+ assert dateparser .parse (resp .json ["created" ]) <= dateparser .parse (
579
+ datetime_before .replace (DATETIME_INTERVAL_OPEN_START_SYMBOL , "" ))
580
+
581
+ def test_jobs_datetime_after (self ):
582
+ """
583
+ .. seealso::
584
+ - `/req/collections/rc-time-collections-response
585
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
586
+ """
587
+ datetime_after = str (self .datetime_interval [2 ] + DATETIME_INTERVAL_OPEN_END_SYMBOL )
588
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_after )
589
+ resp = self .app .get (path , headers = self .json_headers )
590
+ assert resp .status_code == 200
591
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
592
+ assert len (resp .json ["jobs" ]) == 2
593
+ for job in resp .json ["jobs" ]:
594
+ base_uri = sd .jobs_service .path + "/{}" .format (job )
595
+ path = get_path_kvp (base_uri )
596
+ resp = self .app .get (path , headers = self .json_headers )
597
+ assert resp .status_code == 200
598
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
599
+ assert dateparser .parse (resp .json ["created" ]) >= dateparser .parse (
600
+ datetime_after .replace (DATETIME_INTERVAL_OPEN_END_SYMBOL , "" ))
601
+
602
+ def test_jobs_datetime_interval (self ):
603
+ """
604
+ .. seealso::
605
+ - `/req/collections/rc-time-collections-response
606
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
607
+ """
608
+ datetime_interval = self .datetime_interval [1 ] + DATETIME_INTERVAL_CLOSED_SYMBOL + self .datetime_interval [3 ]
609
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_interval )
610
+ resp = self .app .get (path , headers = self .json_headers )
611
+ assert resp .status_code == 200
612
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
613
+
614
+ datetime_after , datetime_before = datetime_interval .split (DATETIME_INTERVAL_CLOSED_SYMBOL )
615
+ assert len (resp .json ["jobs" ]) == 3
616
+ for job in resp .json ["jobs" ]:
617
+ base_uri = sd .jobs_service .path + "/{}" .format (job )
618
+ path = get_path_kvp (base_uri )
619
+ resp = self .app .get (path , headers = self .json_headers )
620
+ assert resp .status_code == 200
621
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
622
+ assert dateparser .parse (resp .json ["created" ]) >= dateparser .parse (datetime_after )
623
+ assert dateparser .parse (resp .json ["created" ]) <= dateparser .parse (datetime_before )
624
+
625
+ def test_jobs_datetime_match (self ):
626
+ """
627
+ .. seealso::
628
+ - `/req/collections/rc-time-collections-response
629
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
630
+ """
631
+ datetime_match = self .datetime_interval [1 ]
632
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_match )
633
+ resp = self .app .get (path , headers = self .json_headers )
634
+ assert resp .status_code == 200
635
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
636
+ assert len (resp .json ["jobs" ]) == 1
637
+ for job in resp .json ["jobs" ]:
638
+ base_uri = sd .jobs_service .path + "/{}" .format (job )
639
+ path = get_path_kvp (base_uri )
640
+ resp = self .app .get (path , headers = self .json_headers )
641
+ assert resp .status_code == 200
642
+ assert resp .content_type == CONTENT_TYPE_APP_JSON
643
+ assert dateparser .parse (resp .json ["created" ]) == dateparser .parse (datetime_match )
644
+
645
+ def test_jobs_datetime_invalid (self ):
646
+ """
647
+ .. seealso::
648
+ - `/req/collections/rc-time-collections-response
649
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
650
+
651
+ datetime_invalid is not formated against the rfc3339 datetime format,
652
+ for more details refer to https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
653
+ """
654
+ datetime_invalid = "2022-31-12 23:59:59"
655
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_invalid )
656
+ resp = self .app .get (path , headers = self .json_headers , expect_errors = True )
657
+ assert resp .status_code == 422
658
+
659
+ def test_jobs_datetime_interval_invalid (self ):
660
+ """
661
+ .. seealso::
662
+ - `/req/collections/rc-time-collections-response
663
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
664
+
665
+ datetime_invalid represents a datetime interval where the limit dates are inverted,
666
+ the minimun is greather than the maximum datetime limit
667
+ """
668
+ datetime_interval = self .datetime_interval [3 ] + DATETIME_INTERVAL_CLOSED_SYMBOL + self .datetime_interval [1 ]
669
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_interval )
670
+ resp = self .app .get (path , headers = self .json_headers , expect_errors = True )
671
+ assert resp .status_code == 422
672
+
673
+ def test_jobs_datetime_before_invalid (self ):
674
+ """
675
+ .. seealso::
676
+ - `/req/collections/rc-time-collections-response
677
+ <https://github.com/opengeospatial/ogcapi-common/blob/master/collections/requirements/collections/REQ_rc-time-collections-response.adoc>`_
678
+
679
+ datetime_before represents a bad open range datetime interval
680
+ """
681
+ datetime_before = "./" + self .datetime_interval [3 ]
682
+ path = get_path_kvp (sd .jobs_service .path , datetime = datetime_before )
683
+ resp = self .app .get (path , headers = self .json_headers , expect_errors = True )
684
+ assert resp .status_code == 422
0 commit comments