Skip to content

Commit

Permalink
Merge pull request #157 from stac-utils/fix-get-sort
Browse files Browse the repository at this point in the history
Fix GET sortby, tests
  • Loading branch information
jonhealy1 committed Nov 7, 2023
2 parents 8b12370 + b5ec2b0 commit ab7d930
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Corrected the automatic converstion of float values to int when building Filter Clauses [#135](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/135)
- Do not index `proj:geometry` field as geo_shape [#154](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/154)
- Remove unsupported characters from Elasticsearch index names [#153](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/153)
- Fixed GET /search sortby requests [#25](https://github.com/stac-utils/stac-fastapi-elasticsearch/issues/25)

## [v0.3.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,10 @@ async def get_search(
sort_param.append(
{
"field": sort[1:],
"direction": "asc" if sort[0] == "+" else "desc",
"direction": "desc" if sort[0] == "-" else "asc",
}
)
print(sort_param)
base_args["sortby"] = sort_param

if filter:
Expand Down
72 changes: 71 additions & 1 deletion stac_fastapi/elasticsearch/tests/api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,77 @@ async def test_app_query_extension_limit_10000(app_client):


@pytest.mark.asyncio
async def test_app_sort_extension(app_client, txn_client, ctx):
async def test_app_sort_extension_get_asc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%SZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
await create_item(txn_client, second_item)

resp = await app_client.get("/search?sortby=+properties.datetime")
assert resp.status_code == 200
resp_json = resp.json()
assert resp_json["features"][1]["id"] == first_item["id"]
assert resp_json["features"][0]["id"] == second_item["id"]


@pytest.mark.asyncio
async def test_app_sort_extension_get_desc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%SZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
await create_item(txn_client, second_item)

resp = await app_client.get("/search?sortby=-properties.datetime")
assert resp.status_code == 200
resp_json = resp.json()
assert resp_json["features"][0]["id"] == first_item["id"]
assert resp_json["features"][1]["id"] == second_item["id"]


@pytest.mark.asyncio
async def test_app_sort_extension_post_asc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%SZ"
)

second_item = dict(first_item)
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = another_item_date.strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
await create_item(txn_client, second_item)

params = {
"collections": [first_item["collection"]],
"sortby": [{"field": "properties.datetime", "direction": "asc"}],
}
resp = await app_client.post("/search", json=params)
assert resp.status_code == 200
resp_json = resp.json()
assert resp_json["features"][1]["id"] == first_item["id"]
assert resp_json["features"][0]["id"] == second_item["id"]


@pytest.mark.asyncio
async def test_app_sort_extension_post_desc(app_client, txn_client, ctx):
first_item = ctx.item
item_date = datetime.strptime(
first_item["properties"]["datetime"], "%Y-%m-%dT%H:%M:%SZ"
Expand Down
4 changes: 3 additions & 1 deletion stac_fastapi/elasticsearch/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ class Config:

@pytest.fixture(scope="session")
def event_loop():
return asyncio.get_event_loop()
loop = asyncio.new_event_loop()
yield loop
loop.close()


def _load_file(filename: str) -> Dict:
Expand Down
64 changes: 10 additions & 54 deletions stac_fastapi/elasticsearch/tests/resources/test_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ async def test_item_collection_filter_datetime(app_client, ctx):
assert len(resp_json["features"]) == 0


@pytest.mark.asyncio
@pytest.mark.skip(reason="Pagination extension not implemented")
async def test_pagination(app_client, load_test_data):
"""Test item collection pagination (paging extension)"""
Expand Down Expand Up @@ -384,6 +385,7 @@ async def test_item_search_temporal_window_post(app_client, ctx):
assert resp_json["features"][0]["id"] == test_item["id"]


@pytest.mark.asyncio
@pytest.mark.skip(reason="KeyError: 'features")
async def test_item_search_temporal_open_window(app_client, ctx):
"""Test POST search with open spatio-temporal query (core)"""
Expand All @@ -398,39 +400,6 @@ async def test_item_search_temporal_open_window(app_client, ctx):
assert resp_json["features"][0]["id"] == test_item["id"]


@pytest.mark.skip(reason="sortby date not implemented")
async def test_item_search_sort_post(app_client, load_test_data):
"""Test POST search with sorting (sort extension)"""
first_item = load_test_data("test_item.json")
item_date = rfc3339_str_to_datetime(first_item["properties"]["datetime"])
resp = await app_client.post(
f"/collections/{first_item['collection']}/items", json=first_item
)
assert resp.status_code == 200

second_item = load_test_data("test_item.json")
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item["properties"]["datetime"] = datetime_to_str(another_item_date)
resp = await app_client.post(
f"/collections/{second_item['collection']}/items", json=second_item
)
assert resp.status_code == 200

params = {
"collections": [first_item["collection"]],
"sortby": [{"field": "datetime", "direction": "desc"}],
}
resp = await app_client.post("/search", json=params)
assert resp.status_code == 200
resp_json = resp.json()
assert resp_json["features"][0]["id"] == first_item["id"]
assert resp_json["features"][1]["id"] == second_item["id"]
await app_client.delete(
f"/collections/{first_item['collection']}/items/{first_item['id']}"
)


@pytest.mark.asyncio
async def test_item_search_by_id_get(app_client, ctx, txn_client):
"""Test GET search by item id (core)"""
Expand Down Expand Up @@ -498,27 +467,6 @@ async def test_item_search_temporal_window_get(app_client, ctx):
assert resp_json["features"][0]["id"] == test_item["id"]


@pytest.mark.skip(reason="sorting not fully implemented")
async def test_item_search_sort_get(app_client, ctx, txn_client):
"""Test GET search with sorting (sort extension)"""
first_item = ctx.item
item_date = rfc3339_str_to_datetime(first_item["properties"]["datetime"])
await create_item(txn_client, ctx.item)

second_item = ctx.item.copy()
second_item["id"] = "another-item"
another_item_date = item_date - timedelta(days=1)
second_item.update({"properties": {"datetime": datetime_to_str(another_item_date)}})
await create_item(txn_client, second_item)

params = {"collections": [first_item["collection"]], "sortby": "-datetime"}
resp = await app_client.get("/search", params=params)
assert resp.status_code == 200
resp_json = resp.json()
assert resp_json["features"][0]["id"] == first_item["id"]
assert resp_json["features"][1]["id"] == second_item["id"]


@pytest.mark.asyncio
async def test_item_search_post_without_collection(app_client, ctx):
"""Test POST search without specifying a collection"""
Expand Down Expand Up @@ -742,6 +690,7 @@ async def test_field_extension_post(app_client, ctx):
}


@pytest.mark.asyncio
async def test_field_extension_exclude_and_include(app_client, ctx):
"""Test POST search including/excluding same field (fields extension)"""
test_item = ctx.item
Expand All @@ -758,6 +707,7 @@ async def test_field_extension_exclude_and_include(app_client, ctx):
assert "eo:cloud_cover" not in resp_json["features"][0]["properties"]


@pytest.mark.asyncio
async def test_field_extension_exclude_default_includes(app_client, ctx):
"""Test POST search excluding a forbidden field (fields extension)"""
test_item = ctx.item
Expand All @@ -768,6 +718,7 @@ async def test_field_extension_exclude_default_includes(app_client, ctx):
assert "gsd" not in resp_json["features"][0]


@pytest.mark.asyncio
async def test_search_intersects_and_bbox(app_client):
"""Test POST search intersects and bbox are mutually exclusive (core)"""
bbox = [-118, 34, -117, 35]
Expand All @@ -777,20 +728,23 @@ async def test_search_intersects_and_bbox(app_client):
assert resp.status_code == 400


@pytest.mark.asyncio
async def test_get_missing_item(app_client, load_test_data):
"""Test read item which does not exist (transactions extension)"""
test_coll = load_test_data("test_collection.json")
resp = await app_client.get(f"/collections/{test_coll['id']}/items/invalid-item")
assert resp.status_code == 404


@pytest.mark.asyncio
@pytest.mark.skip(reason="invalid queries not implemented")
async def test_search_invalid_query_field(app_client):
body = {"query": {"gsd": {"lt": 100}, "invalid-field": {"eq": 50}}}
resp = await app_client.post("/search", json=body)
assert resp.status_code == 400


@pytest.mark.asyncio
async def test_search_bbox_errors(app_client):
body = {"query": {"bbox": [0]}}
resp = await app_client.post("/search", json=body)
Expand All @@ -805,6 +759,7 @@ async def test_search_bbox_errors(app_client):
assert resp.status_code == 400


@pytest.mark.asyncio
async def test_conformance_classes_configurable():
"""Test conformance class configurability"""
landing = LandingPageMixin()
Expand All @@ -822,6 +777,7 @@ async def test_conformance_classes_configurable():
assert client.conformance_classes()[0] == "this is a test"


@pytest.mark.asyncio
async def test_search_datetime_validation_errors(app_client):
bad_datetimes = [
"37-01-01T12:00:27.87Z",
Expand Down

0 comments on commit ab7d930

Please sign in to comment.