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
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Tests for $center valid argument and type handling."""

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 INT64_ZERO

TESTS: list[QueryTestCase] = [
QueryTestCase(
id="valid_structure",
filter={"loc": {"$geoWithin": {"$center": [[-74, 40.74], 100]}}},
doc=[{"_id": 1, "loc": [0, 0]}],
expected=[{"_id": 1, "loc": [0, 0]}],
msg="Should accept valid $center structure",
),
QueryTestCase(
id="coords_int_and_int64",
filter={"loc": {"$geoWithin": {"$center": [[0, INT64_ZERO], 10]}}},
doc=[{"_id": 1, "loc": [0, 0]}, {"_id": 2, "loc": [100, 100]}],
expected=[{"_id": 1, "loc": [0, 0]}],
msg="Should accept int and Int64 mixed coordinates",
),
QueryTestCase(
id="coords_int64_and_decimal128",
filter={"loc": {"$geoWithin": {"$center": [[Int64(0), Decimal128("0")], 10]}}},
doc=[{"_id": 1, "loc": [0, 0]}, {"_id": 2, "loc": [100, 100]}],
expected=[{"_id": 1, "loc": [0, 0]}],
msg="Should accept Int64 and Decimal128 mixed coordinates",
),
QueryTestCase(
id="coords_float_and_decimal128",
filter={"loc": {"$geoWithin": {"$center": [[0.0, Decimal128("0")], 10]}}},
doc=[{"_id": 1, "loc": [0, 0]}, {"_id": 2, "loc": [100, 100]}],
expected=[{"_id": 1, "loc": [0, 0]}],
msg="Should accept float and Decimal128 mixed coordinates",
),
QueryTestCase(
id="string_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": "not a coordinate"}],
expected=[],
msg="Should not match string field value",
),
QueryTestCase(
id="number_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": 42}],
expected=[],
msg="Should not match plain number field value",
),
QueryTestCase(
id="object_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": {"nested": "object"}}],
expected=[],
msg="Should not match nested object field value",
),
QueryTestCase(
id="boolean_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": True}],
expected=[],
msg="Should not match boolean field value",
),
QueryTestCase(
id="decimal128_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": Decimal128("42")}],
expected=[],
msg="Should not match Decimal128 field value",
),
QueryTestCase(
id="null_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "loc": None}, {"_id": 2, "loc": [0, 0]}],
expected=[{"_id": 2, "loc": [0, 0]}],
msg="Should not match document with null location field",
),
QueryTestCase(
id="missing_field_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 100]}}},
doc=[{"_id": 1, "other": "value"}, {"_id": 2, "loc": [0, 0]}],
expected=[{"_id": 2, "loc": [0, 0]}],
msg="Should not match document with missing location field",
),
QueryTestCase(
id="embedded_xy_document",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 5]}}},
doc=[{"_id": 1, "loc": {"x": 0, "y": 0}}, {"_id": 2, "loc": {"x": 10, "y": 10}}],
expected=[{"_id": 1, "loc": {"x": 0, "y": 0}}],
msg="Should match embedded document with x/y fields",
),
QueryTestCase(
id="array_more_than_two_elements",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 5]}}},
doc=[{"_id": 1, "loc": [1, 1, 99]}, {"_id": 2, "loc": [10, 10, 0]}],
expected=[{"_id": 1, "loc": [1, 1, 99]}],
msg="Should use first 2 elements of array with more than 2 elements",
),
QueryTestCase(
id="nested_array_field",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 5]}}},
doc=[{"_id": 1, "loc": [[0, 0]]}, {"_id": 2, "loc": [0, 0]}],
expected=[{"_id": 1, "loc": [[0, 0]]}, {"_id": 2, "loc": [0, 0]}],
msg="Should match array-of-arrays via multi-key expansion",
),
QueryTestCase(
id="empty_array_no_match",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 5]}}},
doc=[{"_id": 1, "loc": []}, {"_id": 2, "loc": [0, 0]}],
expected=[{"_id": 2, "loc": [0, 0]}],
msg="Should not match document with empty array location field",
),
]


@pytest.mark.parametrize("test", pytest_params(TESTS))
def test_center_argument_handling(collection, test):
"""Verifies $center accepts valid argument structures and field formats."""
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, ignore_doc_order=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
Tests for $center BSON type comparison.

Verifies that $center coordinate and radius values reject invalid BSON types with expected
error codes and accept valid numeric BSON types without error.
"""

import pytest
from bson import Decimal128, Int64

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
from documentdb_tests.framework.executor import execute_command

CENTER_COORDINATE_PARAMS = [
BsonTypeTestCase(
id="center_coordinate",
msg="$center coordinates should reject non-numeric types",
keyword="$center",
valid_types=[BsonType.DOUBLE, BsonType.INT, BsonType.LONG, BsonType.DECIMAL],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={
BsonType.DOUBLE: 10.5,
BsonType.INT: 10,
BsonType.LONG: Int64(10),
BsonType.DECIMAL: Decimal128("10"),
},
),
]

CENTER_RADIUS_PARAMS = [
BsonTypeTestCase(
id="center_radius",
msg="$center radius should reject non-numeric types",
keyword="$center",
valid_types=[BsonType.DOUBLE, BsonType.INT, BsonType.LONG, BsonType.DECIMAL],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={
BsonType.DOUBLE: 10.5,
BsonType.INT: 10,
BsonType.LONG: Int64(10),
BsonType.DECIMAL: Decimal128("10"),
},
),
]

COORDINATE_REJECTION_CASES = generate_bson_rejection_test_cases(CENTER_COORDINATE_PARAMS)
RADIUS_REJECTION_CASES = generate_bson_rejection_test_cases(CENTER_RADIUS_PARAMS)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
COORDINATE_REJECTION_CASES,
)
def test_center_coordinate_bson_type_rejected(collection, bson_type, sample_value, spec):
"""Test $center rejects invalid BSON types as coordinate values."""
query = {"loc": {"$geoWithin": {"$center": [[sample_value, sample_value], 10]}}}
result = execute_command(collection, {"find": collection.name, "filter": query})
assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
RADIUS_REJECTION_CASES,
)
def test_center_radius_bson_type_rejected(collection, bson_type, sample_value, spec):
"""Test $center rejects invalid BSON types as radius value."""
query = {"loc": {"$geoWithin": {"$center": [[0, 0], sample_value]}}}
result = execute_command(collection, {"find": collection.name, "filter": query})
assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg)


COORDINATE_ACCEPTANCE_CASES = generate_bson_acceptance_test_cases(CENTER_COORDINATE_PARAMS)
RADIUS_ACCEPTANCE_CASES = generate_bson_acceptance_test_cases(CENTER_RADIUS_PARAMS)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
COORDINATE_ACCEPTANCE_CASES,
)
def test_center_coordinate_bson_type_accepted(collection, bson_type, sample_value, spec):
"""Test $center accepts valid numeric BSON types as coordinate values."""
collection.insert_many(
[
{"_id": 1, "loc": [12, 8]},
{"_id": 2, "loc": [100, 100]},
]
)
query = {"loc": {"$geoWithin": {"$center": [[sample_value, sample_value], 50]}}}
result = execute_command(collection, {"find": collection.name, "filter": query})
assertSuccess(result, [{"_id": 1, "loc": [12, 8]}], ignore_doc_order=True)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
RADIUS_ACCEPTANCE_CASES,
)
def test_center_radius_bson_type_accepted(collection, bson_type, sample_value, spec):
"""Test $center accepts valid numeric BSON types as radius value."""
collection.insert_many(
[
{"_id": 1, "loc": [-30, 25]},
{"_id": 2, "loc": [100, 100]},
]
)
query = {"loc": {"$geoWithin": {"$center": [[-28, 22], sample_value]}}}
result = execute_command(collection, {"find": collection.name, "filter": query})
assertSuccess(result, [{"_id": 1, "loc": [-30, 25]}], ignore_doc_order=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Tests for $center core functionality."""

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

TESTS: list[QueryTestCase] = [
QueryTestCase(
id="returns_documents_within_circle",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 2]}}},
doc=[{"_id": 1, "loc": [0, 0]}, {"_id": 2, "loc": [1, 1]}, {"_id": 3, "loc": [5, 5]}],
expected=[{"_id": 1, "loc": [0, 0]}, {"_id": 2, "loc": [1, 1]}],
msg="Should return documents within circular bounds",
),
QueryTestCase(
id="point_at_center_matches",
filter={"loc": {"$geoWithin": {"$center": [[10, 10], 5]}}},
doc=[{"_id": 1, "loc": [10, 10]}, {"_id": 2, "loc": [20, 20]}],
expected=[{"_id": 1, "loc": [10, 10]}],
msg="Should match point at center (distance=0)",
),
QueryTestCase(
id="longitude_latitude_ordering",
filter={"loc": {"$geoWithin": {"$center": [[-74, 40], 1]}}},
doc=[{"_id": 1, "loc": [-74, 40]}, {"_id": 2, "loc": [40, -74]}],
expected=[{"_id": 1, "loc": [-74, 40]}],
msg="Should use [x, y] ordering",
),
QueryTestCase(
id="includes_geojson_point_without_index",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 10]}}},
doc=[
{"_id": 1, "loc": [0, 0]},
{"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}},
],
expected=[
{"_id": 1, "loc": [0, 0]},
{"_id": 2, "loc": {"type": "Point", "coordinates": [0, 0]}},
],
msg="Should return both legacy and GeoJSON documents without 2d index",
),
QueryTestCase(
id="no_documents_inside",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 1]}}},
doc=[{"_id": 1, "loc": [10, 10]}, {"_id": 2, "loc": [20, 20]}],
expected=[],
msg="Should return empty result when no documents match",
),
QueryTestCase(
id="boundary_points_count",
filter={"loc": {"$geoWithin": {"$center": [[0, 0], 1.5]}}},
doc=[
{"_id": 1, "loc": [0, 0]},
{"_id": 2, "loc": [1, 0]},
{"_id": 3, "loc": [0, 1]},
{"_id": 4, "loc": [-1, 0]},
{"_id": 5, "loc": [0, -1]},
{"_id": 6, "loc": [2, 2]},
],
expected=[
{"_id": 1, "loc": [0, 0]},
{"_id": 2, "loc": [1, 0]},
{"_id": 3, "loc": [0, 1]},
{"_id": 4, "loc": [-1, 0]},
{"_id": 5, "loc": [0, -1]},
],
msg="Should return exactly 5 points within radius 1.5",
),
QueryTestCase(
id="with_additional_field_predicate",
filter={
"loc": {"$geoWithin": {"$center": [[0, 0], 2]}},
"type": "a",
},
doc=[
{"_id": 1, "loc": [0, 0], "type": "a"},
{"_id": 2, "loc": [1, 0], "type": "b"},
{"_id": 3, "loc": [0, 1], "type": "a"},
{"_id": 4, "loc": [10, 10], "type": "a"},
],
expected=[
{"_id": 1, "loc": [0, 0], "type": "a"},
{"_id": 3, "loc": [0, 1], "type": "a"},
],
msg="Should filter by both $center and additional field predicate",
),
]


@pytest.mark.parametrize("test", pytest_params(TESTS))
def test_center_core_functionality(collection, test):
"""Verifies $center returns correct documents within the circular region."""
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, ignore_doc_order=True)
Loading
Loading