Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions stac_fastapi/eodag/config.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests are failing

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failure is not caused by this PR cause tests/test_download.py::test_download_item_from_collection_stream fails on the branch main. See for example this run. An issue has already been opened: #57

Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class Settings(ApiSettings):
validate_default=False,
)

validate_request: bool = Field(
default=True,
description="Validate search and product order requests",
alias="validate",
)


@lru_cache(maxsize=1)
def get_settings() -> Settings:
Expand Down
10 changes: 7 additions & 3 deletions stac_fastapi/eodag/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ def _search_base(self, search_request: BaseSearchPostRequest, request: Request)

request.state.eodag_args = eodag_args

# validate request
settings = get_settings()
validate: bool = settings.validate_request

# check if the collection exists
if product_type := eodag_args.get("productType"):
all_pt = request.app.state.dag.list_product_types(fetch_providers=False)
Expand All @@ -175,11 +179,11 @@ def _search_base(self, search_request: BaseSearchPostRequest, request: Request)
search_result = SearchResult([])
for item_id in ids:
eodag_args["id"] = item_id
search_result.extend(request.app.state.dag.search(**eodag_args))
search_result.extend(request.app.state.dag.search(validate=validate, **eodag_args))
search_result.number_matched = len(search_result)
else:
# search without ids
search_result = request.app.state.dag.search(**eodag_args)
search_result = request.app.state.dag.search(validate=validate, **eodag_args)

if search_result.errors and not len(search_result):
raise ResponseSearchError(search_result.errors, self.stac_metadata_model)
Expand Down Expand Up @@ -552,7 +556,7 @@ def prepare_search_base_args(search_request: BaseSearchPostRequest, model: type[

:param search_request: the search request
:param model: the model used to validate stac metadata
:returns: a dictionnary containing arguments for the eodag search
:returns: a dictionary containing arguments for the eodag search
"""
base_args = (
{
Expand Down
15 changes: 13 additions & 2 deletions stac_fastapi/eodag/extensions/collection_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
from stac_fastapi.types.search import APIRequest
from stac_fastapi.types.stac import Item

from stac_fastapi.eodag.config import get_settings
from stac_fastapi.eodag.errors import ResponseSearchError
from stac_fastapi.eodag.models.stac_metadata import (
CommonStacMetadata,
create_stac_item,
Expand Down Expand Up @@ -86,11 +88,20 @@ def order_collection(
federation_backend = request_body.federation_backends[0] if request_body.federation_backends else None

request_params = request_body.model_dump(exclude={"federation_backends": True})
search_results = dag.search(productType=collection_id, provider=federation_backend, **request_params)

settings = get_settings()
validate: bool = settings.validate_request
search_results = dag.search(
productType=collection_id,
provider=federation_backend,
validate=validate,
**request_params,
)

if len(search_results) > 0:
product = cast(EOProduct, search_results[0])

elif search_results.errors:
raise ResponseSearchError(search_results.errors, self.stac_metadata_model)
else:
raise NotFoundError(
f"Could not find any item in {collection_id} collection for backend {federation_backend}.",
Expand Down
128 changes: 128 additions & 0 deletions tests/test_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
from eodag.config import load_default_config
from eodag.plugins.download.base import Download
from eodag.plugins.manager import PluginManager
from eodag.utils.exceptions import ValidationError

from stac_fastapi.eodag.config import get_settings


@pytest.mark.parametrize("post_data", [{"foo": "bar"}, {}])
Expand Down Expand Up @@ -96,6 +99,7 @@ async def run():
expected_search_kwargs=dict(
productType=collection_id,
provider=None,
validate=True,
**post_data,
),
)
Expand Down Expand Up @@ -186,6 +190,7 @@ async def run():
expected_search_kwargs=dict(
productType=collection_id,
provider=None,
validate=True,
**post_data,
),
)
Expand Down Expand Up @@ -334,3 +339,126 @@ async def test_order_not_order_id_ko(request_not_found, mock_search, mock_order)
post_data={},
error_message="Download order failed.",
)


@pytest.mark.parametrize("validate", [True, False])
async def test_order_validate(request_valid, settings_cache_clear, validate):
"""Product order through eodag server must be validated according to settings"""
get_settings().validate_request = validate
post_data = {"foo": "bar"}
federation_backend = "cop_ads"
collection_id = "CAMS_EAC4"
expected_search_kwargs = dict(
productType=collection_id,
provider=None,
validate=validate,
**post_data,
)
url = f"collections/{collection_id}/order"
product = EOProduct(
federation_backend,
dict(
geometry="POINT (0 0)",
title="dummy_product",
id="dummy_id",
),
)
product.product_type = collection_id

product_dataset = "cams-global-reanalysis-eac4"
endpoint = "https://ads.atmosphere.copernicus.eu/api/retrieve/v1"
product.properties["orderLink"] = f"{endpoint}/processes/{product_dataset}/execution" + '?{"qux": "quux"}'

# order an offline product
product.properties["storageStatus"] = OFFLINE_STATUS

# add auth and download plugins to make the order works
plugins_manager = PluginManager(load_default_config())
download_plugin = plugins_manager.get_download_plugin(product)
auth_plugin = plugins_manager.get_auth_plugin(download_plugin, product)
auth_plugin.config.credentials = {"apikey": "anicekey"}
product.register_downloader(download_plugin, auth_plugin)

product_id = product.properties["id"]

@responses.activate(registry=responses.registries.OrderedRegistry)
async def run():
responses.add(
responses.POST,
f"{endpoint}/processes/{product_dataset}/execution",
status=200,
content_type="application/json",
body=f'{{"status": "accepted", "jobID": "{product_id}"}}'.encode("utf-8"),
auto_calculate_content_length=True,
)
responses.add(
responses.GET,
f"{endpoint}/jobs/{product_id}",
status=200,
content_type="application/json",
body=f'{{"status": "successful", "jobID": "{product_id}"}}'.encode("utf-8"),
auto_calculate_content_length=True,
)
responses.add(
responses.GET,
f"{endpoint}/jobs/{product_id}/results",
status=200,
content_type="application/json",
body=(f'{{"asset": {{"value": {{"href": "http://somewhere/download/{product_id}"}} }} }}'.encode("utf-8")),
auto_calculate_content_length=True,
)

await request_valid(
url=url,
method="POST",
post_data=post_data,
search_result=SearchResult([product]),
expected_search_kwargs=expected_search_kwargs,
)

await run()


async def test_order_validate_with_errors(app, app_client, mocker, settings_cache_clear):
"""Order a product through eodag server with invalid parameters must return informative error message"""
get_settings().validate_request = True
collection_id = "AG_ERA5"
errors = [
("wekeo_ecmwf", ValidationError("2 error(s). ecmwf:version: Field required; ecmwf:variable: Field required")),
("cop_cds", ValidationError("2 error(s). ecmwf:version: Field required; ecmwf:variable: Field required")),
]
expected_response = {
"code": "400",
"description": "Something went wrong",
"errors": [
{
"provider": "wekeo_ecmwf",
"error": "ValidationError",
"status_code": 400,
"message": "2 error(s). ecmwf:version: Field required; ecmwf:variable: Field required",
},
{
"provider": "cop_cds",
"error": "ValidationError",
"status_code": 400,
"message": "2 error(s). ecmwf:version: Field required; ecmwf:variable: Field required",
},
],
}

mock_search = mocker.patch.object(app.state.dag, "search")
mock_search.return_value = SearchResult([], 0, errors)

response = await app_client.request(
"POST",
f"/collections/{collection_id}/order",
json=None,
follow_redirects=True,
headers={},
)
response_content = response.json()

assert response.status_code == 400
assert "ticket" in response_content
response_content.pop("ticket", None)
assert expected_response == response_content
Loading
Loading