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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import pytest

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.parametrize import pytest_params

# Property [No Cross-Type Coercion]: BSON equality does not implicitly coerce
# values across types.
NO_COERCION_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"int_ne_string",
expression={"$eq": [1, "1"]},
expected=False,
msg="BSON equality should not coerce a number to a string",
),
ExpressionTestCase(
"int_ne_bool",
expression={"$eq": [0, False]},
expected=False,
msg="BSON equality should not coerce a number to a bool",
),
ExpressionTestCase(
"one_ne_true",
expression={"$eq": [1, True]},
expected=False,
msg="BSON equality should not coerce a number to a bool",
),
ExpressionTestCase(
"null_ne_bool",
expression={"$eq": [None, False]},
expected=False,
msg="BSON equality should not coerce null to a bool",
),
ExpressionTestCase(
"empty_string_ne_null",
expression={"$eq": ["", None]},
expected=False,
msg="BSON equality should not coerce an empty string to null",
),
ExpressionTestCase(
"zero_ne_null",
expression={"$eq": [0, None]},
expected=False,
msg="BSON equality should not coerce zero to null",
),
ExpressionTestCase(
"empty_string_ne_false",
expression={"$eq": ["", False]},
expected=False,
msg="BSON equality should not coerce an empty string to false",
),
ExpressionTestCase(
"empty_array_ne_false",
expression={"$eq": [[], False]},
expected=False,
msg="BSON equality should not coerce an empty array to false",
),
]


@pytest.mark.parametrize("test", pytest_params(NO_COERCION_TESTS))
def test_bson_types_no_coercion(collection, test):
"""Test BSON equality does not implicitly coerce across types."""
result = execute_expression(collection, test.expression)
assert_expression_result(result, expected=test.expected, msg=test.msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
from datetime import datetime, timezone

import pytest
from bson import Binary, Code, MaxKey, MinKey, ObjectId, Regex, Timestamp

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.parametrize import pytest_params
from documentdb_tests.framework.test_constants import (
DECIMAL128_ZERO,
DOUBLE_ZERO,
INT64_ZERO,
)

# Property [Adjacent Type Order]: each BSON type sorts immediately before the
# next type in the canonical order MinKey < Null < Number < String < Object <
# Array < BinData < ObjectId < Bool < Date < Timestamp < Regex < JavaScript <
# MaxKey.
ADJACENT_TYPE_ORDERING_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"minkey_lt_null",
expression={"$lt": [MinKey(), None]},
expected=True,
msg="BSON ordering should place MinKey before null",
),
ExpressionTestCase(
"null_lt_int",
expression={"$lt": [None, 0]},
expected=True,
msg="BSON ordering should place null before int32",
),
ExpressionTestCase(
"null_lt_long",
expression={"$lt": [None, INT64_ZERO]},
expected=True,
msg="BSON ordering should place null before Int64",
),
ExpressionTestCase(
"null_lt_double",
expression={"$lt": [None, DOUBLE_ZERO]},
expected=True,
msg="BSON ordering should place null before double",
),
ExpressionTestCase(
"null_lt_decimal128",
expression={"$lt": [None, DECIMAL128_ZERO]},
expected=True,
msg="BSON ordering should place null before Decimal128",
),
ExpressionTestCase(
"int_lt_string",
expression={"$lt": [0, ""]},
expected=True,
msg="BSON ordering should place int32 before string",
),
ExpressionTestCase(
"long_lt_string",
expression={"$lt": [INT64_ZERO, ""]},
expected=True,
msg="BSON ordering should place Int64 before string",
),
ExpressionTestCase(
"double_lt_string",
expression={"$lt": [DOUBLE_ZERO, ""]},
expected=True,
msg="BSON ordering should place double before string",
),
ExpressionTestCase(
"decimal128_lt_string",
expression={"$lt": [DECIMAL128_ZERO, ""]},
expected=True,
msg="BSON ordering should place Decimal128 before string",
),
ExpressionTestCase(
"string_lt_object",
expression={"$lt": ["", {}]},
expected=True,
msg="BSON ordering should place string before object",
),
ExpressionTestCase(
"object_lt_array",
expression={"$lt": [{}, []]},
expected=True,
msg="BSON ordering should place object before array",
),
ExpressionTestCase(
"array_lt_bindata",
expression={"$lt": [[], Binary(b"", 0)]},
expected=True,
msg="BSON ordering should place array before BinData",
),
ExpressionTestCase(
"bindata_lt_objectid",
expression={"$lt": [Binary(b"", 0), ObjectId("000000000000000000000000")]},
expected=True,
msg="BSON ordering should place BinData before ObjectId",
),
ExpressionTestCase(
"objectid_lt_bool",
expression={"$lt": [ObjectId("000000000000000000000000"), False]},
expected=True,
msg="BSON ordering should place ObjectId before bool",
),
ExpressionTestCase(
"bool_lt_date",
expression={"$lt": [False, datetime(1970, 1, 1, tzinfo=timezone.utc)]},
expected=True,
msg="BSON ordering should place bool before date",
),
ExpressionTestCase(
"date_lt_timestamp",
expression={"$lt": [datetime(1970, 1, 1, tzinfo=timezone.utc), Timestamp(0, 0)]},
expected=True,
msg="BSON ordering should place date before Timestamp",
),
ExpressionTestCase(
"timestamp_lt_regex",
expression={"$lt": [Timestamp(0, 0), Regex("a")]},
expected=True,
msg="BSON ordering should place Timestamp before regex",
),
ExpressionTestCase(
"regex_lt_javascript",
expression={"$lt": [Regex("a"), Code("f")]},
expected=True,
msg="BSON ordering should place regex before JavaScript code",
),
ExpressionTestCase(
"javascript_lt_maxkey",
expression={"$lt": [Code("f"), MaxKey()]},
expected=True,
msg="BSON ordering should place JavaScript code before MaxKey",
),
]

# Property [Non-Adjacent Type Order]: cross-type ordering is transitive, so a
# type sorts before every type that appears later in the canonical order, not
# only its immediate successor.
NON_ADJACENT_TYPE_ORDERING_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"null_lt_object",
expression={"$lt": [None, {}]},
expected=True,
msg="BSON ordering should place null before object across intervening types",
),
ExpressionTestCase(
"int_lt_bindata",
expression={"$lt": [0, Binary(b"", 0)]},
expected=True,
msg="BSON ordering should place number before BinData across intervening types",
),
ExpressionTestCase(
"string_lt_timestamp",
expression={"$lt": ["", Timestamp(0, 0)]},
expected=True,
msg="BSON ordering should place string before Timestamp across intervening types",
),
ExpressionTestCase(
"minkey_lt_maxkey",
expression={"$lt": [MinKey(), MaxKey()]},
expected=True,
msg="BSON ordering should place MinKey before MaxKey at the extremes",
),
]

TYPE_ORDERING_TESTS = ADJACENT_TYPE_ORDERING_TESTS + NON_ADJACENT_TYPE_ORDERING_TESTS


@pytest.mark.parametrize("test", pytest_params(TYPE_ORDERING_TESTS))
def test_bson_types_ordering(collection, test):
"""Test cross-type BSON comparison order."""
result = execute_expression(collection, test.expression)
assert_expression_result(result, expected=test.expected, msg=test.msg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import pytest
from bson import Int64

from documentdb_tests.compatibility.tests.core.operator.expressions.utils.expression_test_case import ( # noqa: E501
ExpressionTestCase,
)
from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import (
assert_expression_result,
execute_expression,
)
from documentdb_tests.framework.assertions import assertSuccess
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params

from ..utils.round_trip_test_case import RoundTripTestCase

# Property [Array Ordering]: arrays compare element by element, with a prefix
# array sorting before a longer one.
ARRAY_COMPARISON_TESTS: list[ExpressionTestCase] = [
ExpressionTestCase(
"empty_equality",
expression={"$eq": [[], []]},
expected=True,
msg="Array equality should hold for two empty arrays",
),
ExpressionTestCase(
"element_by_element",
expression={"$lt": [[1, 2], [2]]},
expected=True,
msg="Array ordering should compare the first differing element",
),
ExpressionTestCase(
"prefix_shorter_first",
expression={"$lt": [[1], [1, 2]]},
expected=True,
msg="Array ordering should place a prefix before the longer array",
),
ExpressionTestCase(
"empty_before_nonempty",
expression={"$lt": [[], [1]]},
expected=True,
msg="Array ordering should place the empty array before a non-empty array",
),
ExpressionTestCase(
"numeric_equivalence",
expression={"$eq": [[1], [Int64(1)]]},
expected=True,
msg="Array equality should apply numeric equivalence to elements",
),
ExpressionTestCase(
"bool_vs_number_distinct",
expression={"$eq": [[True], [1]]},
expected=False,
msg="Array equality should not coerce a bool element to a number",
),
ExpressionTestCase(
"nested_recursion",
expression={"$lt": [[[1]], [[2]]]},
expected=True,
msg="Array ordering should recurse into nested arrays",
),
]


@pytest.mark.parametrize("test", pytest_params(ARRAY_COMPARISON_TESTS))
def test_array_comparison(collection, test):
"""Test array BSON type comparison semantics."""
result = execute_expression(collection, test.expression)
assert_expression_result(result, expected=test.expected, msg=test.msg)


# Property [Array Round-Trip Fidelity]: array values survive insert and
# retrieval unchanged.
ARRAY_ROUND_TRIP_TESTS: list[RoundTripTestCase] = [
RoundTripTestCase(
"empty_array",
value=[],
expected=[],
msg="Empty array should survive round-trip",
),
RoundTripTestCase(
"nested_array",
value=[[1, 2], [3, [4, 5]]],
expected=[[1, 2], [3, [4, 5]]],
msg="Nested array should survive round-trip",
),
RoundTripTestCase(
"mixed_types",
value=[1, "two", True, None, [3]],
expected=[1, "two", True, None, [3]],
msg="Array with mixed types should survive round-trip",
),
]


@pytest.mark.parametrize("test", pytest_params(ARRAY_ROUND_TRIP_TESTS))
def test_array_round_trip(collection, test):
"""Test array values survive storage and retrieval unchanged."""
collection.insert_one({"_id": test.id, "v": test.value})
result = execute_command(collection, {"find": collection.name, "filter": {"_id": test.id}})
assertSuccess(result, [{"_id": test.id, "v": test.expected}], msg=test.msg)
Loading
Loading