Repository: CS-SI/eodag

Issue:
Two consecutive executions will fully download twice the same products
**Describe the bug**
When running an eodag request twice in a row, products are being fully downloaded twice while eodag would be expected to detect that files already exist locally and jump on to next search result.

**Code To Reproduce**
```py
self._gateway.download_all(search_result, output_dir=self._download_dir, extract=False)
```
with search_result being :
```py
[EOProduct(id=S2A_MSIL1C_20231006T102911_N0510_R108_T31TFK_20241102T002017, provider=geodes), EOProduct(id=S2A_MSIL1C_20231006T102911_N0509_R108_T31TFK_20231006T141008, provider=geodes)]
```

**Output**
```
2025-07-01 10:33:00,174 eodag.config                     [DEBUG   ] Loading configuration from /usr/local/lib/python3.12/site-packages/eodag/resources/providers.yml
2025-07-01 10:33:00,612 eodag.plugins.manager            [DEBUG   ] CSWSearch plugin skipped, eodag[csw] or eodag[all] needed
2025-07-01 10:33:00,866 eodag.plugins.manager            [DEBUG   ] EcmwfApi plugin skipped, eodag[ecmwf] or eodag[all] needed
2025-07-01 10:33:00,867 eodag.plugins.manager            [DEBUG   ] UsgsApi plugin skipped, eodag[usgs] or eodag[all] needed
2025-07-01 10:33:00,873 eodag.config                     [INFO    ] Loading user configuration from: /app/eodag_config/eodag.yml
2025-07-01 10:33:00,882 eodag.core                       [DEBUG   ] usgs: provider needing unavailable plugin has been removed
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] aws_eos: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [DEBUG   ] ecmwf: provider needing unavailable plugin has been removed
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] cop_ads: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] cop_cds: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] meteoblue: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] hydroweb_next: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] wekeo_main: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] wekeo_ecmwf: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] wekeo_cmems: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,882 eodag.core                       [INFO    ] creodias_s3: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,883 eodag.core                       [INFO    ] dedt_lumi: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,883 eodag.core                       [INFO    ] dedl: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,883 eodag.core                       [INFO    ] geodes_s3: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,883 eodag.core                       [INFO    ] cop_ewds: provider needing auth for search has been pruned because no credentials could be found
2025-07-01 10:33:00,888 eodag.core                       [DEBUG   ] Opening product types index in /home/user/.config/eodag/.index
2025-07-01 10:33:00,894 eodag.core                       [INFO    ] Locations configuration loaded from /app/eodag_config/locations.yml
2025-07-01 10:33:02,336 eodag.core                       [INFO    ] Searching for all the products with provider geodes and a maximum of 1000 items per page.
2025-07-01 10:33:02,336 eodag.core                       [INFO    ] Iterate search over multiple pages: page #1
2025-07-01 10:33:02,337 eodag.core                       [INFO    ] Searching on provider geodes
2025-07-01 10:33:02,337 eodag.search.base                [INFO    ] geodes is configured with default sorting by 'startTimeFromAscendingNode' in ascending order
2025-07-01 10:33:02,337 eodag.search.base                [DEBUG   ] Mapping eodag product type to provider product type
2025-07-01 10:33:02,337 eodag.search.base                [DEBUG   ] Getting provider product type definition parameters for S1_SAR_GRD
2025-07-01 10:33:02,337 eodag.search.qssearch            [DEBUG   ] Building the query string that will be used for search
2025-07-01 10:33:02,337 eodag.product.metadata_mapping   [DEBUG   ] Retrieving queryable metadata from metadata_mapping
2025-07-01 10:33:02,340 eodag.search.qssearch            [INFO    ] Sending search request: https://geodes-portal.cnes.fr/api/stac/search
2025-07-01 10:33:02,340 eodag.search.qssearch            [DEBUG   ] Query parameters: {"query": {"dataset": {"eq": "PEPS_S1_L1"}, "end_datetime": {"gte": "2023-10-06T00:00:00.000Z"}, "start_datetime": {"lte": "2023-10-07T00:00:00.000Z"}, "product:type": {"eq": "GRD"}}, "intersects": {"type": "Polygon", "coordinates": [[[8.999756774, 42.4526911987], [10.3348915669, 42.4449177239], [10.3144181193, 41.4562418971], [8.9997605057, 41.4637518867], [8.999756774, 42.4526911987]]]}, "sortBy": [{"field": "start_datetime", "direction": "asc"}], "limit": 1000, "page": 1}
2025-07-01 10:33:02,340 eodag.search.qssearch            [DEBUG   ] Query kwargs: {}
/usr/local/lib/python3.12/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'geodes-portal.cnes.fr'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
2025-07-01 10:33:02,891 eodag.search.qssearch            [DEBUG   ] Next page URL could not be collected
2025-07-01 10:33:02,891 eodag.search.qssearch            [DEBUG   ] Next page Query-object could not be collected
2025-07-01 10:33:02,891 eodag.search.qssearch            [DEBUG   ] Next page merge could not be collected
2025-07-01 10:33:02,912 eodag.search.qssearch            [DEBUG   ] Adapting 2 plugin results to eodag product representation
2025-07-01 10:33:02,923 eodag.core                       [DEBUG   ] Iterate over pages: last products found on page 1
2025-07-01 10:33:02,923 eodag.core                       [INFO    ] Found 2 result(s) on provider 'geodes'
2025-07-01 10:33:02,923 eodag.core                       [INFO    ] Downloading 2 products
Downloaded products:   0%|                                                              2025-07-01 10:33:02,942 eodag.download.base              [INFO    ] Download url: https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:2250ecbb-f4b2-3e9c-8894-d8cb429894b3:V1/files/6605f5caae059dd997970057bd9afaa9
/usr/local/lib/python3.12/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'geodes-portal.cnes.fr'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
                                                                                                                                                        2025-07-01 10:34:44,398 eodag.download.http              [DEBUG   ] Download recorded in /app/tests/download_dir/.downloaded/a43acb537944a793c0fcf06647663d4f
                                                                                                                                                        2025-07-01 10:34:44,399 eodag.download.base              [INFO    ] Extraction not activated. The product is available as is.1:39<00:00, 8222326.54file/s]
S1A_IW_GRDH_1SDV_20231006T052826_20231006T052851_050640_0619D2_D8CE: 100%|██████████████████████████████████████████████| 1/1 [00:00<00:00, 841.72file/s]
2025-07-01 10:34:44,401 eodag.product                    [DEBUG   ] Product location updated from 'https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:2250ecbb-f4b2-3e9c-8894-d8cb429894b3:V1/files/6605f5caae059dd997970057bd9afaa9' to 'file:///app/tests/download_dir/S1A_IW_GRDH_1SDV_20231006T052826_20231006T052851_050640_0619D2_D8CE.zip'
2025-07-01 10:34:44,402 eodag.product                    [INFO    ] Remote location of the product is still available through its 'remote_location' property: https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:2250ecbb-f4b2-3e9c-8894-d8cb429894b3:V1/files/6605f5caae059dd997970057bd9afaa9
Downloaded products:  50%|█████████████████████████████████████████████▌                2025-07-01 10:34:44,403 eodag.download.base              [INFO    ] Download url: https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:71b5ee81-ca6c-3587-838e-6a359d1a3baf:V1/files/8c5a6c26032ec164f2b7d3e6ec5d1a6b
/usr/local/lib/python3.12/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'geodes-portal.cnes.fr'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
  warnings.warn(
                                                                                                                                                        2025-07-01 10:36:47,807 eodag.download.http              [DEBUG   ] Download recorded in /app/tests/download_dir/.downloaded/62896b85ca582d15f7ae39cffd75f9eb
                                                                                                                                                        2025-07-01 10:36:47,809 eodag.download.base              [INFO    ] Extraction not activated. The product is available as is.2:01<00:00, 3117439.03file/s]
S1A_IW_GRDH_1SDV_20231006T052851_20231006T052916_050640_0619D2_3711: 100%|█████████████████████████████████████████████| 1/1 [00:00<00:00, 1122.67file/s]
2025-07-01 10:36:47,810 eodag.product                    [DEBUG   ] Product location updated from 'https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:71b5ee81-ca6c-3587-838e-6a359d1a3baf:V1/files/8c5a6c26032ec164f2b7d3e6ec5d1a6b' to 'file:///app/tests/download_dir/S1A_IW_GRDH_1SDV_20231006T052851_20231006T052916_050640_0619D2_3711.zip'
2025-07-01 10:36:47,811 eodag.product                    [INFO    ] Remote location of the product is still available through its 'remote_location' property: https://geodes-portal.cnes.fr/api/download/URN:FEATURE:DATA:gdh:71b5ee81-ca6c-3587-838e-6a359d1a3baf:V1/files/8c5a6c26032ec164f2b7d3e6ec5d1a6b
Downloaded products: 100%|███████████████████████████████████████████████████████████████████████████████████████████| 2/2 [03:44<00:00, 112.43s/product]
```
**Environment:**
Python 3.12.11
eodag (Earth Observation Data Access Gateway): version 3.5.1

**Additional context**
provider = geodes
product type = S2_MSI_L1C
product id = S2A_MSIL1C_20231006T102911_N0509_R108_T31TFK_20231006T141008.zip and S2A_MSIL1C_20231006T102911_N0510_R108_T31TFK_20241102T002017.zip


Discussion hints:
eodag-bot: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/fix-download-again-unextracted/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         320      64  80.00%   62-63, 87, 687-750, 852-903, 907
config.py                                      450      30  93.33%   81-83, 92, 100, 104-106, 177, 188, 205-206, 723-725, 849-852, 896-897, 906-907, 941, 1021, 1080-1085, 1087
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    818      80  90.22%   380-381, 431, 700, 731, 775-778, 816, 860, 894, 939-944, 970, 1061, 1129, 1352-1364, 1404, 1406, 1436, 1440-1451, 1464-1470, 1555-1558, 1595-1615, 1684-1689, 1701-1704, 1722-1734, 2019, 2043-2049, 2300, 2304-2308, 2317-2319, 2363-2364, 2393-2394
api/search_result.py                           126      11  91.27%   87, 96, 103, 117, 282, 287-288, 336, 359-360, 366
api/product/__init__.py                         18       1  94.44%   62
api/product/_assets.py                          52       5  90.38%   97, 183, 191, 194-198
api/product/_product.py                        213      19  91.08%   72-74, 238-239, 345, 372, 473, 491, 515-518, 527-530, 585-588
api/product/metadata_mapping.py                708      63  91.10%   122-124, 222, 254-255, 312-324, 326, 337, 362, 402-403, 440, 461-464, 487, 495-496, 589-590, 614-615, 621-624, 639-640, 789, 840, 993, 1002-1006, 1023-1028, 1155, 1169-1189, 1209, 1214, 1366, 1380, 1405, 1451, 1512, 1535-1536, 1556-1560, 1576, 1584
api/product/drivers/__init__.py                 20       2  90.00%   33-36
api/product/drivers/base.py                     29       1  96.55%   98
api/product/drivers/generic.py                   7       0  100.00%
api/product/drivers/sentinel1.py                15       0  100.00%
api/product/drivers/sentinel2.py                15       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       2  90.48%   48, 55
plugins/manager.py                             173      19  89.02%   105-110, 145-150, 154, 192, 214, 218, 242, 281-282, 382-385, 397-398
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      10  89.69%   165-167, 214-215, 241-243, 296-297
plugins/apis/usgs.py                           180      26  85.56%   156, 258, 292, 327-329, 334, 360-361, 366, 396-403, 414-419, 441-447, 478
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              20       0  100.00%
plugins/authentication/base.py                  17       2  88.24%   45, 58
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   153-156, 177-182
plugins/authentication/oauth.py                 13       7  46.15%   43-45, 49-52
plugins/authentication/openid_connect.py       231      28  87.88%   92-93, 105-123, 170, 176-204, 212, 351-354, 380, 421
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              47       1  97.87%   76
plugins/authentication/token.py                128       9  92.97%   180, 217, 289-290, 340-344
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 100-102, 109-111, 118
plugins/crunch/filter_latest_intersect.py       47       8  82.98%   52-53, 69, 78-81, 83, 90-93
plugins/crunch/filter_latest_tpl_name.py        31       1  96.77%   83
plugins/crunch/filter_overlap.py                66      18  72.73%   28-30, 66-69, 76-79, 85, 93, 104-120
plugins/crunch/filter_property.py               30       5  83.33%   55-60, 63-64
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        528     158  70.08%   272, 285, 365-368, 382-386, 476-478, 482, 515-516, 522-526, 556, 589, 593, 600, 630-638, 642, 677-685, 696-698, 729-810, 828-886, 897-902, 955, 970-972, 975, 985-993, 1001-1015, 1025-1048, 1055-1068, 1106, 1132, 1177-1179, 1399
plugins/download/base.py                       260      35  86.54%   129, 157, 304-305, 363-364, 406, 410-421, 435, 503-507, 537-538, 563-571, 573-577, 627, 648, 670, 678
plugins/download/creodias_s3.py                 25       9  64.00%   55-69
plugins/download/http.py                       542      93  82.84%   228, 270-273, 334-337, 340, 347-352, 383-385, 402, 417, 471, 506, 520, 534, 542-546, 562-567, 578, 596, 632-635, 656, 666, 673, 693, 770, 789-800, 808-813, 823-840, 858, 888-897, 933, 958-959, 978-983, 992, 1007-1009, 1013, 1016, 1031-1032, 1067, 1129, 1144, 1203-1204, 1210, 1220, 1256, 1292, 1312, 1347-1349
plugins/download/s3rest.py                     120      26  78.33%   119, 153, 160, 195, 222-229, 232-234, 238, 249-255, 263-264, 267-271, 286-289, 298, 319-322
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         151      11  92.72%   101, 105, 129-135, 276, 297, 436
plugins/search/build_search_result.py          479      56  88.31%   245-246, 282, 285, 310, 313, 344-346, 556, 558, 562, 565, 692, 694, 755, 762, 786, 823, 874, 924-939, 987, 1012, 1015, 1019, 1140-1141, 1150-1159, 1222, 1243, 1262-1271, 1388-1389, 1434, 1443-1445, 1505, 1551
plugins/search/cop_marine.py                   252      47  81.35%   56, 64-66, 76-77, 82, 87-88, 104, 106, 109, 175-176, 232, 238, 242, 246, 259, 270-271, 279, 310, 314, 335, 339, 343, 347, 351-355, 361-364, 367-381, 398, 447-451, 456, 468
plugins/search/creodias_s3.py                   25       1  96.00%   51
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          207      72  65.22%   190-193, 209, 220, 224-225, 236, 241, 246, 253, 266-269, 323-324, 328, 338-344, 349, 367-371, 387-390, 398-409, 426, 428, 435-438, 440-441, 459-463, 496, 506, 517, 530, 536-551, 556
plugins/search/qssearch.py                     710      88  87.61%   448, 506, 520, 524-530, 545-546, 558-562, 675-687, 732-735, 806-807, 858, 877, 884, 896, 953, 974, 977-978, 987-988, 997-998, 1007-1008, 1035, 1119-1124, 1128-1137, 1171, 1194, 1267-1268, 1274, 1364, 1525, 1528, 1534-1535, 1556, 1584-1596, 1603, 1635-1637, 1647-1653, 1683, 1706, 1711-1712, 1727, 1743, 1830, 1837, 1848, 1871-1876, 1885, 1894
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            79      13  83.54%   100-126, 170, 183
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33       7  78.79%   35-37, 53-55, 59, 68
rest/config.py                                  25       0  100.00%
rest/constants.py                                6       0  100.00%
rest/core.py                                   260      62  76.15%   258, 301, 316-352, 448, 485-524, 601, 631-634, 707, 714-767
rest/errors.py                                  72       5  93.06%   117, 127, 138, 163-164
rest/server.py                                 194      24  87.63%   86, 109-111, 285-290, 318, 518-520, 537-542, 571, 573, 577-578, 582-583
rest/stac.py                                   333      72  78.38%   312, 334, 386-389, 416-443, 474-476, 499, 531-532, 614-654, 676-692, 784-788, 795, 850-851, 861-866, 876-880, 929, 1019-1021
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180       6  96.67%   225-229, 282, 285, 353, 375
rest/types/queryables.py                        64      12  81.25%   61, 71-79, 105-110, 174
rest/types/stac_search.py                      125       7  94.40%   128, 174, 189-191, 199, 203
rest/utils/__init__.py                          94      12  87.23%   101-102, 121-123, 175, 185-199
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           23       3  86.96%   48, 60, 62
types/__init__.py                              161      40  75.16%   67, 80-84, 95-107, 135-137, 144-149, 252, 262-278, 283, 285, 307, 312, 320, 330
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                            111       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/whoosh.py                                 81      16  80.25%   129-132, 136-143, 155-161, 174-176
utils/__init__.py                              551      40  92.74%   81, 208-209, 218-245, 248, 263, 343-347, 420-424, 443-445, 459, 551, 566, 606-607, 636, 1011-1014, 1022-1023, 1061-1062, 1109-1110, 1250
utils/cache.py                                  22       0  100.00%
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             46       0  100.00%
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       1  96.43%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       8  78.95%   51, 53, 57, 98, 122-129
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36       1  97.22%   55
utils/s3.py                                     69       3  95.65%   162-163, 226
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
TOTAL                                        10647    1649  84.51%
```

</details>


### Diff against develop

```
Filename                    Stmts    Miss  Cover
------------------------  -------  ------  -------
plugins/download/base.py       +4      -7  +2.95%
TOTAL                          +4      -7  +0.07%
```


Results for commit: 7cc2e5029bf48267c83c41bcd2dd9ad0829c629a


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.13-ubuntu-latest/coverage.xml -->
eodag-bot: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports_win/data/fix-download-again-unextracted/./badge.svg)


## Code Coverage (Windows)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         320      64  80.00%   62-63, 87, 687-750, 852-903, 907
config.py                                      450      31  93.11%   81-83, 92, 100, 104-106, 177, 188, 205-206, 723-725, 849-852, 896-897, 906-907, 941, 1021, 1050, 1080-1085, 1087
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    818      80  90.22%   380-381, 431, 700, 731, 775-778, 816, 860, 894, 939-944, 970, 1061, 1129, 1352-1364, 1404, 1406, 1436, 1440-1451, 1464-1470, 1555-1558, 1595-1615, 1684-1689, 1701-1704, 1722-1734, 2019, 2043-2049, 2300, 2304-2308, 2317-2319, 2363-2364, 2393-2394
api/search_result.py                           126      11  91.27%   87, 96, 103, 117, 282, 287-288, 336, 359-360, 366
api/product/__init__.py                         18       1  94.44%   62
api/product/_assets.py                          52       5  90.38%   97, 183, 191, 194-198
api/product/_product.py                        213      19  91.08%   72-74, 238-239, 345, 372, 473, 491, 515-518, 527-530, 585-588
api/product/metadata_mapping.py                708      63  91.10%   122-124, 222, 254-255, 312-324, 326, 337, 362, 402-403, 440, 461-464, 487, 495-496, 589-590, 614-615, 621-624, 639-640, 789, 840, 993, 1002-1006, 1023-1028, 1155, 1169-1189, 1209, 1214, 1366, 1380, 1405, 1451, 1512, 1535-1536, 1556-1560, 1576, 1584
api/product/drivers/__init__.py                 20       2  90.00%   33-36
api/product/drivers/base.py                     29       1  96.55%   98
api/product/drivers/generic.py                   7       0  100.00%
api/product/drivers/sentinel1.py                15       0  100.00%
api/product/drivers/sentinel2.py                15       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       3  85.71%   48, 55, 68
plugins/manager.py                             173      19  89.02%   105-110, 145-150, 154, 192, 214, 218, 242, 281-282, 382-385, 397-398
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      10  89.69%   165-167, 214-215, 241-243, 296-297
plugins/apis/usgs.py                           180      26  85.56%   156, 258, 292, 327-329, 334, 360-361, 366, 396-403, 414-419, 441-447, 478
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              20       0  100.00%
plugins/authentication/base.py                  17       2  88.24%   45, 58
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   153-156, 177-182
plugins/authentication/oauth.py                 13       7  46.15%   43-45, 49-52
plugins/authentication/openid_connect.py       231      28  87.88%   92-93, 105-123, 170, 176-204, 212, 351-354, 380, 421
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              47       1  97.87%   76
plugins/authentication/token.py                128       9  92.97%   180, 217, 289-290, 340-344
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 100-102, 109-111, 118
plugins/crunch/filter_latest_intersect.py       47      33  29.79%   49-54, 67-112
plugins/crunch/filter_latest_tpl_name.py        31       1  96.77%   83
plugins/crunch/filter_overlap.py                66      18  72.73%   28-30, 66-69, 76-79, 85, 93, 104-120
plugins/crunch/filter_property.py               30       5  83.33%   55-60, 63-64
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        528     158  70.08%   272, 285, 365-368, 382-386, 476-478, 482, 515-516, 522-526, 556, 589, 593, 600, 630-638, 642, 677-685, 696-698, 729-810, 828-886, 897-902, 955, 970-972, 975, 985-993, 1001-1015, 1025-1048, 1055-1068, 1106, 1132, 1177-1179, 1399
plugins/download/base.py                       260      37  85.77%   129, 157, 224-226, 304-305, 363-364, 406, 410-421, 435, 503-507, 537-538, 563-571, 573-577, 627, 648, 670, 678
plugins/download/creodias_s3.py                 25       9  64.00%   55-69
plugins/download/http.py                       542      93  82.84%   228, 270-273, 334-337, 340, 347-352, 383-385, 402, 417, 471, 506, 520, 534, 542-546, 562-567, 578, 596, 632-635, 656, 666, 673, 693, 770, 789-800, 808-813, 823-840, 858, 888-897, 933, 958-959, 978-983, 992, 1007-1009, 1013, 1016, 1031-1032, 1067, 1129, 1144, 1203-1204, 1210, 1220, 1256, 1292, 1312, 1347-1349
plugins/download/s3rest.py                     120      26  78.33%   119, 153, 160, 195, 222-229, 232-234, 238, 249-255, 263-264, 267-271, 286-289, 298, 319-322
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         151      11  92.72%   101, 105, 129-135, 276, 297, 436
plugins/search/build_search_result.py          479      63  86.85%   245-246, 282, 285, 310, 313, 344-346, 554-565, 692, 694, 755, 762, 786, 823, 874, 890, 924-939, 987, 1012, 1015, 1019, 1140-1141, 1150-1159, 1222, 1237, 1243, 1262-1271, 1388-1389, 1434, 1443-1445, 1505, 1551
plugins/search/cop_marine.py                   252      47  81.35%   56, 64-66, 76-77, 82, 87-88, 104, 106, 109, 175-176, 232, 238, 242, 246, 259, 270-271, 279, 310, 314, 335, 339, 343, 347, 351-355, 361-364, 367-381, 398, 447-451, 456, 468
plugins/search/creodias_s3.py                   25       1  96.00%   51
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          207      72  65.22%   190-193, 209, 220, 224-225, 236, 241, 246, 253, 266-269, 323-324, 328, 338-344, 349, 367-371, 387-390, 398-409, 426, 428, 435-438, 440-441, 459-463, 496, 506, 517, 530, 536-551, 556
plugins/search/qssearch.py                     710     100  85.92%   448, 506, 520, 524-530, 545-546, 558-562, 675-687, 732-735, 806-807, 858, 877, 884, 896, 953, 974, 977-978, 987-988, 997-998, 1007-1008, 1035, 1119-1124, 1128-1137, 1171, 1194, 1267-1268, 1274, 1364, 1461-1465, 1525, 1528, 1534-1535, 1556, 1584-1596, 1603, 1635-1637, 1647-1653, 1683, 1706, 1711-1712, 1727, 1743, 1818, 1830, 1837, 1848, 1871-1876, 1885, 1893-1903, 1921, 1937-1941
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            79      13  83.54%   100-126, 170, 183
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33      22  33.33%   35-37, 44-70
rest/config.py                                  25       1  96.00%   35
rest/constants.py                                6       0  100.00%
rest/core.py                                   260     153  41.15%   157, 159, 161, 164-165, 179-189, 198-199, 205, 208, 249-303, 316-352, 384-422, 437-453, 469-478, 485-524, 541, 583-668, 707, 714-767
rest/errors.py                                  72      51  29.17%   63, 68-111, 116-119, 126-129, 137-167, 175-180, 195-201
rest/server.py                                 194     194  0.00%    18-596
rest/stac.py                                   333      77  76.88%   246, 312, 334, 386-389, 416-443, 474-476, 499, 531-532, 614-654, 676-692, 719, 784-788, 795, 850-851, 857, 861-866, 876-880, 929, 967, 1000, 1019-1021
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180      20  88.89%   225-229, 262-264, 282, 285, 291, 295, 353, 371-386
rest/types/queryables.py                        64      20  68.75%   54-55, 60-63, 68-79, 105-110, 119-120, 174
rest/types/stac_search.py                      125      11  91.20%   126-128, 174, 189-191, 199, 203, 251, 254
rest/utils/__init__.py                          94      30  68.09%   72-78, 98, 101-102, 121-123, 136, 143, 168-176, 183-204
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           23       5  78.26%   43-44, 48, 60, 62
types/__init__.py                              161      41  74.53%   67, 80-84, 95-107, 135-137, 144-149, 214, 252, 262-278, 283, 285, 307, 312, 320, 330
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                            111       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/whoosh.py                                 81      16  80.25%   129-132, 136-143, 155-161, 174-176
utils/__init__.py                              551      40  92.74%   81, 208-209, 218-245, 248, 263, 343-347, 420-424, 443-445, 459, 551, 566, 606-607, 636, 1011-1014, 1022-1023, 1061-1062, 1109-1110, 1250
utils/cache.py                                  22       0  100.00%
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             46       0  100.00%
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       1  96.43%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       8  78.95%   51, 53, 57, 98, 122-129
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36       1  97.22%   55
utils/s3.py                                     69       3  95.65%   162-163, 226
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
TOTAL                                        10647    2072  80.54%
```

</details>


### Diff against develop

```
Filename                    Stmts    Miss  Cover
------------------------  -------  ------  -------
plugins/download/base.py       +4      -7  +2.96%
TOTAL                          +4      -7  +0.07%
```


Results for commit: dd27884bcd2abe02b265d3dc38817fcb9d500bc4


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


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

Solution patch:
diff --git a/eodag/plugins/download/base.py b/eodag/plugins/download/base.py
index b286a77d..80216f82 100644
--- a/eodag/plugins/download/base.py
+++ b/eodag/plugins/download/base.py
@@ -25,6 +25,7 @@ import tarfile
 import tempfile
 import zipfile
 from datetime import datetime, timedelta
+from pathlib import Path
 from time import sleep
 from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
 
@@ -232,12 +233,23 @@ class Download(PluginTopic):
         )
         if os.path.isfile(old_record_filename):
             os.rename(old_record_filename, record_filename)
-        if os.path.isfile(record_filename) and os.path.isfile(fs_path):
+
+        # path with or without extension
+        path_obj = Path(fs_path)
+        matched_paths = list(path_obj.parent.glob(f"{path_obj.stem}.*"))
+        fs_path_with_ext = matched_paths[0] if matched_paths else fs_path
+        if (
+            os.path.isfile(record_filename)
+            and fs_path_with_ext
+            and os.path.isfile(fs_path_with_ext)
+        ):
             logger.info(
-                f"Product already downloaded: {fs_path}",
+                f"Product already downloaded: {fs_path_with_ext}",
             )
             return (
-                self._finalize(fs_path, progress_callback=progress_callback, **kwargs),
+                self._finalize(
+                    str(fs_path_with_ext), progress_callback=progress_callback, **kwargs
+                ),
                 None,
             )
         elif os.path.isfile(record_filename) and os.path.isdir(fs_dir_path):


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index d7488dbd..29cc5b01 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -306,7 +306,7 @@ class EODagTestCase(unittest.TestCase):
         self._set_download_simulation()
         self.tmp_download_dir = tempfile.TemporaryDirectory()
         if output_dir is None:
-            output_dir = str(Path(self.tmp_download_dir.name).parent)
+            output_dir = str(Path(self.tmp_download_dir.name))
         dl_config = config.PluginConfig.from_mapping(
             {
                 "type": "HTTPDownload",
diff --git a/tests/units/test_download_plugins.py b/tests/units/test_download_plugins.py
index ccdb597b..0fd0204a 100644
--- a/tests/units/test_download_plugins.py
+++ b/tests/units/test_download_plugins.py
@@ -149,7 +149,8 @@ class TestDownloadPluginBase(BaseDownloadPluginTest):
 
             # < v3.0.0b1 formatted record file and product path exist, record file moved to new format
             product_path_dir.mkdir()
-            (product_path_dir / "foo").touch()
+            file_in_dir = product_path_dir / "foo"
+            file_in_dir.touch()
             old_recordfile_path.touch()
             with self.assertLogs(level="INFO") as cm:
                 plugin._prepare_download(self.product, **download_kwargs)
@@ -163,6 +164,24 @@ class TestDownloadPluginBase(BaseDownloadPluginTest):
                 self.assertTrue(os.path.isfile(recordfile_path))
                 self.assertIn("Product already downloaded", str(cm.output))
 
+            # already downloaded product without extension
+            file_in_dir.unlink()
+            product_path_dir.rmdir()
+            product_path_dir.touch()
+            with self.assertLogs(level="INFO") as cm:
+                plugin._prepare_download(self.product, **download_kwargs)
+                self.assertIn("Product already downloaded", str(cm.output))
+                self.assertIn(
+                    "Remove existing partially downloaded file", str(cm.output)
+                )
+
+            # already downloaded product with extension
+            product_path_with_ext = product_path_dir.with_suffix(".xyz")
+            product_path_with_ext.touch()
+            with self.assertLogs(level="INFO") as cm:
+                plugin._prepare_download(self.product, **download_kwargs)
+                self.assertIn("Product already downloaded", str(cm.output))
+
     def test_plugins_download_base_prepare_download_no_url(self):
         """Download._prepare_download must return None when no download url"""