From f21e30281039b0c91e3ecbd12584d5ec6eb5095d Mon Sep 17 00:00:00 2001 From: "Victor [C] Tsang" Date: Thu, 21 May 2026 23:30:20 +0000 Subject: [PATCH 1/2] Added geospatial tests for $maxDistance Signed-off-by: Victor [C] Tsang --- .../operator/query/geospatial/conftest.py | 13 + .../query/geospatial/specifiers/__init__.py | 0 .../specifiers/maxDistance/__init__.py | 0 .../test_maxDistance_bson_validator.py | 116 +++ .../maxDistance/test_maxDistance_errors.py | 158 ++++ .../maxDistance/test_maxDistance_geojson.py | 699 ++++++++++++++++++ .../maxDistance/test_maxDistance_legacy.py | 190 +++++ documentdb_tests/framework/error_codes.py | 2 + .../framework/test_structure_validator.py | 2 + 9 files changed, 1180 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/conftest.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/__init__.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/__init__.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_bson_validator.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_errors.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_legacy.py diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/conftest.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/conftest.py new file mode 100644 index 00000000..b61c80d9 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/conftest.py @@ -0,0 +1,13 @@ +import pytest + + +@pytest.fixture +def geo_2dsphere(collection): + """Create a 2dsphere index on loc.""" + collection.create_index([("loc", "2dsphere")]) + + +@pytest.fixture +def geo_2d(collection): + """Create a 2d index on loc.""" + collection.create_index([("loc", "2d")]) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/__init__.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/__init__.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_bson_validator.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_bson_validator.py new file mode 100644 index 00000000..39be343b --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_bson_validator.py @@ -0,0 +1,116 @@ +"""Tests for $maxDistance BSON type validation — GeoJSON and legacy syntax. + +The legacy code path returns a different error code (16895) than GeoJSON (BAD_VALUE_ERROR = 2). +""" + +import pytest + +from documentdb_tests.framework.assertions import assertFailureCode, assertSuccess +from documentdb_tests.framework.bson_type_validator import ( + BsonType, + BsonTypeTestCase, + generate_bson_acceptance_test_cases, + generate_bson_rejection_test_cases, +) +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + LEGACY_MAX_DISTANCE_NOT_NUMBER_ERROR, +) +from documentdb_tests.framework.executor import execute_command + +ORIGIN = {"type": "Point", "coordinates": [0, 0]} + +# --- GeoJSON (2dsphere index) --- + +GEOJSON_BSON_PARAMS = [ + BsonTypeTestCase( + id="maxDistance", + msg="$maxDistance should reject non-numeric types", + keyword="$maxDistance", + valid_types=[BsonType.DOUBLE, BsonType.INT, BsonType.LONG, BsonType.DECIMAL], + default_error_code=BAD_VALUE_ERROR, + ), +] + +GEOJSON_REJECTION = generate_bson_rejection_test_cases(GEOJSON_BSON_PARAMS) +GEOJSON_ACCEPTANCE = generate_bson_acceptance_test_cases(GEOJSON_BSON_PARAMS) + + +@pytest.mark.usefixtures("geo_2dsphere") +@pytest.mark.parametrize("operator", ["$near", "$nearSphere"]) +@pytest.mark.parametrize("bson_type,sample_value,spec", GEOJSON_REJECTION) +def test_maxDistance_geojson_bson_rejected(collection, operator, bson_type, sample_value, spec): + """Verifies $maxDistance rejects invalid BSON types (GeoJSON).""" + filt = { + "loc": { + operator: { + "$geometry": ORIGIN, + spec.keyword: sample_value, + } + } + } + result = execute_command(collection, {"find": collection.name, "filter": filt}) + assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) + + +@pytest.mark.usefixtures("geo_2dsphere") +@pytest.mark.parametrize("operator", ["$near", "$nearSphere"]) +@pytest.mark.parametrize("bson_type,sample_value,spec", GEOJSON_ACCEPTANCE) +def test_maxDistance_geojson_bson_accepted(collection, operator, bson_type, sample_value, spec): + """Verifies $maxDistance accepts valid BSON types (GeoJSON).""" + collection.insert_one({"_id": 1, "loc": ORIGIN}) + filt = { + "loc": { + operator: { + "$geometry": ORIGIN, + spec.keyword: sample_value, + } + } + } + result = execute_command(collection, {"find": collection.name, "filter": filt}) + assertSuccess( + result, + [{"_id": 1, "loc": ORIGIN}], + msg=f"{spec.keyword} should accept {bson_type.value}", + ) + + +# --- Legacy (2d index) --- + +LEGACY_BSON_PARAMS = [ + BsonTypeTestCase( + id="maxDistance_legacy", + msg="$maxDistance should reject non-numeric types (legacy)", + keyword="$maxDistance", + valid_types=[BsonType.DOUBLE, BsonType.INT, BsonType.LONG, BsonType.DECIMAL], + default_error_code=LEGACY_MAX_DISTANCE_NOT_NUMBER_ERROR, + ), +] + +LEGACY_REJECTION = generate_bson_rejection_test_cases(LEGACY_BSON_PARAMS) +LEGACY_ACCEPTANCE = generate_bson_acceptance_test_cases(LEGACY_BSON_PARAMS) + + +@pytest.mark.usefixtures("geo_2d") +@pytest.mark.parametrize("operator", ["$near", "$nearSphere"]) +@pytest.mark.parametrize("bson_type,sample_value,spec", LEGACY_REJECTION) +def test_maxDistance_legacy_bson_rejected(collection, operator, bson_type, sample_value, spec): + """Verifies $maxDistance rejects invalid BSON types (legacy).""" + filt = {"loc": {operator: [0, 0], spec.keyword: sample_value}} + result = execute_command(collection, {"find": collection.name, "filter": filt}) + assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) + + +@pytest.mark.usefixtures("geo_2d") +@pytest.mark.parametrize("operator", ["$near", "$nearSphere"]) +@pytest.mark.parametrize("bson_type,sample_value,spec", LEGACY_ACCEPTANCE) +def test_maxDistance_legacy_bson_accepted(collection, operator, bson_type, sample_value, spec): + """Verifies $maxDistance accepts valid BSON types (legacy).""" + collection.insert_one({"_id": 1, "loc": [0, 0]}) + filt = {"loc": {operator: [0, 0], spec.keyword: sample_value}} + result = execute_command(collection, {"find": collection.name, "filter": filt}) + assertSuccess( + result, + [{"_id": 1, "loc": [0, 0]}], + msg=f"{spec.keyword} should accept {bson_type.value} (legacy)", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_errors.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_errors.py new file mode 100644 index 00000000..cf9f4998 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_errors.py @@ -0,0 +1,158 @@ +"""Tests for $maxDistance error cases — invalid context, values, types, and missing index.""" + +import pytest +from bson import Decimal128, Int64 + +from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import ( + QueryTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import BAD_VALUE_ERROR, NO_QUERY_EXECUTION_PLANS_ERROR +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DECIMAL128_INFINITY, + DECIMAL128_MAX, + DECIMAL128_NAN, + DECIMAL128_NEGATIVE_INFINITY, + FLOAT_INFINITY, + FLOAT_NAN, + FLOAT_NEGATIVE_INFINITY, +) + +ORIGIN = {"type": "Point", "coordinates": [0, 0]} + + +ERROR_TESTS: list[QueryTestCase] = [ + # Invalid context + QueryTestCase( + id="without_near_context", + filter={"loc": {"$maxDistance": 1000}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject $maxDistance without $near or $nearSphere context", + ), + QueryTestCase( + id="with_geoWithin", + filter={ + "loc": { + "$geoWithin": { + "$geometry": { + "type": "Polygon", + "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], + } + }, + "$maxDistance": 1000, + } + }, + error_code=BAD_VALUE_ERROR, + msg="Should reject $maxDistance with $geoWithin", + ), + QueryTestCase( + id="nearSphere_without_geometry", + filter={"loc": {"$nearSphere": {"$maxDistance": 5000, "$minDistance": 0}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject $nearSphere with $maxDistance but no $geometry", + ), + # Invalid values + QueryTestCase( + id="negative", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": -1}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject negative $maxDistance", + ), + QueryTestCase( + id="negative_double", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": -0.5}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject negative double $maxDistance", + ), + QueryTestCase( + id="negative_int64", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": Int64(-1)}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject negative Int64 $maxDistance", + ), + QueryTestCase( + id="negative_decimal128", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": Decimal128("-1")}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject negative Decimal128 $maxDistance", + ), + QueryTestCase( + id="nan", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": FLOAT_NAN}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject NaN $maxDistance", + ), + QueryTestCase( + id="infinity", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": FLOAT_INFINITY}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject Infinity $maxDistance", + ), + QueryTestCase( + id="negative_infinity", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": FLOAT_NEGATIVE_INFINITY}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject -Infinity $maxDistance", + ), + QueryTestCase( + id="decimal128_max", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DECIMAL128_MAX}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject $maxDistance=DECIMAL128_MAX as non-negative overflow", + ), + QueryTestCase( + id="decimal128_nan", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DECIMAL128_NAN}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject Decimal128 NaN $maxDistance", + ), + QueryTestCase( + id="decimal128_infinity", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DECIMAL128_INFINITY}}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject Decimal128 Infinity $maxDistance", + ), + QueryTestCase( + id="decimal128_negative_infinity", + filter={ + "loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DECIMAL128_NEGATIVE_INFINITY}} + }, + error_code=BAD_VALUE_ERROR, + msg="Should reject Decimal128 -Infinity $maxDistance", + ), +] + + +@pytest.mark.usefixtures("geo_2dsphere") +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_maxDistance_errors(collection, test): + """Verifies $maxDistance rejects invalid contexts, values, and types.""" + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertFailureCode(result, test.error_code, msg=test.msg) + + +# No geo_2dsphere fixture applied — these tests intentionally run without an index. +NO_INDEX_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="near_without_index", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000}}}, + error_code=NO_QUERY_EXECUTION_PLANS_ERROR, + msg="Should error when no geospatial index exists", + ), + QueryTestCase( + id="nearSphere_without_index", + filter={"loc": {"$nearSphere": {"$geometry": ORIGIN, "$maxDistance": 1000}}}, + error_code=NO_QUERY_EXECUTION_PLANS_ERROR, + msg="Should error when no geospatial index for $nearSphere", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NO_INDEX_TESTS)) +def test_maxDistance_no_index_errors(collection, test): + """Verifies $maxDistance fails without geospatial index.""" + collection.insert_one({"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}) + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py new file mode 100644 index 00000000..b37303a4 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py @@ -0,0 +1,699 @@ +"""Tests for $maxDistance with GeoJSON geometry ($near and $nearSphere, 2dsphere index).""" + +import pytest +from bson import Decimal128, Int64 + +from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import ( + QueryTestCase, +) +from documentdb_tests.framework.assertions import assertSuccess +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DOUBLE_MAX, + DOUBLE_NEGATIVE_ZERO, + INT32_MAX, + INT64_MAX, +) + +ORIGIN = {"type": "Point", "coordinates": [0, 0]} + + +NEAR_GEOJSON_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="filters_within_distance", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 200000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [10, 10]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + ], + msg="Should return only documents within $maxDistance meters", + ), + QueryTestCase( + id="sorted_nearest_first", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 2000000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [10, 10]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [5, 5]}}, + ], + expected=[ + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [5, 5]}}, + {"_id": 1, "loc": {"type": "Point", "coordinates": [10, 10]}}, + ], + msg="Should return results sorted by distance (nearest first)", + ), + QueryTestCase( + id="boundary_inclusion_1_degree", + filter={ + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [0, 0]}, + "$maxDistance": 111320, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.5, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [2, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.5, 0]}}, + ], + msg="Should include docs within ~111km (1 degree) and exclude those beyond", + ), + QueryTestCase( + id="with_minDistance_annular", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 200000, + "$maxDistance": 500000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, # 0m — below min + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 0]}}, # ~111km — below min + {"_id": 3, "loc": {"type": "Point", "coordinates": [2, 0]}}, # ~222km — inside ring + {"_id": 4, "loc": {"type": "Point", "coordinates": [4, 0]}}, # ~445km — inside ring + {"_id": 5, "loc": {"type": "Point", "coordinates": [5, 0]}}, # ~556km — above max + ], + expected=[ + {"_id": 3, "loc": {"type": "Point", "coordinates": [2, 0]}}, + {"_id": 4, "loc": {"type": "Point", "coordinates": [4, 0]}}, + ], + msg="Should exclude docs below $minDistance and above $maxDistance", + ), + QueryTestCase( + id="maxDistance_2000m_filters", + filter={ + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [0, 0]}, + "$maxDistance": 2000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.01, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [1, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.01, 0]}}, + ], + msg="Should filter with $maxDistance 2000 meters", + ), + QueryTestCase( + id="half_earth_circumference_antipodal", + filter={ + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [180, 0]}, + "$maxDistance": 20100000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [180, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [180, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + msg="Should include antipodal point with $maxDistance > half earth circumference", + ), + QueryTestCase( + id="empty_result_when_none_within", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[], + msg="Should return empty when no documents within distance", + ), +] + + +VALID_TYPE_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="positive_int", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should accept positive integer $maxDistance", + ), + QueryTestCase( + id="positive_double", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000.5}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should accept positive double $maxDistance", + ), + QueryTestCase( + id="int64", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": Int64(5000)}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should accept Int64 $maxDistance", + ), + QueryTestCase( + id="decimal128", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": Decimal128("1000.5")}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should accept Decimal128 $maxDistance", + ), + QueryTestCase( + id="very_large_value", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 40075000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should accept very large $maxDistance (earth circumference)", + ), +] + + +BOUNDARY_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="very_small_1mm", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 0.001}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should handle very small $maxDistance (1mm)", + ), + QueryTestCase( + id="very_small_1e_neg10", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1e-10}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.0001, 0]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should handle extremely small $maxDistance (1e-10)", + ), + QueryTestCase( + id="half_earth_circumference", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$maxDistance": 20037508, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [90, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [90, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should include all docs with $maxDistance = half earth circumference", + ), + QueryTestCase( + id="larger_than_earth", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 50000000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [-90, 45]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [-90, 45]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should return all documents with $maxDistance larger than earth circumference", + ), + QueryTestCase( + id="int32_max_accepted_returns_all", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": INT32_MAX}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should accept INT32_MAX as $maxDistance", + ), + QueryTestCase( + id="int64_max_accepted_returns_all", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": INT64_MAX}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should accept INT64_MAX as $maxDistance", + ), + QueryTestCase( + id="double_max_accepted_returns_all", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DOUBLE_MAX}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [180, 0]}}, + ], + msg="Should accept DOUBLE_MAX as $maxDistance without overflow error", + ), + QueryTestCase( + id="antimeridian_crossing", + filter={ + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [179.5, 0]}, + "$maxDistance": 200000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [179.5, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [-179.5, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [179.5, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [-179.5, 0]}}, + ], + msg="Should find point across antimeridian (~1 degree apart, not 359)", + ), + QueryTestCase( + id="pole_query", + filter={ + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [0, 90]}, + "$maxDistance": 200000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 89]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [90, 89]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 80]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 89]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [90, 89]}}, + ], + msg="Should find points near pole regardless of longitude", + ), + QueryTestCase( + id="exact_center_with_zero_distance", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 0}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should return document at exact center with $maxDistance 0", + ), + QueryTestCase( + id="negative_zero", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": DOUBLE_NEGATIVE_ZERO}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + ], + expected=[{"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}], + msg="Should treat -0.0 same as 0 for $maxDistance", + ), + QueryTestCase( + id="empty_collection", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000000}}}, + doc=[], + expected=[], + msg="Should return empty array on empty collection (no error)", + ), +] + + +NULL_MISSING_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="null_location_excluded", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": None}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + msg="Should not match documents with null location field", + ), + QueryTestCase( + id="missing_location_excluded", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "other": "value"}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + msg="Should not match documents with missing location field", + ), + QueryTestCase( + id="all_null_or_missing", + filter={"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 1000000}}}, + doc=[ + {"_id": 1, "loc": None}, + {"_id": 2, "other": "value"}, + ], + expected=[], + msg="Should return empty when all docs have null/missing location", + ), +] + + +INTERACTION_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="min_less_than_max_normal", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 100000, + "$maxDistance": 500000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [2, 2]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [20, 20]}}, + ], + expected=[ + {"_id": 2, "loc": {"type": "Point", "coordinates": [2, 2]}}, + ], + msg="Should return docs in annular region when $minDistance < $maxDistance", + ), + QueryTestCase( + id="min_greater_than_max_empty", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 500000, + "$maxDistance": 100000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [2, 2]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [20, 20]}}, + ], + expected=[], + msg="Should return empty when $minDistance > $maxDistance", + ), + QueryTestCase( + id="min_equals_max", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 500000, + "$maxDistance": 500000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [3, 3]}}, + ], + expected=[], + msg="Should return empty (or exact distance match) when $minDistance = $maxDistance", + ), + QueryTestCase( + id="min_zero_same_as_max_alone", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 0, + "$maxDistance": 200000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [10, 10]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + ], + msg="Should behave same as $maxDistance alone when $minDistance is 0", + ), + QueryTestCase( + id="both_zero", + filter={ + "loc": { + "$near": { + "$geometry": ORIGIN, + "$minDistance": 0, + "$maxDistance": 0, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ], + msg="Should return only exact match when both $minDistance and $maxDistance are 0", + ), +] + + +NEARSPHERE_GEOJSON_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="geojson_filters_within_meters", + filter={"loc": {"$nearSphere": {"$geometry": ORIGIN, "$maxDistance": 200000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [10, 10]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 1]}}, + ], + msg="Should filter $nearSphere GeoJSON results within $maxDistance meters", + ), + QueryTestCase( + id="geojson_sorted_nearest_first", + filter={"loc": {"$nearSphere": {"$geometry": ORIGIN, "$maxDistance": 2000000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [10, 10]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [5, 5]}}, + ], + expected=[ + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [5, 5]}}, + {"_id": 1, "loc": {"type": "Point", "coordinates": [10, 10]}}, + ], + msg="Should return $nearSphere results sorted nearest first", + ), + QueryTestCase( + id="geojson_with_minDistance_annular", + filter={ + "loc": { + "$nearSphere": { + "$geometry": ORIGIN, + "$minDistance": 500000, + "$maxDistance": 1500000, + } + } + }, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [5, 5]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [50, 50]}}, + ], + expected=[ + {"_id": 2, "loc": {"type": "Point", "coordinates": [5, 5]}}, + ], + msg="Should return $nearSphere docs in annular region", + ), + QueryTestCase( + id="geojson_maxDistance_only", + filter={"loc": {"$nearSphere": {"$geometry": ORIGIN, "$maxDistance": 500000}}}, + doc=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [2, 2]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [20, 20]}}, + ], + expected=[ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [2, 2]}}, + ], + msg="Should limit $nearSphere to nearby documents only", + ), +] + + +ALL_NEAR_TESTS = ( + NEAR_GEOJSON_TESTS + VALID_TYPE_TESTS + BOUNDARY_TESTS + NULL_MISSING_TESTS + INTERACTION_TESTS +) + + +@pytest.mark.usefixtures("geo_2dsphere") +@pytest.mark.parametrize("test", pytest_params(ALL_NEAR_TESTS)) +def test_maxDistance_near_geojson(collection, test): + """Verifies $maxDistance with $near and GeoJSON geometry. + + Covers: NEAR_GEOJSON_TESTS, VALID_TYPE_TESTS, BOUNDARY_TESTS, + NULL_MISSING_TESTS, INTERACTION_TESTS. + """ + if test.doc: + collection.insert_many(test.doc) + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertSuccess(result, test.expected, msg=test.msg) + + +@pytest.mark.usefixtures("geo_2dsphere") +@pytest.mark.parametrize("test", pytest_params(NEARSPHERE_GEOJSON_TESTS)) +def test_maxDistance_nearSphere_geojson(collection, test): + """Verifies $maxDistance with $nearSphere and GeoJSON geometry.""" + collection.insert_many(test.doc) + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertSuccess(result, test.expected, msg=test.msg) + + +def test_maxDistance_sparse_index(collection): + """Verifies $maxDistance works with sparse 2dsphere index.""" + collection.create_index([("loc", "2dsphere")], sparse=True) + collection.insert_many( + [ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 2, "name": "no location field"}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [1, 0]}}, + ] + ) + result = execute_command( + collection, + { + "find": collection.name, + "filter": {"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 200000}}}, + }, + ) + assertSuccess( + result, + [ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [1, 0]}}, + ], + msg="Should work with sparse index, skipping docs without loc field", + ) + + +def test_maxDistance_3d_coordinates_altitude_ignored(collection): + """Verifies $maxDistance ignores altitude (3rd coordinate).""" + collection.create_index([("loc", "2dsphere")]) + collection.insert_many( + [ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0, 10000]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 0, 0]}}, + ] + ) + result = execute_command( + collection, + { + "find": collection.name, + "filter": { + "loc": { + "$near": { + "$geometry": {"type": "Point", "coordinates": [0, 0, 5000]}, + "$maxDistance": 200000, + } + } + }, + }, + ) + assertSuccess( + result, + [ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0, 0, 10000]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [1, 0, 0]}}, + ], + msg="Should ignore altitude — distance calculated on 2D surface only", + ) + + +def test_maxDistance_sort_stability_close_points(collection): + """Verifies sort order for points at similar distances.""" + collection.create_index([("loc", "2dsphere")]) + collection.insert_many( + [ + {"_id": 1, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.001]}}, + {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 0]}}, + ] + ) + result = execute_command( + collection, + { + "find": collection.name, + "filter": {"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 200}}}, + }, + ) + assertSuccess( + result, + [ + {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 0]}}, + {"_id": 1, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.001]}}, + ], + msg="Should return all points within 200m sorted by distance", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_legacy.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_legacy.py new file mode 100644 index 00000000..5eb74975 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_legacy.py @@ -0,0 +1,190 @@ +"""Tests for $maxDistance with legacy coordinate pairs ($near and $nearSphere, 2d index).""" + +import math + +import pytest + +from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import ( + QueryTestCase, +) +from documentdb_tests.framework.assertions import assertSuccess +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +NEAR_LEGACY_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="basic_legacy_maxDistance", + filter={"loc": {"$near": [0, 0], "$maxDistance": 5}}, + doc=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [1, 1]}, + {"_id": 3, "loc": [50, 50]}, + ], + expected=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [1, 1]}, + ], + msg="Should filter legacy coordinates with $maxDistance in flat units", + ), + QueryTestCase( + id="legacy_sorted_nearest_first", + filter={"loc": {"$near": [0, 0], "$maxDistance": 100}}, + doc=[ + {"_id": 1, "loc": [10, 10]}, + {"_id": 2, "loc": [0, 0]}, + {"_id": 3, "loc": [5, 5]}, + ], + expected=[ + {"_id": 2, "loc": [0, 0]}, + {"_id": 3, "loc": [5, 5]}, + {"_id": 1, "loc": [10, 10]}, + ], + msg="Should return legacy results sorted nearest first", + ), + QueryTestCase( + id="legacy_zero_maxDistance", + filter={"loc": {"$near": [0, 0], "$maxDistance": 0}}, + doc=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [1, 1]}, + ], + expected=[ + {"_id": 1, "loc": [0, 0]}, + ], + msg="Should return only exact match with $maxDistance 0 on legacy", + ), + QueryTestCase( + id="legacy_with_minDistance", + filter={"loc": {"$near": [0, 0], "$maxDistance": 50, "$minDistance": 5}}, + doc=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [10, 10]}, + {"_id": 3, "loc": [40, 40]}, + ], + expected=[ + {"_id": 2, "loc": [10, 10]}, + ], + msg="Should return docs in annular region with legacy coordinates", + ), + QueryTestCase( + id="legacy_empty_result", + filter={"loc": {"$near": [0, 0], "$maxDistance": 0.001}}, + doc=[ + {"_id": 1, "loc": [10, 10]}, + ], + expected=[], + msg="Should return empty when no legacy docs within distance", + ), +] + +NEARSPHERE_LEGACY_TESTS: list[QueryTestCase] = [ + QueryTestCase( + id="nearSphere_2d_with_maxDistance_radians", + filter={ + "loc": { + "$nearSphere": [0, 0], + "$maxDistance": math.radians(5), + } + }, + doc=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [2, 2]}, + {"_id": 3, "loc": [50, 50]}, + ], + expected=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [2, 2]}, + ], + msg="Should filter $nearSphere with $maxDistance in radians (2d index)", + ), + QueryTestCase( + id="legacy_coords_radians_with_minDistance", + filter={ + "loc": { + "$nearSphere": [0, 0], + "$minDistance": math.radians(2), + "$maxDistance": math.radians(20), + } + }, + doc=[ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [5, 5]}, + {"_id": 3, "loc": [50, 50]}, + ], + expected=[ + {"_id": 2, "loc": [5, 5]}, + ], + msg="Should return $nearSphere legacy docs in annular region (radians)", + ), +] + + +@pytest.mark.usefixtures("geo_2d") +@pytest.mark.parametrize("test", pytest_params(NEAR_LEGACY_TESTS)) +def test_maxDistance_near_legacy(collection, test): + """Verifies $maxDistance with $near and legacy coordinate pairs (2d index).""" + collection.insert_many(test.doc) + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertSuccess(result, test.expected, msg=test.msg) + + +@pytest.mark.usefixtures("geo_2d") +@pytest.mark.parametrize("test", pytest_params(NEARSPHERE_LEGACY_TESTS)) +def test_maxDistance_nearSphere_legacy(collection, test): + """Verifies $maxDistance with $nearSphere and legacy coordinates (radians, 2d index).""" + collection.insert_many(test.doc) + result = execute_command(collection, {"find": collection.name, "filter": test.filter}) + assertSuccess(result, test.expected, msg=test.msg) + + +def test_maxDistance_nearSphere_2d_annular(collection): + """Verifies $nearSphere + 2d index with both $minDistance and $maxDistance in radians.""" + collection.create_index([("loc", "2d")]) + collection.insert_many( + [ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [3, 0]}, + {"_id": 3, "loc": [50, 0]}, + ] + ) + result = execute_command( + collection, + { + "find": collection.name, + "filter": { + "loc": { + "$nearSphere": [0, 0], + "$minDistance": math.radians(1), + "$maxDistance": math.radians(10), + } + }, + }, + ) + assertSuccess( + result, + [{"_id": 2, "loc": [3, 0]}], + msg="Should return docs in annular region with $nearSphere + 2d (radians)", + ) + + +def test_maxDistance_zero_nearSphere_2d_radians(collection): + """Verifies $maxDistance: 0 with $nearSphere + 2d index returns only exact match.""" + collection.create_index([("loc", "2d")]) + collection.insert_many( + [ + {"_id": 1, "loc": [0, 0]}, + {"_id": 2, "loc": [1, 0]}, + ] + ) + result = execute_command( + collection, + { + "find": collection.name, + "filter": {"loc": {"$nearSphere": [0, 0], "$maxDistance": 0}}, + }, + ) + assertSuccess( + result, + [{"_id": 1, "loc": [0, 0]}], + msg="Should return only exact match with $maxDistance: 0 (radians)", + ) diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index adbc5c20..f138fa54 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -92,6 +92,8 @@ MAP_MISSING_INPUT_ERROR = 16880 MAP_MISSING_IN_ERROR = 16882 MAP_INPUT_NOT_ARRAY_ERROR = 16883 +LEGACY_MIN_DISTANCE_NOT_NUMBER_ERROR = 16893 +LEGACY_MAX_DISTANCE_NOT_NUMBER_ERROR = 16895 OUT_ARGUMENT_TYPE_ERROR = 16990 ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR = 17040 ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR = 17041 diff --git a/documentdb_tests/framework/test_structure_validator.py b/documentdb_tests/framework/test_structure_validator.py index 57f811f0..c0b72544 100644 --- a/documentdb_tests/framework/test_structure_validator.py +++ b/documentdb_tests/framework/test_structure_validator.py @@ -17,6 +17,8 @@ def validate_python_files_in_tests(tests_dir: Path) -> list[str]: for py_file in tests_dir.rglob("*.py"): if py_file.name == "__init__.py": continue + if py_file.name == "conftest.py": + continue if any(folder in py_file.parts for folder in allowed_folders): continue From 2ce6bf10a15373694049422f998e8ee6b4b98eff Mon Sep 17 00:00:00 2001 From: "Victor [C] Tsang" Date: Tue, 26 May 2026 21:18:35 +0000 Subject: [PATCH 2/2] revised based on reviwer comment Signed-off-by: Victor [C] Tsang --- .../specifiers/maxDistance/test_maxDistance_geojson.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py index b37303a4..9d3e865f 100644 --- a/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py +++ b/documentdb_tests/compatibility/tests/core/operator/query/geospatial/specifiers/maxDistance/test_maxDistance_geojson.py @@ -677,7 +677,7 @@ def test_maxDistance_sort_stability_close_points(collection): collection.insert_many( [ {"_id": 1, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, - {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.001]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.005]}}, {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 0]}}, ] ) @@ -685,7 +685,7 @@ def test_maxDistance_sort_stability_close_points(collection): collection, { "find": collection.name, - "filter": {"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 200}}}, + "filter": {"loc": {"$near": {"$geometry": ORIGIN, "$maxDistance": 600}}}, }, ) assertSuccess( @@ -693,7 +693,7 @@ def test_maxDistance_sort_stability_close_points(collection): [ {"_id": 3, "loc": {"type": "Point", "coordinates": [0, 0]}}, {"_id": 1, "loc": {"type": "Point", "coordinates": [0.001, 0]}}, - {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.001]}}, + {"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0.005]}}, ], - msg="Should return all points within 200m sorted by distance", + msg="Should return all points sorted by distance", )