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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Tests for $geoWithin BSON type validation of shape operator arguments.

Verifies that each $geoWithin shape operator rejects invalid BSON types for its
value with expected error codes and accepts valid BSON types without error.
"""

import pytest

from documentdb_tests.framework.assertions import assertFailureCode, assertNotError
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

GEOWITHIN_PARAMS = [
BsonTypeTestCase(
id="geometry",
msg="$geometry should reject non-object types",
keyword="$geometry",
valid_types=[BsonType.OBJECT],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={
BsonType.OBJECT: {
"type": "Polygon",
"coordinates": [[[-10, -10], [10, -10], [10, 10], [-10, 10], [-10, -10]]],
}
},
),
BsonTypeTestCase(
id="box",
msg="$box should reject non-array types and empty array",
keyword="$box",
valid_types=[BsonType.ARRAY],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={BsonType.ARRAY: [[-10, -10], [10, 10]]},
),
BsonTypeTestCase(
id="polygon",
msg="$polygon should reject non-array types and empty array",
keyword="$polygon",
valid_types=[BsonType.ARRAY],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={BsonType.ARRAY: [[0, 0], [10, 0], [10, 10]]},
),
BsonTypeTestCase(
id="center",
msg="$center should reject non-array types and empty array",
keyword="$center",
valid_types=[BsonType.ARRAY],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={BsonType.ARRAY: [[0, 0], 10]},
),
BsonTypeTestCase(
id="centerSphere",
msg="$centerSphere should reject non-array types and empty array",
keyword="$centerSphere",
valid_types=[BsonType.ARRAY],
default_error_code=BAD_VALUE_ERROR,
valid_inputs={BsonType.ARRAY: [[0, 0], 0.5]},
),
]

TEST_CASES = generate_bson_rejection_test_cases(GEOWITHIN_PARAMS)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
TEST_CASES,
)
def test_geoWithin_bson_type_rejected(collection, bson_type, sample_value, spec):
"""Test $geoWithin shape operators reject invalid BSON types."""
query_filter = {"loc": {"$geoWithin": {spec.keyword: sample_value}}}
result = execute_command(collection, {"find": collection.name, "filter": query_filter})
assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg)


ACCEPTANCE_CASES = generate_bson_acceptance_test_cases(GEOWITHIN_PARAMS)


@pytest.mark.parametrize(
"bson_type,sample_value,spec",
ACCEPTANCE_CASES,
)
def test_geoWithin_bson_type_accepted(collection, bson_type, sample_value, spec):
"""Test $geoWithin shape operators accept valid BSON types."""
query_filter = {"loc": {"$geoWithin": {spec.keyword: sample_value}}}
result = execute_command(collection, {"find": collection.name, "filter": query_filter})
assertNotError(result, msg=f"{spec.keyword} should accept {bson_type.value}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""
Tests for $geoWithin $centerSphere containment of non-Point geometry types
(LineString, Polygon, MultiPolygon).
"""

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

# Spherical cap centered at [0, 0] with radius ~111km (0.01 radians ≈ 0.57 degrees)
SMALL_CAP = {"$centerSphere": [[0, 0], 0.01]}

# Larger cap centered at [0, 0] with radius ~1111km (0.1 radians ≈ 5.7 degrees)
LARGE_CAP = {"$centerSphere": [[0, 0], 0.1]}


CENTERSPHERE_LINESTRING_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="linestring_entirely_within_cap",
filter={"geo": {"$geoWithin": LARGE_CAP}},
doc=[
{"_id": 1, "geo": {"type": "LineString", "coordinates": [[0.1, 0.1], [0.2, 0.2]]}},
{"_id": 2, "geo": {"type": "LineString", "coordinates": [[0, 0], [20, 20]]}},
],
expected=[
{"_id": 1, "geo": {"type": "LineString", "coordinates": [[0.1, 0.1], [0.2, 0.2]]}}
],
msg="LineString entirely within $centerSphere should match",
),
QueryTestCase(
id="linestring_intersecting_cap_no_match",
filter={"geo": {"$geoWithin": SMALL_CAP}},
doc=[{"_id": 1, "geo": {"type": "LineString", "coordinates": [[0, 0], [5, 5]]}}],
expected=[],
msg="LineString intersecting but not entirely within $centerSphere should not match",
),
QueryTestCase(
id="linestring_outside_cap_no_match",
filter={"geo": {"$geoWithin": SMALL_CAP}},
doc=[{"_id": 1, "geo": {"type": "LineString", "coordinates": [[10, 10], [11, 11]]}}],
expected=[],
msg="LineString entirely outside $centerSphere should not match",
),
]


CENTERSPHERE_POLYGON_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="polygon_entirely_within_cap",
filter={"geo": {"$geoWithin": LARGE_CAP}},
doc=[
{
"_id": 1,
"geo": {
"type": "Polygon",
"coordinates": [[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]],
},
},
{
"_id": 2,
"geo": {
"type": "Polygon",
"coordinates": [[[20, 20], [21, 20], [21, 21], [20, 21], [20, 20]]],
},
},
],
expected=[
{
"_id": 1,
"geo": {
"type": "Polygon",
"coordinates": [[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]],
},
}
],
msg="Polygon entirely within $centerSphere should match",
),
QueryTestCase(
id="polygon_intersecting_cap_no_match",
filter={"geo": {"$geoWithin": SMALL_CAP}},
doc=[
{
"_id": 1,
"geo": {
"type": "Polygon",
"coordinates": [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]],
},
}
],
expected=[],
msg="Polygon intersecting but not entirely within $centerSphere should not match",
),
QueryTestCase(
id="polygon_outside_cap_no_match",
filter={"geo": {"$geoWithin": SMALL_CAP}},
doc=[
{
"_id": 1,
"geo": {
"type": "Polygon",
"coordinates": [[[10, 10], [11, 10], [11, 11], [10, 11], [10, 10]]],
},
}
],
expected=[],
msg="Polygon entirely outside $centerSphere should not match",
),
]


CENTERSPHERE_MULTIPOLYGON_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="multipolygon_all_within_cap",
filter={"geo": {"$geoWithin": LARGE_CAP}},
doc=[
{
"_id": 1,
"geo": {
"type": "MultiPolygon",
"coordinates": [
[[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]],
[[[0.3, 0.3], [0.4, 0.3], [0.4, 0.4], [0.3, 0.4], [0.3, 0.3]]],
],
},
},
{
"_id": 2,
"geo": {
"type": "MultiPolygon",
"coordinates": [
[[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]],
[[[20, 20], [21, 20], [21, 21], [20, 21], [20, 20]]],
],
},
},
],
expected=[
{
"_id": 1,
"geo": {
"type": "MultiPolygon",
"coordinates": [
[[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2], [0.1, 0.1]]],
[[[0.3, 0.3], [0.4, 0.3], [0.4, 0.4], [0.3, 0.4], [0.3, 0.3]]],
],
},
}
],
msg="MultiPolygon with all polygons within $centerSphere should match",
),
QueryTestCase(
id="multipolygon_one_outside_no_match",
filter={"geo": {"$geoWithin": SMALL_CAP}},
doc=[
{
"_id": 1,
"geo": {
"type": "MultiPolygon",
"coordinates": [
[
[
[0.001, 0.001],
[0.002, 0.001],
[0.002, 0.002],
[0.001, 0.002],
[0.001, 0.001],
]
],
[[[20, 20], [21, 20], [21, 21], [20, 21], [20, 20]]],
],
},
}
],
expected=[],
msg="MultiPolygon with one polygon outside $centerSphere should not match",
),
]


ALL_TESTS = (
CENTERSPHERE_LINESTRING_TESTS + CENTERSPHERE_POLYGON_TESTS + CENTERSPHERE_MULTIPOLYGON_TESTS
)


@pytest.mark.parametrize("test", pytest_params(ALL_TESTS))
def test_geoWithin_centersphere_containment(collection, test):
"""Test $geoWithin $centerSphere containment of non-Point geometry types."""
collection.insert_many(test.doc)
result = execute_command(collection, {"find": collection.name, "filter": test.filter})
assertSuccess(result, test.expected, ignore_doc_order=True)
Loading
Loading