Repository: CS-SI/eodag
Pull request #609 (2023-01-17 13:39:21)

fix: AwsDownload SAFE build fix and more tests
- renamed `AwsDownload.get_bucket_name_and_prefix()` to `AwsDownload.get_product_bucket_name_and_prefix()`
- asset out of SAFE pattern won't break `AwsDownload.download()`
- more `AwsDownload` tests

github-actions[bot]: ## Unit Test Results
    2 files  ±  0      2 suites  ±0   12m 48s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests") +30s
348 tests +  6  345 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +  6  3 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 
696 runs  +12  690 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") +12  6 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") ±0  0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests") ±0 

Results for commit 360a6699. ± Comparison against base commit b5486f71.

<details>
  <summary>This pull request <b>removes</b> 3 and <b>adds</b> 9 tests. <i>Note that renamed tests count towards both.</i></summary>

```
tests.units.test_safe_build.TestSafeBuild ‑ test_s1_sar_grd_safe_build
tests.units.test_safe_build.TestSafeBuild ‑ test_s2_msi_l1c_safe_build
tests.units.test_safe_build.TestSafeBuild ‑ test_s2_msi_l2a_safe_build
```

```
tests.units.test_download_plugins.TestDownloadPluginAws ‑ test_plugins_download_aws_no_safe_build_flatten_top_dirs
tests.units.test_download_plugins.TestDownloadPluginAws ‑ test_plugins_download_aws_no_safe_build_no_flatten_top_dirs
tests.units.test_download_plugins.TestDownloadPluginAws ‑ test_plugins_download_aws_safe_build
tests.units.test_download_plugins.TestDownloadPluginAws ‑ test_plugins_download_aws_safe_build_assets
tests.units.test_safe_build.TestSafeBuild ‑ test_safe_build_no_safe
tests.units.test_safe_build.TestSafeBuild ‑ test_safe_build_out_of_pattern
tests.units.test_safe_build.TestSafeBuild ‑ test_safe_build_s1_sar_grd
tests.units.test_safe_build.TestSafeBuild ‑ test_safe_build_s2_msi_l1c
tests.units.test_safe_build.TestSafeBuild ‑ test_safe_build_s2_msi_l2a
```
</details>

[test-results]:data:application/gzip;base64,H4sIAHenxmMC/03MTQ7CIBCG4as0rF1QoAPjZQwBmhD7YyisGu8u1Dp2+T4z+XY2xils7N6JW8e2EjOFL8nmuC41NZgK9ZTbUSqqx1acO2j40zO+GhGMNk4VOEFIaU2npLK0TUA44zcJyEm+i/RxGTz6uufWeY65BpPALQCicr3WXEiDwQ3aKuN7H4T0/WikFojs/QFxhIOsBQEAAA==

github-actions[bot]: <strong> </strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `86%` | :white_check_mark: |
| eodag/plugins/download/aws.py | `71%` | :white_check_mark: |
| tests/units/test\_download\_plugins.py | `98%` | :white_check_mark: |
| tests/units/test\_safe\_build.py | `94%` | :white_check_mark: |

<strong> </strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `86%` | :white_check_mark: |
| eodag/plugins/download/aws.py | `71%` | :white_check_mark: |
| tests/units/test\_download\_plugins.py | `98%` | :white_check_mark: |
| tests/units/test\_safe\_build.py | `94%` | :white_check_mark: |
| eodag/plugins/download/aws.py | `71%` | :white_check_mark: |
| tests/units/test\_download\_plugins.py | `97%` | :white_check_mark: |
| tests/units/test\_safe\_build.py | `94%` | :white_check_mark: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against 360a66994c17702389ec57a48d1de23d1f837299 </p>

Patch:
diff --git a/eodag/plugins/download/aws.py b/eodag/plugins/download/aws.py
index 8c28dcce..41c2c605 100644
--- a/eodag/plugins/download/aws.py
+++ b/eodag/plugins/download/aws.py
@@ -40,7 +40,7 @@ from eodag.utils import (
     path_to_uri,
     rename_subfolder,
 )
-from eodag.utils.exceptions import AuthenticationError, DownloadError
+from eodag.utils.exceptions import AuthenticationError, DownloadError, NotAvailableError
 from eodag.utils.stac_reader import HTTP_REQ_TIMEOUT
 
 logger = logging.getLogger("eodag.plugins.download.aws")
@@ -274,23 +274,25 @@ class AwsDownload(Download):
             bucket_names_and_prefixes = []
             for complementary_url in getattr(product, "assets", {}).values():
                 bucket_names_and_prefixes.append(
-                    self.get_bucket_name_and_prefix(
+                    self.get_product_bucket_name_and_prefix(
                         product, complementary_url.get("href", "")
                     )
                 )
         else:
-            bucket_names_and_prefixes = [self.get_bucket_name_and_prefix(product)]
+            bucket_names_and_prefixes = [
+                self.get_product_bucket_name_and_prefix(product)
+            ]
 
         # add complementary urls
         try:
             for complementary_url_key in product_conf.get("complementary_url_key", []):
                 bucket_names_and_prefixes.append(
-                    self.get_bucket_name_and_prefix(
+                    self.get_product_bucket_name_and_prefix(
                         product, product.properties[complementary_url_key]
                     )
                 )
         except KeyError:
-            logger.error(
+            logger.warning(
                 "complementary_url_key %s is missing in %s properties"
                 % (complementary_url_key, product.properties["id"])
             )
@@ -375,12 +377,16 @@ class AwsDownload(Download):
         progress_callback.reset(total=total_size)
         try:
             for product_chunk in unique_product_chunks:
-                chunk_rel_path = self.get_chunk_dest_path(
-                    product,
-                    product_chunk,
-                    build_safe=build_safe,
-                    dir_prefix=prefix,
-                )
+                try:
+                    chunk_rel_path = self.get_chunk_dest_path(
+                        product,
+                        product_chunk,
+                        build_safe=build_safe,
+                    )
+                except NotAvailableError as e:
+                    # out of SAFE format chunk
+                    logger.warning(e)
+                    continue
                 chunk_abs_path = os.path.join(product_local_path, chunk_rel_path)
                 chunk_abs_path_dir = os.path.dirname(chunk_abs_path)
                 if not os.path.isdir(chunk_abs_path_dir):
@@ -421,7 +427,7 @@ class AwsDownload(Download):
             self.finalize_s2_safe_product(product_local_path)
         # flatten directory structure
         elif flatten_top_dirs:
-            flatten_top_directories(product_local_path, common_prefix)
+            flatten_top_directories(product_local_path)
 
         if build_safe:
             self.check_manifest_file_list(product_local_path)
@@ -588,7 +594,7 @@ class AwsDownload(Download):
         self.s3_session = s3_session
         return objects
 
-    def get_bucket_name_and_prefix(self, product, url=None):
+    def get_product_bucket_name_and_prefix(self, product, url=None):
         """Extract bucket name and prefix from product URL
 
         :param product: The EO product to download
@@ -694,8 +700,8 @@ class AwsDownload(Download):
             logger.exception("Could not finalize SAFE product from downloaded data")
             raise DownloadError(e)
 
-    def get_chunk_dest_path(self, product, chunk, dir_prefix, build_safe=False):
-        """Get chunk destination path"""
+    def get_chunk_dest_path(self, product, chunk, dir_prefix=None, build_safe=False):
+        """Get chunk SAFE destination path"""
         if build_safe:
             # S2 common
             if "S2_MSI" in product.product_type:
@@ -912,10 +918,17 @@ class AwsDownload(Download):
                     product.properties["title"],
                     found_dict["file"],
                 )
+            # out of SAFE format
+            else:
+                raise NotAvailableError(
+                    f"Ignored {chunk.key} out of SAFE matching pattern"
+                )
         # no SAFE format
         else:
+            if not dir_prefix:
+                dir_prefix = chunk.key
             product_path = chunk.key.split(dir_prefix.strip("/") + "/")[-1]
-        logger.debug("Downloading %s to %s" % (chunk.key, product_path))
+        logger.debug(f"Downloading {chunk.key} to {product_path}")
         return product_path
 
     def download_all(


Test patch:
diff --git a/tests/units/test_download_plugins.py b/tests/units/test_download_plugins.py
index 902ba08f..e7ebbc94 100644
--- a/tests/units/test_download_plugins.py
+++ b/tests/units/test_download_plugins.py
@@ -589,26 +589,252 @@ class TestDownloadPluginAws(BaseDownloadPluginTest):
             ),
             productType="S2_MSI_L2A",
         )
+        self.product.location = (
+            self.product.remote_location
+        ) = "http://somebucket.somehost.com/path/to/some/product"
 
     def test_plugins_download_aws_get_bucket_prefix(self):
-        """AwsDownload.get_bucket_name_and_prefix() must extract bucket & prefix from location"""
+        """AwsDownload.get_product_bucket_name_and_prefix() must extract bucket & prefix from location"""
 
         plugin = self.get_download_plugin(self.product)
         plugin.config.products["S2_MSI_L2A"]["default_bucket"] = "default_bucket"
-        self.product.properties["id"] = "someproduct"
 
-        self.product.location = (
-            self.product.remote_location
-        ) = "http://somebucket.somehost.com/path/to/some/product"
-        bucket, prefix = plugin.get_bucket_name_and_prefix(self.product)
+        bucket, prefix = plugin.get_product_bucket_name_and_prefix(self.product)
         self.assertEqual((bucket, prefix), ("somebucket", "path/to/some/product"))
 
         plugin.config.bucket_path_level = 1
-        bucket, prefix = plugin.get_bucket_name_and_prefix(self.product)
+        bucket, prefix = plugin.get_product_bucket_name_and_prefix(self.product)
         self.assertEqual((bucket, prefix), ("to", "some/product"))
         del plugin.config.bucket_path_level
 
-        bucket, prefix = plugin.get_bucket_name_and_prefix(
+        bucket, prefix = plugin.get_product_bucket_name_and_prefix(
             self.product, url="/somewhere/else"
         )
         self.assertEqual((bucket, prefix), ("default_bucket", "somewhere/else"))
+
+    @mock.patch("eodag.plugins.download.aws.flatten_top_directories", autospec=True)
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.check_manifest_file_list", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.finalize_s2_safe_product", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_chunk_dest_path", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_authenticated_objects",
+        autospec=True,
+    )
+    @mock.patch("eodag.plugins.download.aws.requests.get", autospec=True)
+    def test_plugins_download_aws_no_safe_build_no_flatten_top_dirs(
+        self,
+        mock_requests_get,
+        mock_get_authenticated_objects,
+        mock_get_chunk_dest_path,
+        mock_finalize_s2_safe_product,
+        mock_check_manifest_file_list,
+        mock_flatten_top_directories,
+    ):
+        """AwsDownload.download() must not call safe build methods if not needed"""
+
+        plugin = self.get_download_plugin(self.product)
+        self.product.properties["tileInfo"] = "http://example.com/tileInfo.json"
+
+        # no SAFE build and no flatten_top_dirs
+        plugin.config.products[self.product.product_type]["build_safe"] = False
+        plugin.config.flatten_top_dirs = False
+
+        plugin.download(self.product, outputs_prefix=self.output_dir)
+
+        mock_get_authenticated_objects.assert_called_once_with(
+            plugin, "somebucket", "path/to/some", None
+        )
+        self.assertEqual(mock_get_chunk_dest_path.call_count, 0)
+        self.assertEqual(mock_finalize_s2_safe_product.call_count, 0)
+        self.assertEqual(mock_check_manifest_file_list.call_count, 0)
+        self.assertEqual(mock_flatten_top_directories.call_count, 0)
+
+    @mock.patch("eodag.plugins.download.aws.flatten_top_directories", autospec=True)
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.check_manifest_file_list", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.finalize_s2_safe_product", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_chunk_dest_path", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_authenticated_objects",
+        autospec=True,
+    )
+    @mock.patch("eodag.plugins.download.aws.requests.get", autospec=True)
+    def test_plugins_download_aws_no_safe_build_flatten_top_dirs(
+        self,
+        mock_requests_get,
+        mock_get_authenticated_objects,
+        mock_get_chunk_dest_path,
+        mock_finalize_s2_safe_product,
+        mock_check_manifest_file_list,
+        mock_flatten_top_directories,
+    ):
+        """AwsDownload.download() must not call safe build methods if not needed"""
+
+        plugin = self.get_download_plugin(self.product)
+        self.product.properties["tileInfo"] = "http://example.com/tileInfo.json"
+
+        # no SAFE build and flatten_top_dirs
+        plugin.config.products[self.product.product_type]["build_safe"] = False
+        plugin.config.flatten_top_dirs = True
+
+        plugin.download(self.product, outputs_prefix=self.output_dir)
+
+        mock_get_authenticated_objects.assert_called_once_with(
+            plugin, "somebucket", "path/to/some", None
+        )
+        self.assertEqual(mock_get_chunk_dest_path.call_count, 0)
+        self.assertEqual(mock_finalize_s2_safe_product.call_count, 0)
+        self.assertEqual(mock_check_manifest_file_list.call_count, 0)
+        mock_flatten_top_directories.assert_called_once_with(
+            os.path.join(self.output_dir, self.product.properties["title"]),
+        )
+
+    @mock.patch("eodag.plugins.download.aws.flatten_top_directories", autospec=True)
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.check_manifest_file_list", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.finalize_s2_safe_product", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_chunk_dest_path", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_authenticated_objects",
+        autospec=True,
+    )
+    @mock.patch("eodag.plugins.download.aws.requests.get", autospec=True)
+    def test_plugins_download_aws_safe_build(
+        self,
+        mock_requests_get,
+        mock_get_authenticated_objects,
+        mock_get_chunk_dest_path,
+        mock_finalize_s2_safe_product,
+        mock_check_manifest_file_list,
+        mock_flatten_top_directories,
+    ):
+        """AwsDownload.download() must call safe build methods if needed"""
+
+        plugin = self.get_download_plugin(self.product)
+        self.product.properties["tileInfo"] = "http://example.com/tileInfo.json"
+        execpected_output = os.path.join(
+            self.output_dir, self.product.properties["title"]
+        )
+        # fetch_metadata return
+        mock_requests_get.return_value.json.return_value = {
+            "title": "foo",
+            "productPath": "s3://example/here/is/productPath",
+        }
+        # authenticated objects mock
+        mock_get_authenticated_objects.return_value.keys.return_value = [
+            "somebucket",
+            "example",
+        ]
+        mock_get_authenticated_objects.return_value.filter.side_effect = (
+            lambda *x, **y: [mock.Mock(size=0, key=y["Prefix"])]
+        )
+        # # chunk dest path mock
+        mock_get_chunk_dest_path.side_effect = lambda *x, **y: x[2].key
+
+        # SAFE build
+        plugin.config.products[self.product.product_type]["build_safe"] = True
+
+        path = plugin.download(self.product, outputs_prefix=self.output_dir)
+
+        self.assertEqual(mock_get_authenticated_objects.call_count, 2)
+        mock_get_authenticated_objects.assert_any_call(
+            plugin, "somebucket", "path/to/some", None
+        )
+        mock_get_authenticated_objects.assert_any_call(
+            plugin, "example", "here/is", None
+        )
+        self.assertEqual(mock_get_chunk_dest_path.call_count, 2)
+        mock_get_chunk_dest_path.assert_any_call(
+            plugin, product=self.product, chunk=mock.ANY, build_safe=True
+        )
+        mock_finalize_s2_safe_product.assert_called_once_with(plugin, execpected_output)
+        mock_check_manifest_file_list.assert_called_once_with(plugin, execpected_output)
+        self.assertEqual(mock_flatten_top_directories.call_count, 0)
+
+        self.assertEqual(path, execpected_output)
+
+    @mock.patch("eodag.plugins.download.aws.flatten_top_directories", autospec=True)
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.check_manifest_file_list", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.finalize_s2_safe_product", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_chunk_dest_path", autospec=True
+    )
+    @mock.patch(
+        "eodag.plugins.download.aws.AwsDownload.get_authenticated_objects",
+        autospec=True,
+    )
+    @mock.patch("eodag.plugins.download.aws.requests.get", autospec=True)
+    def test_plugins_download_aws_safe_build_assets(
+        self,
+        mock_requests_get,
+        mock_get_authenticated_objects,
+        mock_get_chunk_dest_path,
+        mock_finalize_s2_safe_product,
+        mock_check_manifest_file_list,
+        mock_flatten_top_directories,
+    ):
+        """AwsDownload.download() must call safe build methods if needed"""
+
+        plugin = self.get_download_plugin(self.product)
+        self.product.properties["tileInfo"] = "http://example.com/tileInfo.json"
+        self.product.properties["tilePath"] = "http://example.com/tilePath"
+        self.product.assets = {
+            "file1": {"href": "http://example.com/path/to/file1"},
+            "file2": {"href": "http://example.com/path/to/file2"},
+        }
+        execpected_output = os.path.join(
+            self.output_dir, self.product.properties["title"]
+        )
+        # fetch_metadata return
+        mock_requests_get.return_value.json.return_value = {
+            "title": "foo",
+            "productPath": "s3://example/here/is/productPath",
+        }
+        # authenticated objects mock
+        mock_get_authenticated_objects.return_value.keys.return_value = [
+            "somebucket",
+            "example",
+        ]
+        mock_get_authenticated_objects.return_value.filter.side_effect = (
+            lambda *x, **y: [mock.Mock(size=0, key=y["Prefix"])]
+        )
+        # chunk dest path mock
+        mock_get_chunk_dest_path.side_effect = lambda *x, **y: x[2].key
+
+        # SAFE build
+        plugin.config.products[self.product.product_type]["build_safe"] = True
+
+        path = plugin.download(self.product, outputs_prefix=self.output_dir)
+
+        mock_get_authenticated_objects.assert_called_once_with(
+            plugin, "example", "", None
+        )
+        self.assertEqual(mock_get_chunk_dest_path.call_count, 4)
+        mock_get_chunk_dest_path.assert_any_call(
+            plugin, product=self.product, chunk=mock.ANY, build_safe=True
+        )
+        mock_finalize_s2_safe_product.assert_called_once_with(plugin, execpected_output)
+        mock_check_manifest_file_list.assert_called_once_with(plugin, execpected_output)
+        self.assertEqual(mock_flatten_top_directories.call_count, 0)
+
+        self.assertEqual(path, execpected_output)
diff --git a/tests/units/test_safe_build.py b/tests/units/test_safe_build.py
index f41cb6bd..4526156e 100644
--- a/tests/units/test_safe_build.py
+++ b/tests/units/test_safe_build.py
@@ -28,6 +28,7 @@ from tests.context import (
     TESTS_DOWNLOAD_PATH,
     AwsDownload,
     EOProduct,
+    NotAvailableError,
 )
 
 
@@ -48,7 +49,53 @@ class TestSafeBuild(unittest.TestCase):
         if os.path.isdir(TESTS_DOWNLOAD_PATH):
             rmtree(TESTS_DOWNLOAD_PATH)
 
-    def test_s1_sar_grd_safe_build(self):
+    def test_safe_build_out_of_pattern(self):
+        """Cannot build SAFE product from out of pattern file"""
+        prod = EOProduct(
+            provider="some_provider",
+            properties=self.aws_sentinel_chunks["S1_SAR_GRD"]["properties"],
+            productType="S1_SAR_GRD",
+        )
+
+        def chunk():
+            return None
+
+        chunk.key = "path/to/some/dummy_chunk"
+
+        with self.assertRaisesRegex(
+            NotAvailableError, f"Ignored {chunk.key} out of SAFE matching pattern"
+        ):
+            self.awsd.get_chunk_dest_path(
+                prod,
+                chunk,
+                build_safe=True,
+            )
+
+    def test_safe_build_no_safe(self):
+        """Do not use patterns if no SAFE is asked"""
+        prod = EOProduct(
+            provider="some_provider",
+            properties=self.aws_sentinel_chunks["S1_SAR_GRD"]["properties"],
+            productType="S1_SAR_GRD",
+        )
+
+        def chunk():
+            return None
+
+        chunk.key = "path/to/some/dummy_chunk"
+        chunk_dest_path = self.awsd.get_chunk_dest_path(
+            prod,
+            chunk,
+            build_safe=False,
+        )
+        self.assertEqual(chunk_dest_path, chunk.key)
+
+        chunk_dest_path = self.awsd.get_chunk_dest_path(
+            prod, chunk, build_safe=False, dir_prefix="path/to"
+        )
+        self.assertEqual(chunk_dest_path, "some/dummy_chunk")
+
+    def test_safe_build_s1_sar_grd(self):
         """build S1_SAR_GRD SAFE product with empty files and check content"""
 
         prod = EOProduct(
@@ -65,7 +112,9 @@ class TestSafeBuild(unittest.TestCase):
         for chunk_key in self.aws_sentinel_chunks["S1_SAR_GRD"]["chunks"]:
             chunk.key = chunk_key
             chunk_dest_rel_path = self.awsd.get_chunk_dest_path(
-                prod, chunk, build_safe=True, dir_prefix=""
+                prod,
+                chunk,
+                build_safe=True,
             )
 
             chunk_abs_path = os.path.join(product_path, chunk_dest_rel_path)
@@ -90,7 +139,7 @@ class TestSafeBuild(unittest.TestCase):
         self.assertEqual(len(cm.output), 1)
         self.assertIn("Dummy warning", cm.output[0])
 
-    def test_s2_msi_l2a_safe_build(self):
+    def test_safe_build_s2_msi_l2a(self):
         """build S2_MSI_L2A SAFE product with empty files and check content"""
 
         prod = EOProduct(
@@ -107,7 +156,9 @@ class TestSafeBuild(unittest.TestCase):
         for chunk_key in self.aws_sentinel_chunks["S2_MSI_L2A"]["chunks"]:
             chunk.key = chunk_key
             chunk_dest_rel_path = self.awsd.get_chunk_dest_path(
-                prod, chunk, build_safe=True, dir_prefix=""
+                prod,
+                chunk,
+                build_safe=True,
             )
 
             chunk_abs_path = os.path.join(product_path, chunk_dest_rel_path)
@@ -134,7 +185,7 @@ class TestSafeBuild(unittest.TestCase):
         self.assertEqual(len(cm.output), 1)
         self.assertIn("Dummy warning", cm.output[0])
 
-    def test_s2_msi_l1c_safe_build(self):
+    def test_safe_build_s2_msi_l1c(self):
         """build S2_MSI_L1C SAFE product with empty files and check content"""
 
         prod = EOProduct(
@@ -151,7 +202,9 @@ class TestSafeBuild(unittest.TestCase):
         for chunk_key in self.aws_sentinel_chunks["S2_MSI_L1C"]["chunks"]:
             chunk.key = chunk_key
             chunk_dest_rel_path = self.awsd.get_chunk_dest_path(
-                prod, chunk, build_safe=True, dir_prefix=""
+                prod,
+                chunk,
+                build_safe=True,
             )
 
             chunk_abs_path = os.path.join(product_path, chunk_dest_rel_path)