|
15 | 15 | from stac_fastapi.core.datetime_utils import datetime_to_str, now_to_rfc3339_str |
16 | 16 | from stac_fastapi.types.core import LandingPageMixin |
17 | 17 |
|
18 | | -from ..conftest import create_item, refresh_indices |
| 18 | +from ..conftest import create_collection, create_item, refresh_indices |
19 | 19 |
|
20 | 20 | if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch": |
21 | 21 | from stac_fastapi.opensearch.database_logic import DatabaseLogic |
@@ -398,8 +398,8 @@ async def test_item_search_temporal_intersecting_window_post(app_client, ctx): |
398 | 398 | test_item = ctx.item |
399 | 399 |
|
400 | 400 | item_date = rfc3339_str_to_datetime(test_item["properties"]["datetime"]) |
401 | | - item_date_before = item_date - timedelta(days=10) |
402 | | - item_date_after = item_date - timedelta(days=2) |
| 401 | + item_date_before = item_date - timedelta(days=2) # Changed from 10 to 2 |
| 402 | + item_date_after = item_date + timedelta(days=2) # Changed from -2 to +2 |
403 | 403 |
|
404 | 404 | params = { |
405 | 405 | "collections": [test_item["collection"]], |
@@ -973,3 +973,116 @@ async def test_search_datetime_validation_errors(app_client): |
973 | 973 | # assert link["wms:transparent"] |
974 | 974 | # return True |
975 | 975 | # assert False, resp_json |
| 976 | + |
| 977 | + |
| 978 | +@pytest.mark.asyncio |
| 979 | +async def test_search_datetime_with_null_datetime( |
| 980 | + app_client, txn_client, load_test_data |
| 981 | +): |
| 982 | + """Test datetime filtering when properties.datetime is null""" |
| 983 | + # First, create the test collection |
| 984 | + test_collection = load_test_data("test_collection.json") |
| 985 | + await create_collection(txn_client, collection=test_collection) |
| 986 | + |
| 987 | + # Create test items with different datetime scenarios |
| 988 | + base_item = load_test_data("test_item.json") |
| 989 | + |
| 990 | + # Item with null datetime but valid start/end datetimes |
| 991 | + null_dt_item = deepcopy(base_item) |
| 992 | + null_dt_item["id"] = "null-datetime-item" |
| 993 | + null_dt_item["properties"]["datetime"] = None |
| 994 | + null_dt_item["properties"]["start_datetime"] = "2020-01-01T00:00:00Z" |
| 995 | + null_dt_item["properties"]["end_datetime"] = "2020-01-02T00:00:00Z" |
| 996 | + await create_item(txn_client, null_dt_item) |
| 997 | + |
| 998 | + # Item with valid datetime |
| 999 | + valid_dt_item = deepcopy(base_item) |
| 1000 | + valid_dt_item["id"] = "valid-datetime-item" |
| 1001 | + valid_dt_item["properties"]["datetime"] = "2020-01-01T12:00:00Z" |
| 1002 | + await create_item(txn_client, valid_dt_item) |
| 1003 | + |
| 1004 | + await refresh_indices(txn_client) |
| 1005 | + |
| 1006 | + # Test 1: Search for exact datetime that should match both items |
| 1007 | + # since the time falls within the null-datetime-item's range |
| 1008 | + resp = await app_client.get( |
| 1009 | + "/search", |
| 1010 | + params={ |
| 1011 | + "datetime": "2020-01-01T12:00:00Z", |
| 1012 | + "collections": [base_item["collection"]], |
| 1013 | + }, |
| 1014 | + ) |
| 1015 | + assert resp.status_code == 200 |
| 1016 | + feature_ids = {f["id"] for f in resp.json()["features"]} |
| 1017 | + assert "valid-datetime-item" in feature_ids |
| 1018 | + assert "null-datetime-item" in feature_ids |
| 1019 | + |
| 1020 | + # Test 2: Search for a range that includes both items |
| 1021 | + resp = await app_client.get( |
| 1022 | + "/search", |
| 1023 | + params={ |
| 1024 | + "datetime": "2020-01-01T00:00:00Z/2020-01-03T00:00:00Z", |
| 1025 | + "collections": [base_item["collection"]], |
| 1026 | + }, |
| 1027 | + ) |
| 1028 | + assert resp.status_code == 200 |
| 1029 | + feature_ids = {f["id"] for f in resp.json()["features"]} |
| 1030 | + assert "valid-datetime-item" in feature_ids |
| 1031 | + assert "null-datetime-item" in feature_ids |
| 1032 | + |
| 1033 | + # Test 3: Search with POST request to test JSON body |
| 1034 | + resp = await app_client.post( |
| 1035 | + "/search", |
| 1036 | + json={ |
| 1037 | + "datetime": "2020-01-01T00:00:00Z/2020-01-02T00:00:00Z", |
| 1038 | + "collections": [base_item["collection"]], |
| 1039 | + }, |
| 1040 | + ) |
| 1041 | + assert resp.status_code == 200 |
| 1042 | + feature_ids = {f["id"] for f in resp.json()["features"]} |
| 1043 | + assert "null-datetime-item" in feature_ids |
| 1044 | + |
| 1045 | + # Add this after creating the other test items |
| 1046 | + # Item with datetime outside its start/end range |
| 1047 | + range_item = deepcopy(base_item) |
| 1048 | + range_item["id"] = "range-item" |
| 1049 | + range_item["properties"]["datetime"] = "2020-01-03T00:00:00Z" # Outside the range |
| 1050 | + range_item["properties"]["start_datetime"] = "2020-01-01T00:00:00Z" |
| 1051 | + range_item["properties"]["end_datetime"] = "2020-01-02T00:00:00Z" |
| 1052 | + await create_item(txn_client, range_item) |
| 1053 | + |
| 1054 | + # Refresh indices after creating all items |
| 1055 | + await refresh_indices(txn_client) |
| 1056 | + |
| 1057 | + # Add these test cases after the existing ones |
| 1058 | + # Test 4: Search for exact datetime that matches range_item's datetime (should match) |
| 1059 | + resp = await app_client.get( |
| 1060 | + "/search", |
| 1061 | + params={ |
| 1062 | + "datetime": "2020-01-03T00:00:00Z", # Matches range_item's datetime |
| 1063 | + "collections": [base_item["collection"]], |
| 1064 | + }, |
| 1065 | + ) |
| 1066 | + assert resp.status_code == 200 |
| 1067 | + feature_ids = {f["id"] for f in resp.json()["features"]} |
| 1068 | + assert "range-item" in feature_ids # Should match on exact datetime |
| 1069 | + assert "null-datetime-item" not in feature_ids # Should not match |
| 1070 | + assert "valid-datetime-item" not in feature_ids # Should not match |
| 1071 | + |
| 1072 | + # Test 5: Search for range that includes range_item's range but not its datetime |
| 1073 | + resp = await app_client.get( |
| 1074 | + "/search", |
| 1075 | + params={ |
| 1076 | + "datetime": "2020-01-01T12:00:00Z/2020-01-02T12:00:00Z", # Within range_item's range |
| 1077 | + "collections": [base_item["collection"]], |
| 1078 | + }, |
| 1079 | + ) |
| 1080 | + assert resp.status_code == 200 |
| 1081 | + feature_ids = {f["id"] for f in resp.json()["features"]} |
| 1082 | + assert ( |
| 1083 | + "range-item" not in feature_ids |
| 1084 | + ) # Should not match as datetime is outside this range |
| 1085 | + assert "null-datetime-item" in feature_ids # Should match if in range |
| 1086 | + |
| 1087 | + # Clean up |
| 1088 | + await txn_client.delete_collection(test_collection["id"]) |
0 commit comments