Repository: CS-SI/eodag
Pull request #1117 (2024-04-26 16:00:14)

feat(server): order and storage extensions usage
[order](https://github.com/stac-extensions/order) and [storage](https://github.com/stac-extensions/storage) STAC extensions usage in server-mode in order to return `EOProduct.properties["storageStatus"]` in server response

github-actions[bot]: ## Test Results
    4 files  ±0      4 suites  ±0   4m 28s :stopwatch: -1s
  508 tests +1    505 :white_check_mark: +1   3 :zzz: ±0  0 :x: ±0 
2 032 runs  +4  1 942 :white_check_mark: +2  90 :zzz: +2  0 :x: ±0 

Results for commit bc1efc3e. ± Comparison against base commit ad46243a.

[test-results]:data:application/gzip;base64,H4sIAI3QK2YC/03MSw6DIBSF4a0Yxh0AAko30/BMSFUaHiPTvfdqFR3+3809K/Jhchk9O/boUK6htLA1qRLiAknFCACnsh05bvXK1Zid+EXv8AHqG3gVJgDcwKUU0yGpLtsmxT096twkkl3035Tnx31z7/ukifMcCgTShjhvesekYBoTT7TRdBCK02EUYExQay0R6PsDNq5zKwgBAAA=

github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/server-order-storage/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                     11       0  100.00%
cli.py                                         303      51  83.17%   60, 646-692, 794-845, 849
config.py                                      318      27  91.51%   80-82, 91, 99, 103-105, 178, 190, 391-393, 457-460, 507-508, 517-518, 597, 666-671, 673
crunch.py                                        6       6  0.00%    18-24
api/__init__.py                                  1       0  100.00%
api/core.py                                    739      76  89.72%   88-97, 370, 597, 641-644, 682, 787, 791-796, 822, 892, 969, 1082, 1172-1184, 1224, 1226, 1230, 1253, 1257-1268, 1281-1287, 1377-1380, 1409-1429, 1486, 1503-1506, 1518-1521, 1889, 1922-1928, 2193, 2197-2200, 2214-2216, 2251
api/search_result.py                            44       6  86.36%   33-35, 70, 79, 86, 100
api/product/__init__.py                          6       0  100.00%
api/product/_assets.py                          44       5  88.64%   27-29, 79, 129
api/product/_product.py                        211      32  84.83%   53-60, 64-66, 170-177, 261-262, 352, 388, 449, 463-466, 479, 503-506, 549-555
api/product/metadata_mapping.py                649      82  87.37%   67-69, 130-132, 233, 265-266, 318-330, 332, 343, 349-361, 402-405, 442, 463-466, 489, 497-498, 571-572, 596-597, 603-606, 621-622, 771, 817, 934-939, 1070, 1084-1104, 1124, 1129, 1239, 1261, 1275, 1288-1307, 1346, 1398, 1436-1440, 1459
api/product/drivers/__init__.py                  6       0  100.00%
api/product/drivers/base.py                      8       2  75.00%   23, 41
plugins/__init__.py                              1       0  100.00%
plugins/base.py                                 23       3  86.96%   25, 48, 55
plugins/manager.py                             132      14  89.39%   49-51, 105-110, 156, 195, 215, 241, 280-281
plugins/apis/__init__.py                         1       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           98      15  84.69%   47-55, 156-158, 205-206, 232-234
plugins/apis/usgs.py                           172      36  79.07%   59-64, 128, 201, 235, 270-272, 277, 303-304, 309, 339-346, 357-362, 384-390, 392-398, 421
plugins/authentication/__init__.py               7       1  85.71%   31
plugins/authentication/aws_auth.py              20       2  90.00%   25-27
plugins/authentication/base.py                  19       3  84.21%   26, 34, 47
plugins/authentication/generic.py               16       3  81.25%   28, 40, 50
plugins/authentication/header.py                17       1  94.12%   27
plugins/authentication/keycloak.py              88      17  80.68%   32-34, 159-160, 190-212, 238-243
plugins/authentication/oauth.py                 15       8  46.67%   25, 32-34, 38-41
plugins/authentication/openid_connect.py       103      64  37.86%   39-41, 138-150, 154-172, 180-222, 228-237, 246-286, 291-299, 304-305
plugins/authentication/qsauth.py                36       2  94.44%   32, 83
plugins/authentication/sas_auth.py              49       2  95.92%   32, 76
plugins/authentication/token.py                 81      13  83.95%   35-37, 86, 99, 101, 114-117, 165-168
plugins/crunch/__init__.py                       1       0  100.00%
plugins/crunch/base.py                          10       2  80.00%   25, 38
plugins/crunch/filter_date.py                   62      15  75.81%   30, 53-58, 72, 81, 90, 93, 105-107, 116-118, 125
plugins/crunch/filter_latest_intersect.py       50      10  80.00%   32-34, 51-52, 71, 80-83, 85, 92-95
plugins/crunch/filter_latest_tpl_name.py        33       2  93.94%   28, 86
plugins/crunch/filter_overlap.py                68      17  75.00%   28-30, 33, 82-85, 91, 99, 110-126
plugins/crunch/filter_property.py               33       8  75.76%   29, 60-65, 68-69, 85-89
plugins/download/__init__.py                     1       0  100.00%
plugins/download/aws.py                        491     165  66.40%   77-83, 272, 285, 352-355, 369-373, 419-421, 425, 458-459, 465-469, 502, 537, 541, 548, 578-586, 590, 628-636, 643-645, 686-760, 778-839, 850-855, 871-884, 913, 928-930, 933, 943-951, 959-972, 982-1001, 1008-1020, 1061, 1087, 1132-1134, 1354
plugins/download/base.py                       261      57  78.16%   58-64, 145, 180, 319-320, 340-346, 377-381, 387-388, 432, 435-449, 461, 465, 538-542, 572-573, 581-598, 605-613, 615-619, 666, 689, 711, 719
plugins/download/creodias_s3.py                 17       9  47.06%   44-58
plugins/download/http.py                       458      94  79.48%   82-88, 122, 196-208, 211, 292-297, 321, 323, 387, 414-416, 426, 432, 434, 458, 497-501, 558, 644-700, 718, 751-760, 786-787, 814, 838, 846-851, 873-874, 881, 942-948, 1003-1004, 1010, 1020, 1086, 1090, 1104-1120
plugins/download/s3rest.py                     117      27  76.92%   55-58, 124, 165, 199, 229-236, 239-241, 245, 258-264, 272-273, 276-280, 303, 324-327
plugins/search/__init__.py                       1       0  100.00%
plugins/search/base.py                         127       9  92.91%   49-54, 108, 112, 275, 295, 378
plugins/search/build_search_result.py          178      22  87.64%   62, 97, 142-143, 149, 160, 290-293, 327, 384-396, 458, 461, 471, 488, 516, 518
plugins/search/creodias_s3.py                   54       3  94.44%   55, 73, 107
plugins/search/csw.py                          107      83  22.43%   43-45, 57-58, 62-63, 74-122, 128-141, 149-181, 199-240
plugins/search/data_request_search.py          197      65  67.01%   52, 89-92, 108, 120, 124-125, 139, 144, 149, 156, 169-172, 226-227, 231, 241-247, 252, 280-283, 291-302, 319, 321, 328-329, 331-332, 350-354, 387, 394, 405, 418, 424-436, 441
plugins/search/qssearch.py                     548      54  90.15%   89, 361-367, 375-376, 483-489, 544-547, 620-621, 662, 680, 695, 748, 769, 772-773, 782, 793, 802, 825, 885-890, 894-895, 923, 994, 1041, 1117-1121, 1187-1188, 1209, 1234, 1240, 1271-1272, 1282-1288, 1331, 1345, 1365, 1455
plugins/search/static_stac_search.py            47       3  93.62%   39-40, 82
rest/__init__.py                                 5       2  60.00%   21-22
rest/core.py                                   199      15  92.46%   71-77, 261, 588, 590, 593-595, 667, 674-678
rest/server.py                                 299      54  81.94%   80-81, 107, 130-131, 242-244, 260, 300-301, 313-329, 415-420, 451, 612-619, 651, 694-695, 783-785, 802-807, 837, 839, 843-844, 848-849
rest/stac.py                                   437      89  79.63%   60-62, 273, 295, 314-320, 367, 404-406, 429, 464-465, 551-596, 641, 661-662, 842, 907-909, 1128, 1138-1150, 1163-1185, 1199-1244, 1403-1404
rest/types/__init__.py                           1       0  100.00%
rest/types/eodag_search.py                     185       9  95.14%   52-55, 231-235, 288, 291, 359
rest/types/stac_queryables.py                   30       2  93.33%   28, 114
rest/types/stac_search.py                      122      10  91.80%   48-51, 167, 182-184, 192, 196
rest/utils/__init__.py                          96      13  86.46%   51, 108-109, 128-130, 183, 193-207
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           31       3  90.32%   78, 90, 92
types/__init__.py                               76       6  92.11%   53, 87, 154, 174, 179, 187
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                           9       0  100.00%
types/queryables.py                             81       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 104
types/whoosh.py                                 15       0  100.00%
utils/__init__.py                              497      43  91.35%   83, 88, 109-111, 192-193, 202-229, 232, 246, 328-332, 408-412, 433-435, 517, 522, 532, 570-571, 967-970, 978-979, 1020-1021, 1101, 1185, 1203, 1378
utils/constraints.py                           123      42  65.85%   31, 84-93, 134, 139, 143, 154, 177-178, 189-197, 206, 220-236, 245-256
utils/exceptions.py                             37       2  94.59%   23, 93
utils/import_system.py                          30      20  33.33%   27, 67-81, 93-103
utils/logging.py                                29       1  96.55%   123
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/rest.py                                   28       1  96.43%   49
utils/stac_reader.py                            91      28  69.23%   55-56, 63-86, 93-95, 99, 141, 155-158
TOTAL                                         8715    1521  82.55%
```

</details>


### Diff against develop

```
Filename        Stmts    Miss  Cover
------------  -------  ------  -------
rest/stac.py       +2      -6  +1.47%
TOTAL              +2      -6  +0.08%
```


Results for commit: bc1efc3e4964b01f1bcb276a52786964462ddd16


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.12-ubuntu-latest/coverage.xml -->

Patch:
diff --git a/eodag/resources/stac.yml b/eodag/resources/stac.yml
index 83b7109a..7e631caa 100644
--- a/eodag/resources/stac.yml
+++ b/eodag/resources/stac.yml
@@ -211,6 +211,7 @@ item:
     version: '$.product.properties.productVersion'
     view:sun_elevation: '$.product.properties.illuminationElevationAngle'
     view:sun_azimuth: '$.product.properties.illuminationAzimuthAngle'
+    order:status: '{$.product.properties.storageStatus#get_group_name((?P<succeeded>ONLINE)|(?P<shipping>STAGING)|(?P<orderable>OFFLINE))}'
   links:
     - rel: self
       title: "{item[id]}"
@@ -236,10 +237,12 @@ metadata_ignore:
 # extensions mapping {prefix: schema_url,}
 stac_extensions:
   eo: 'https://stac-extensions.github.io/eo/v1.0.0/schema.json'
+  order: 'https://stac-extensions.github.io/order/v1.1.0/schema.json'
   published: 'https://stac-extensions.github.io/timestamps/v1.0.0/schema.json'
   processing: 'https://stac-extensions.github.io/processing/v1.0.0/schema.json'
   sar: 'https://stac-extensions.github.io/sar/v1.0.0/schema.json'
   sat: 'https://stac-extensions.github.io/sat/v1.0.0/schema.json'
+  storage: 'https://stac-extensions.github.io/storage/v1.0.0/schema.json'
   sci: 'https://stac-extensions.github.io/scientific/v1.0.0/schema.json'
   version: 'https://stac-extensions.github.io/version/v1.0.0/schema.json'
   view: 'https://stac-extensions.github.io/view/v1.0.0/schema.json'
diff --git a/eodag/rest/stac.py b/eodag/rest/stac.py
index d8b9b392..6c73a583 100644
--- a/eodag/rest/stac.py
+++ b/eodag/rest/stac.py
@@ -340,6 +340,8 @@ class StacItem(StacCommon):
             "href": downloadlink_href,
             "type": "application/zip",
         }
+        if "storageStatus" in product.properties:
+            assets["downloadLink"]["storage:tier"] = product.properties["storageStatus"]
 
         # move origin asset urls to alternate links and replace with eodag-server ones
         if product.assets:


Test patch:
diff --git a/tests/units/test_http_server.py b/tests/units/test_http_server.py
index 1a52a341..c98c77d6 100644
--- a/tests/units/test_http_server.py
+++ b/tests/units/test_http_server.py
@@ -220,6 +220,7 @@ class RequestTestCase(unittest.TestCase):
                             "parentIdentifier": None,
                             "sensorMode": None,
                             "quicklook": None,
+                            "storageStatus": ONLINE_STATUS,
                         },
                         "id": "578f1768-e66e-5b86-9363-b19f8931cc7b",
                         "type": "Feature",
@@ -280,6 +281,7 @@ class RequestTestCase(unittest.TestCase):
                             "parentIdentifier": None,
                             "sensorMode": None,
                             "quicklook": None,
+                            "storageStatus": OFFLINE_STATUS,
                         },
                         "id": "578f1768-e66e-5b86-9363-b19f8931cc7c",
                         "type": "Feature",
@@ -480,6 +482,51 @@ class RequestTestCase(unittest.TestCase):
             ),
         )
 
+    def test_items_response(self):
+        """Returned items properties must be mapped as expected"""
+        resp_json = self._request_valid(
+            f"search?collections={self.tested_product_type}",
+        )
+        res = resp_json.features
+        self.assertEqual(len(res), 2)
+        first_props = res[0]["properties"]
+        self.assertCountEqual(
+            res[0].keys(),
+            [
+                "type",
+                "stac_version",
+                "stac_extensions",
+                "bbox",
+                "collection",
+                "links",
+                "assets",
+                "id",
+                "geometry",
+                "properties",
+            ],
+        )
+        self.assertEqual(len(first_props["providers"]), 1)
+        self.assertCountEqual(
+            first_props["providers"][0].keys(),
+            ["name", "description", "roles", "url", "priority"],
+        )
+        self.assertEqual(first_props["providers"][0]["name"], "peps")
+        self.assertEqual(first_props["providers"][0]["roles"], ["host"])
+        self.assertEqual(first_props["providers"][0]["url"], "https://peps.cnes.fr")
+        self.assertEqual(first_props["datetime"], "2018-02-15T23:53:22.871Z")
+        self.assertEqual(first_props["start_datetime"], "2018-02-15T23:53:22.871Z")
+        self.assertEqual(first_props["end_datetime"], "2018-02-16T00:12:14.035Z")
+        self.assertEqual(first_props["license"], "proprietary")
+        self.assertEqual(first_props["platform"], "S1A")
+        self.assertEqual(first_props["instruments"], ["SAR-C SAR"])
+        self.assertEqual(first_props["eo:cloud_cover"], 0)
+        self.assertEqual(first_props["sat:absolute_orbit"], 20624)
+        self.assertEqual(first_props["sar:product_type"], "OCN")
+        self.assertEqual(first_props["order:status"], "succeeded")
+        self.assertEqual(res[0]["assets"]["downloadLink"]["storage:tier"], "ONLINE")
+        self.assertEqual(res[1]["assets"]["downloadLink"]["storage:tier"], "OFFLINE")
+        self.assertEqual(res[1]["properties"]["order:status"], "orderable")
+
     def test_not_found(self):
         """A request to eodag server with a not supported product type must return a 404 HTTP error code"""
         self._request_not_found("search?collections=ZZZ&bbox=0,43,1,44")