From d8377add888449e60584d73a9bc7c327d8c409e5 Mon Sep 17 00:00:00 2001 From: Victor Tsang Date: Mon, 20 Apr 2026 00:36:57 -0700 Subject: [PATCH] Added comparison tests for $gt and $gte Signed-off-by: Victor Tsang --- .../expressions/comparisons/gt/__init__.py | 0 .../gt/test_gt_argument_handling.py | 77 ++++ .../gt/test_gt_boundary_precision.py | 173 ++++++++ .../comparisons/gt/test_gt_bson_wiring.py | 132 +++++++ .../gt/test_gt_expression_types.py | 139 +++++++ .../comparisons/gt/test_gt_field_lookup.py | 76 ++++ .../comparisons/gt/test_gt_nan_infinity.py | 98 +++++ .../comparisons/gt/test_gt_null_missing.py | 93 +++++ .../gt/test_gt_same_type_comparisons.py | 321 +++++++++++++++ .../expressions/comparisons/gte/__init__.py | 0 .../gte/test_gte_argument_handling.py | 77 ++++ .../gte/test_gte_boundary_precision.py | 190 +++++++++ .../comparisons/gte/test_gte_bson_wiring.py | 135 +++++++ .../gte/test_gte_expression_types.py | 148 +++++++ .../comparisons/gte/test_gte_field_lookup.py | 100 +++++ .../comparisons/gte/test_gte_nan_infinity.py | 98 +++++ .../comparisons/gte/test_gte_null_missing.py | 94 +++++ .../gte/test_gte_same_type_comparisons.py | 374 ++++++++++++++++++ 18 files changed, 2325 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/__init__.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_argument_handling.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_boundary_precision.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_expression_types.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_field_lookup.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_nan_infinity.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_null_missing.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_same_type_comparisons.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/__init__.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_argument_handling.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_boundary_precision.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_bson_wiring.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_expression_types.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_field_lookup.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_nan_infinity.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_null_missing.py create mode 100644 documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_same_type_comparisons.py diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_argument_handling.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_argument_handling.py new file mode 100644 index 00000000..c52eee9c --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_argument_handling.py @@ -0,0 +1,77 @@ +""" +Tests for $gt argument handling. + +Covers argument count variations and error cases. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.error_codes import EXPRESSION_TYPE_MISMATCH_ERROR +from documentdb_tests.framework.parametrize import pytest_params + +GT_ARG_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "no_args", + expression={"$gt": []}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for empty arguments", + ), + ExpressionTestCase( + "single_arg", + expression={"$gt": [1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for single argument", + ), + ExpressionTestCase( + "non_array_arg", + expression={"$gt": 1}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array argument", + ), + ExpressionTestCase( + "non_array_string", + expression={"$gt": "string"}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for string argument", + ), + ExpressionTestCase( + "three_args", + expression={"$gt": [3, 2, 1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for three arguments", + ), + ExpressionTestCase( + "two_args_gt", + expression={"$gt": [2, 1]}, + expected=True, + msg="Should return true when first > second", + ), + ExpressionTestCase( + "two_args_eq", + expression={"$gt": [1, 1]}, + expected=False, + msg="Should return false when equal", + ), + ExpressionTestCase( + "two_args_lt", + expression={"$gt": [1, 2]}, + expected=False, + msg="Should return false when first < second", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(GT_ARG_TESTS)) +def test_gt_argument_handling(collection, test): + """Test $gt argument count variations.""" + result = execute_expression(collection, test.expression) + assert_expression_result( + result, expected=test.expected, error_code=test.error_code, msg=test.msg + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_boundary_precision.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_boundary_precision.py new file mode 100644 index 00000000..4a31ce9d --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_boundary_precision.py @@ -0,0 +1,173 @@ +""" +Tests for $gt numeric boundaries and double precision. + +Covers INT32/INT64 boundaries, double subnormals, and large number precision edge cases. +""" + +import pytest +from bson import Decimal128, 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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DOUBLE_MIN_NEGATIVE_SUBNORMAL, + DOUBLE_MIN_SUBNORMAL, + DOUBLE_NEAR_MAX, + DOUBLE_NEAR_MIN, + INT32_MAX, + INT32_MIN, + INT64_MAX, + INT64_MIN, +) + +INT32_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int32_max_gt_max_minus1", + expression={"$gt": [INT32_MAX, INT32_MAX - 1]}, + expected=True, + msg="INT32_MAX > INT32_MAX-1", + ), + ExpressionTestCase( + "int32_max_minus1_not_gt_max", + expression={"$gt": [INT32_MAX - 1, INT32_MAX]}, + expected=False, + msg="INT32_MAX-1 not > INT32_MAX", + ), + ExpressionTestCase( + "int32_max_self", + expression={"$gt": [INT32_MAX, INT32_MAX]}, + expected=False, + msg="INT32_MAX not > INT32_MAX", + ), + ExpressionTestCase( + "int32_min_plus1_gt_min", + expression={"$gt": [INT32_MIN + 1, INT32_MIN]}, + expected=True, + msg="INT32_MIN+1 > INT32_MIN", + ), + ExpressionTestCase( + "int32_min_self", + expression={"$gt": [INT32_MIN, INT32_MIN]}, + expected=False, + msg="INT32_MIN not > INT32_MIN", + ), + ExpressionTestCase( + "long_above_int32_gt_int32_max", + expression={"$gt": [Int64(INT32_MAX + 1), INT32_MAX]}, + expected=True, + msg="long(INT32_MAX+1) > int(INT32_MAX)", + ), + ExpressionTestCase( + "int32_max_not_gt_long_above", + expression={"$gt": [INT32_MAX, Int64(INT32_MAX + 1)]}, + expected=False, + msg="int(INT32_MAX) not > long(INT32_MAX+1)", + ), + ExpressionTestCase( + "long_below_int32_gt_int32_min", + expression={"$gt": [Int64(INT32_MIN - 1), INT32_MIN]}, + expected=False, + msg="long(INT32_MIN-1) not > int(INT32_MIN)", + ), +] + +INT64_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_max_gt_max_minus1", + expression={"$gt": [INT64_MAX, Int64(INT64_MAX - 1)]}, + expected=True, + msg="INT64_MAX > INT64_MAX-1", + ), + ExpressionTestCase( + "int64_max_self", + expression={"$gt": [INT64_MAX, INT64_MAX]}, + expected=False, + msg="INT64_MAX not > INT64_MAX", + ), + ExpressionTestCase( + "int64_min_plus1_gt_min", + expression={"$gt": [Int64(INT64_MIN + 1), INT64_MIN]}, + expected=True, + msg="INT64_MIN+1 > INT64_MIN", + ), + ExpressionTestCase( + "int64_min_self", + expression={"$gt": [INT64_MIN, INT64_MIN]}, + expected=False, + msg="INT64_MIN not > INT64_MIN", + ), +] + +DOUBLE_PRECISION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "near_max_self", + expression={"$gt": [DOUBLE_NEAR_MAX, DOUBLE_NEAR_MAX]}, + expected=False, + msg="DOUBLE_NEAR_MAX not > itself", + ), + ExpressionTestCase( + "subnormal_gt_zero", + expression={"$gt": [DOUBLE_MIN_SUBNORMAL, 0.0]}, + expected=True, + msg="min subnormal > 0.0", + ), + ExpressionTestCase( + "zero_gt_subnormal", + expression={"$gt": [0.0, DOUBLE_MIN_SUBNORMAL]}, + expected=False, + msg="0.0 not > min subnormal", + ), + ExpressionTestCase( + "neg_subnormal_gt_zero", + expression={"$gt": [DOUBLE_MIN_NEGATIVE_SUBNORMAL, 0.0]}, + expected=False, + msg="neg subnormal not > 0.0", + ), + ExpressionTestCase( + "zero_gt_neg_subnormal", + expression={"$gt": [0.0, DOUBLE_MIN_NEGATIVE_SUBNORMAL]}, + expected=True, + msg="0.0 > neg subnormal", + ), + ExpressionTestCase( + "near_min_gt_neg_subnormal", + expression={"$gt": [DOUBLE_NEAR_MIN, DOUBLE_MIN_NEGATIVE_SUBNORMAL]}, + expected=True, + msg="DOUBLE_NEAR_MIN > neg subnormal", + ), +] + +LARGE_NUMBER_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_beyond_double_precision", + expression={"$gt": [Int64(9007199254740993), Int64(9007199254740992)]}, + expected=True, + msg="int64 beyond double precision", + ), + ExpressionTestCase( + "dec_34_digit", + expression={ + "$gt": [ + Decimal128("9999999999999999999999999999999999"), + Decimal128("9999999999999999999999999999999998"), + ] + }, + expected=True, + msg="34-digit decimal comparison", + ), +] + +ALL_TESTS = INT32_TESTS + INT64_TESTS + DOUBLE_PRECISION_TESTS + LARGE_NUMBER_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gt_boundary_precision(collection, test): + """Test $gt boundary values and precision.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py new file mode 100644 index 00000000..dc10836f --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py @@ -0,0 +1,132 @@ +""" +Representative BSON comparison engine wiring tests for $gt. + +A small sample of cross-type and special value comparisons to confirm $gt +delegates to the BSON comparison engine correctly. Not an exhaustive matrix — +full BSON ordering coverage lives in /core/data_types/bson_types/. +""" + +from datetime import datetime, timezone + +import pytest +from bson import Decimal128, Int64, MaxKey, MinKey + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import DOUBLE_NEGATIVE_ZERO, FLOAT_NAN + +BSON_WIRING_TESTS: list[ExpressionTestCase] = [ + # Cross-type ordering: number < string < object < array + ExpressionTestCase( + "string_gt_number", expression={"$gt": ["a", 1]}, expected=True, msg="string > number" + ), + ExpressionTestCase( + "number_not_gt_string", + expression={"$gt": [1, "a"]}, + expected=False, + msg="number not > string", + ), + ExpressionTestCase( + "object_gt_string", + expression={"$gt": [{"a": 1}, "a"]}, + expected=True, + msg="object > string", + ), + ExpressionTestCase( + "string_not_gt_object", + expression={"$gt": ["a", {"a": 1}]}, + expected=False, + msg="string not > object", + ), + ExpressionTestCase( + "array_gt_object", + expression={"$gt": [[1], {"a": 1}]}, + expected=True, + msg="array > object", + ), + ExpressionTestCase( + "object_not_gt_array", + expression={"$gt": [{"a": 1}, [1]]}, + expected=False, + msg="object not > array", + ), + # bool vs number (bool > number in BSON ordering) + ExpressionTestCase( + "bool_gt_number", + expression={"$gt": [True, 1]}, + expected=True, + msg="bool > number", + ), + ExpressionTestCase( + "number_not_gt_bool", + expression={"$gt": [1, True]}, + expected=False, + msg="number not > bool", + ), + # date vs string (date > string in BSON ordering) + ExpressionTestCase( + "date_gt_string", + expression={"$gt": [datetime(2024, 1, 1, tzinfo=timezone.utc), "z"]}, + expected=True, + msg="date > string", + ), + ExpressionTestCase( + "string_not_gt_date", + expression={"$gt": ["z", datetime(2024, 1, 1, tzinfo=timezone.utc)]}, + expected=False, + msg="string not > date", + ), + # MinKey / MaxKey extremes + ExpressionTestCase( + "maxkey_gt_number", expression={"$gt": [MaxKey(), 1]}, expected=True, msg="MaxKey > number" + ), + ExpressionTestCase( + "minkey_not_gt_number", + expression={"$gt": [MinKey(), 1]}, + expected=False, + msg="MinKey not > number", + ), + # Numeric equivalence across types + ExpressionTestCase( + "int_not_gt_equivalent_long", + expression={"$gt": [1, Int64(1)]}, + expected=False, + msg="int(1) not > long(1)", + ), + ExpressionTestCase( + "int_not_gt_equivalent_decimal", + expression={"$gt": [1, Decimal128("1")]}, + expected=False, + msg="int(1) not > decimal(1)", + ), + # Negative zero == positive zero + ExpressionTestCase( + "zero_not_gt_neg_zero", + expression={"$gt": [0.0, DOUBLE_NEGATIVE_ZERO]}, + expected=False, + msg="0.0 not > -0.0", + ), + # NaN ordering + ExpressionTestCase( + "nan_not_gt_nan", + expression={"$gt": [FLOAT_NAN, FLOAT_NAN]}, + expected=False, + msg="NaN not > NaN", + ), + ExpressionTestCase( + "zero_gt_nan", expression={"$gt": [0, FLOAT_NAN]}, expected=True, msg="0 > NaN" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(BSON_WIRING_TESTS)) +def test_gt_bson_wiring(collection, test): + """Smoke test: confirm $gt is wired to the BSON comparison engine.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_expression_types.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_expression_types.py new file mode 100644 index 00000000..875cea7c --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_expression_types.py @@ -0,0 +1,139 @@ +""" +Tests for $gt expression type smoke tests. + +Covers literal, field reference, expression operator, array expression, +object expression, composite array, and basic behavior with documents. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +LITERAL_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "literal_true", expression={"$gt": [5, 3]}, expected=True, msg="Literal 5 > 3" + ), + ExpressionTestCase( + "literal_false", + expression={"$gt": ["hello", "world"]}, + expected=False, + msg="hello not > world", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LITERAL_TESTS)) +def test_gt_expression_types_literal(collection, test): + """Test $gt with literal expression inputs.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +FIELD_REFERENCE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "field_ref_true", + expression={"$gt": ["$a", "$b"]}, + doc={"a": 10, "b": 5}, + expected=True, + msg="Field a > field b", + ), + ExpressionTestCase( + "field_ref_false", + expression={"$gt": ["$a", "$b"]}, + doc={"a": 5, "b": 10}, + expected=False, + msg="Field a not > field b", + ), + ExpressionTestCase( + "field_ref_string", + expression={"$gt": ["$x", "ABC"]}, + doc={"x": "abc"}, + expected=True, + msg="Field path 'abc' > 'ABC'", + ), +] + +EXPRESSION_OPERATOR_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "expr_operator", + expression={"$gt": [{"$abs": "$a"}, "$b"]}, + doc={"a": -5, "b": 3}, + expected=True, + msg="abs(-5)=5 > 3", + ), + ExpressionTestCase( + "expr_add", + expression={"$gt": [{"$add": ["$a", "$b"]}, "$c"]}, + doc={"a": 3, "b": 4, "c": 6}, + expected=True, + msg="add(3,4)=7 > 6", + ), + ExpressionTestCase( + "expr_subtract", + expression={"$gt": [{"$subtract": ["$a", "$b"]}, "$c"]}, + doc={"a": 10, "b": 3, "c": 6}, + expected=True, + msg="subtract(10,3)=7 > 6", + ), +] + +ARRAY_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_expr", + expression={"$gt": ["$a", [1, 1]]}, + doc={"a": [1, 2]}, + expected=True, + msg="Array expression [1,2] > [1,1]", + ), +] + +OBJECT_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "object_expr", + expression={"$gt": ["$a", {"x": 1}]}, + doc={"a": {"x": 2}}, + expected=True, + msg="Object expression {x:2} > {x:1}", + ), +] + +COMPOSITE_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "composite_array", + expression={"$gt": ["$a.b", [1]]}, + doc={"a": [{"b": 1}, {"b": 2}]}, + expected=True, + msg="Composite array $a.b [1,2] > [1]", + ), + ExpressionTestCase( + "composite_missing_path", + expression={"$gt": ["$a.b.c.x", None]}, + doc={"a": {"b": {"c": {"d": 1}}}}, + expected=False, + msg="Missing composite path not > null", + ), +] + + +ALL_INSERT_TESTS = ( + FIELD_REFERENCE_TESTS + + EXPRESSION_OPERATOR_TESTS + + ARRAY_EXPRESSION_TESTS + + OBJECT_EXPRESSION_TESTS + + COMPOSITE_ARRAY_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_INSERT_TESTS)) +def test_gt_expression_types_insert(collection, test): + """Test $gt with field reference and expression inputs requiring documents.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_field_lookup.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_field_lookup.py new file mode 100644 index 00000000..06fe1693 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_field_lookup.py @@ -0,0 +1,76 @@ +""" +Tests for $gt field lookup variations. + +Covers simple, nested, and non-existent field paths. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +SIMPLE_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "simple_field", + expression={"$gt": ["$a", 5]}, + doc={"a": 10}, + expected=True, + msg="Simple field 10 > 5", + ), + ExpressionTestCase( + "nonexistent_field", + expression={"$gt": ["$a", 5]}, + doc={"x": 1}, + expected=False, + msg="Missing field not > 5", + ), +] + +NESTED_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_field", + expression={"$gt": ["$a.b", 5]}, + doc={"a": {"b": 10}}, + expected=True, + msg="Nested field a.b=10 > 5", + ), + ExpressionTestCase( + "deeply_nested_field", + expression={"$gt": ["$a.b.c.d", 5]}, + doc={"a": {"b": {"c": {"d": 10}}}}, + expected=True, + msg="Deeply nested a.b.c.d=10 > 5", + ), + ExpressionTestCase( + "deeply_nested_self", + expression={"$gt": ["$a.b.c.d", "$a.b.c.d"]}, + doc={"a": {"b": {"c": {"d": 10}}}}, + expected=False, + msg="Same deeply nested field not > itself", + ), +] + +ARRAY_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_field_vs_array", + expression={"$gt": ["$a", [1, 2]]}, + doc={"a": [1, 2, 3]}, + expected=True, + msg="[1,2,3] > [1,2]", + ), +] + +ALL_TESTS = SIMPLE_FIELD_TESTS + NESTED_FIELD_TESTS + ARRAY_FIELD_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gt_field_lookup(collection, test): + """Test $gt field lookup variations.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_nan_infinity.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_nan_infinity.py new file mode 100644 index 00000000..0bac7d87 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_nan_infinity.py @@ -0,0 +1,98 @@ +""" +Tests for $gt NaN and Infinity handling. + +Covers Infinity comparisons. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DOUBLE_NEAR_MAX, + FLOAT_INFINITY, + FLOAT_NAN, + FLOAT_NEGATIVE_INFINITY, +) + +INF_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "inf_gt_1", expression={"$gt": [FLOAT_INFINITY, 1]}, expected=True, msg="Inf > 1" + ), + ExpressionTestCase( + "1_gt_inf", expression={"$gt": [1, FLOAT_INFINITY]}, expected=False, msg="1 not > Inf" + ), + ExpressionTestCase( + "neg_inf_gt_1", + expression={"$gt": [FLOAT_NEGATIVE_INFINITY, 1]}, + expected=False, + msg="-Inf not > 1", + ), + ExpressionTestCase( + "1_gt_neg_inf", + expression={"$gt": [1, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="1 > -Inf", + ), + ExpressionTestCase( + "inf_gt_neg_inf", + expression={"$gt": [FLOAT_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="Inf > -Inf", + ), + ExpressionTestCase( + "neg_inf_gt_inf", + expression={"$gt": [FLOAT_NEGATIVE_INFINITY, FLOAT_INFINITY]}, + expected=False, + msg="-Inf not > Inf", + ), + ExpressionTestCase( + "inf_self", + expression={"$gt": [FLOAT_INFINITY, FLOAT_INFINITY]}, + expected=False, + msg="Inf not > Inf", + ), + ExpressionTestCase( + "neg_inf_self", + expression={"$gt": [FLOAT_NEGATIVE_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="-Inf not > -Inf", + ), + ExpressionTestCase( + "inf_gt_near_max", + expression={"$gt": [FLOAT_INFINITY, DOUBLE_NEAR_MAX]}, + expected=True, + msg="Inf > DOUBLE_NEAR_MAX", + ), +] + + +NAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nan_gt_int", expression={"$gt": [FLOAT_NAN, 1]}, expected=False, msg="NaN not > int" + ), + ExpressionTestCase( + "int_gt_nan", expression={"$gt": [1, FLOAT_NAN]}, expected=True, msg="int > NaN" + ), + ExpressionTestCase( + "nan_gt_inf", + expression={"$gt": [FLOAT_NAN, FLOAT_INFINITY]}, + expected=False, + msg="NaN not > Infinity", + ), +] + +ALL_TESTS = INF_TESTS + NAN_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gt_nan_infinity(collection, test): + """Test $gt NaN and Infinity handling.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_null_missing.py new file mode 100644 index 00000000..fcc90850 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_null_missing.py @@ -0,0 +1,93 @@ +""" +Tests for $gt null and missing field handling. + +Covers null propagation, missing field behavior, and null/missing equivalence. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +NULL_LITERAL_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_null", expression={"$gt": [None, None]}, expected=False, msg="null not > null" + ), + ExpressionTestCase( + "null_int", expression={"$gt": [None, 1]}, expected=False, msg="null not > int" + ), + ExpressionTestCase("int_null", expression={"$gt": [1, None]}, expected=True, msg="int > null"), +] + + +@pytest.mark.parametrize("test", pytest_params(NULL_LITERAL_TESTS)) +def test_gt_null_literals(collection, test): + """Test $gt with null literal values.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +MISSING_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "missing_pos1", + expression={"$gt": ["$a", "$b"]}, + doc={"b": 5}, + expected=False, + msg="missing in position 1 → null not > 5", + ), + ExpressionTestCase( + "missing_pos2", + expression={"$gt": ["$a", "$b"]}, + doc={"a": 5}, + expected=True, + msg="missing in position 2 → 5 > null", + ), + ExpressionTestCase( + "both_missing", + expression={"$gt": ["$a", "$b"]}, + doc={}, + expected=False, + msg="both missing → null not > null", + ), +] + +NULL_MISSING_EQUIV_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_field_vs_missing", + expression={"$gt": ["$a", "$nonexistent"]}, + doc={"a": None}, + expected=True, + msg="null field > missing (null vs missing differs in $gt)", + ), + ExpressionTestCase( + "missing_vs_null_literal", + expression={"$gt": ["$nonexistent", None]}, + doc={}, + expected=False, + msg="missing not > null literal", + ), + ExpressionTestCase( + "null_vs_null_fields", + expression={"$gt": ["$a", "$b"]}, + doc={"a": None, "b": None}, + expected=False, + msg="null field vs null field", + ), +] + + +ALL_DOC_TESTS = MISSING_FIELD_TESTS + NULL_MISSING_EQUIV_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_DOC_TESTS)) +def test_gt_null_missing(collection, test): + """Test $gt with missing fields and null/missing equivalence.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_same_type_comparisons.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_same_type_comparisons.py new file mode 100644 index 00000000..052f7a4c --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gt/test_gt_same_type_comparisons.py @@ -0,0 +1,321 @@ +""" +Tests for $gt same-type comparisons and within-type ordering. + +Covers string, object, array, date, and boolean comparisons. +""" + +from datetime import datetime + +import pytest +from bson import Binary, 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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params + +CORE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("greater", expression={"$gt": [300, 250]}, expected=True, msg="300 > 250"), + ExpressionTestCase("less", expression={"$gt": [200, 250]}, expected=False, msg="200 not > 250"), + ExpressionTestCase( + "equal", expression={"$gt": [250, 250]}, expected=False, msg="250 not > 250" + ), +] + +STRING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "banana_gt_apple", + expression={"$gt": ["banana", "apple"]}, + expected=True, + msg="banana > apple", + ), + ExpressionTestCase( + "apple_gt_banana", + expression={"$gt": ["apple", "banana"]}, + expected=False, + msg="apple not > banana", + ), + ExpressionTestCase( + "upper_A_gt_lower_a", + expression={"$gt": ["Apple", "apple"]}, + expected=False, + msg="uppercase < lowercase", + ), + ExpressionTestCase( + "lower_a_gt_upper_A", + expression={"$gt": ["apple", "Apple"]}, + expected=True, + msg="lowercase > uppercase", + ), + ExpressionTestCase("a_gt_A", expression={"$gt": ["a", "A"]}, expected=True, msg="a > A"), + ExpressionTestCase( + "empty_gt_a", expression={"$gt": ["", "a"]}, expected=False, msg="empty not > a" + ), + ExpressionTestCase("a_gt_empty", expression={"$gt": ["a", ""]}, expected=True, msg="a > empty"), + ExpressionTestCase( + "empty_gt_empty", expression={"$gt": ["", ""]}, expected=False, msg="empty not > empty" + ), + ExpressionTestCase( + "abc_gt_ab", expression={"$gt": ["abc", "ab"]}, expected=True, msg="longer prefix wins" + ), + ExpressionTestCase( + "abd_gt_abc", expression={"$gt": ["abd", "abc"]}, expected=True, msg="last char comparison" + ), + ExpressionTestCase( + "abc_gt_abd", expression={"$gt": ["abc", "abd"]}, expected=False, msg="abc not > abd" + ), + ExpressionTestCase("z_gt_Z", expression={"$gt": ["z", "Z"]}, expected=True, msg="z > Z"), + ExpressionTestCase( + "digit_0_gt_9", expression={"$gt": ["0", "9"]}, expected=False, msg="0 not > 9" + ), + ExpressionTestCase("digit_9_gt_0", expression={"$gt": ["9", "0"]}, expected=True, msg="9 > 0"), + ExpressionTestCase( + "space_gt_empty", expression={"$gt": [" ", ""]}, expected=True, msg="space > empty" + ), +] + +OBJECT_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "obj_a2_gt_a1", expression={"$gt": [{"a": 2}, {"a": 1}]}, expected=True, msg="{a:2} > {a:1}" + ), + ExpressionTestCase( + "obj_a1_gt_a2", + expression={"$gt": [{"a": 1}, {"a": 2}]}, + expected=False, + msg="{a:1} not > {a:2}", + ), + ExpressionTestCase( + "obj_ab_gt_a", + expression={"$gt": [{"a": 1, "b": 1}, {"a": 1}]}, + expected=True, + msg="more fields > fewer", + ), + ExpressionTestCase( + "obj_a_gt_ab", + expression={"$gt": [{"a": 1}, {"a": 1, "b": 1}]}, + expected=False, + msg="fewer not > more", + ), + ExpressionTestCase( + "obj_b_gt_a", + expression={"$gt": [{"b": 1}, {"a": 1}]}, + expected=True, + msg="field b > field a", + ), + ExpressionTestCase( + "obj_a_gt_b", + expression={"$gt": [{"a": 1}, {"b": 1}]}, + expected=False, + msg="field a not > field b", + ), + ExpressionTestCase( + "empty_obj_self", expression={"$gt": [{}, {}]}, expected=False, msg="{} not > {}" + ), + ExpressionTestCase( + "obj_gt_empty", expression={"$gt": [{"a": 1}, {}]}, expected=True, msg="{a:1} > {}" + ), + ExpressionTestCase( + "empty_gt_obj", expression={"$gt": [{}, {"a": 1}]}, expected=False, msg="{} not > {a:1}" + ), +] + +ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "arr_2_gt_1", expression={"$gt": [[2], [1]]}, expected=True, msg="[2] > [1]" + ), + ExpressionTestCase( + "arr_1_gt_2", expression={"$gt": [[1], [2]]}, expected=False, msg="[1] not > [2]" + ), + ExpressionTestCase( + "arr_12_gt_1", expression={"$gt": [[1, 2], [1]]}, expected=True, msg="[1,2] > [1]" + ), + ExpressionTestCase( + "arr_1_gt_12", expression={"$gt": [[1], [1, 2]]}, expected=False, msg="[1] not > [1,2]" + ), + ExpressionTestCase( + "arr_12_self", expression={"$gt": [[1, 2], [1, 2]]}, expected=False, msg="[1,2] not > [1,2]" + ), + ExpressionTestCase( + "arr_2_gt_1_999", + expression={"$gt": [[2], [1, 999]]}, + expected=True, + msg="first element wins", + ), + ExpressionTestCase( + "empty_arr_self", expression={"$gt": [[], []]}, expected=False, msg="[] not > []" + ), + ExpressionTestCase( + "arr_1_gt_empty", expression={"$gt": [[1], []]}, expected=True, msg="[1] > []" + ), + ExpressionTestCase( + "empty_gt_arr_1", expression={"$gt": [[], [1]]}, expected=False, msg="[] not > [1]" + ), + ExpressionTestCase( + "arr_null_gt_empty", expression={"$gt": [[None], []]}, expected=True, msg="[null] > []" + ), +] + +NESTED_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_1_gt_0", expression={"$gt": [[[1]], [[0]]]}, expected=True, msg="[[1]] > [[0]]" + ), + ExpressionTestCase( + "null_1_gt_null_0", + expression={"$gt": [[None, 1], [None, 0]]}, + expected=True, + msg="second element comparison", + ), + ExpressionTestCase( + "arr_1_null_gt_1", + expression={"$gt": [[1, None], [1]]}, + expected=True, + msg="longer with same prefix", + ), +] + +DATE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "later_date", + expression={"$gt": [datetime(2025, 6, 1), datetime(2025, 1, 1)]}, + expected=True, + msg="later date > earlier", + ), + ExpressionTestCase( + "earlier_date", + expression={"$gt": [datetime(2025, 1, 1), datetime(2025, 6, 1)]}, + expected=False, + msg="earlier not > later", + ), + ExpressionTestCase( + "same_date", + expression={"$gt": [datetime(2025, 1, 1), datetime(2025, 1, 1)]}, + expected=False, + msg="same date not > same date", + ), + ExpressionTestCase( + "millisecond_precision", + expression={"$gt": [datetime(2025, 1, 1, 0, 0, 0, 1000), datetime(2025, 1, 1)]}, + expected=True, + msg="1ms later > earlier", + ), +] + +BOOLEAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "true_gt_false", expression={"$gt": [True, False]}, expected=True, msg="true > false" + ), + ExpressionTestCase( + "false_gt_true", expression={"$gt": [False, True]}, expected=False, msg="false not > true" + ), + ExpressionTestCase( + "true_self", expression={"$gt": [True, True]}, expected=False, msg="true not > true" + ), + ExpressionTestCase( + "false_self", expression={"$gt": [False, False]}, expected=False, msg="false not > false" + ), +] + +OBJECTID_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "objectid_gt", + expression={ + "$gt": [ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa"), ObjectId("000000000000000000000000")] + }, + expected=True, + msg="higher ObjectId > lower ObjectId", + ), +] + +BINDATA_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "bindata_longer_gt_shorter", + expression={"$gt": [Binary(b"\x01\x02\x03", 0), Binary(b"\x01\x02", 0)]}, + expected=True, + msg="longer BinData > shorter BinData", + ), + ExpressionTestCase( + "bindata_higher_bytes", + expression={"$gt": [Binary(b"\x02", 0), Binary(b"\x01", 0)]}, + expected=True, + msg="same length, higher bytes > lower bytes", + ), + ExpressionTestCase( + "bindata_equal", + expression={"$gt": [Binary(b"\x01", 0), Binary(b"\x01", 0)]}, + expected=False, + msg="equal BinData not > equal BinData", + ), + ExpressionTestCase( + "bindata_diff_subtype", + expression={"$gt": [Binary(b"\x01", 5), Binary(b"\x01", 0)]}, + expected=True, + msg="higher subtype > lower subtype (same data)", + ), +] + +TIMESTAMP_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "ts_lower_seconds", + expression={"$gt": [Timestamp(50, 1), Timestamp(100, 1)]}, + expected=False, + msg="lower seconds not > higher seconds", + ), + ExpressionTestCase( + "ts_equal", + expression={"$gt": [Timestamp(100, 1), Timestamp(100, 1)]}, + expected=False, + msg="equal timestamp not > equal timestamp", + ), + ExpressionTestCase( + "ts_same_seconds_higher_ordinal", + expression={"$gt": [Timestamp(100, 2), Timestamp(100, 1)]}, + expected=True, + msg="same seconds, higher ordinal > lower ordinal", + ), +] + +REGEX_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "regex_higher_pattern", + expression={"$gt": [Regex("def"), Regex("abc")]}, + expected=True, + msg="higher pattern > lower pattern", + ), + ExpressionTestCase( + "regex_equal", + expression={"$gt": [Regex("abc"), Regex("abc")]}, + expected=False, + msg="same regex not > same regex", + ), + ExpressionTestCase( + "regex_flags_gt_no_flags", + expression={"$gt": [Regex("abc", "i"), Regex("abc")]}, + expected=True, + msg="regex with flags > without flags", + ), +] + +ALL_TESTS = ( + CORE_TESTS + + STRING_TESTS + + OBJECT_TESTS + + ARRAY_TESTS + + NESTED_ARRAY_TESTS + + DATE_TESTS + + BOOLEAN_TESTS + + OBJECTID_TESTS + + BINDATA_TESTS + + TIMESTAMP_TESTS + + REGEX_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gt_same_type_comparisons(collection, test): + """Test $gt same-type comparisons and within-type ordering.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_argument_handling.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_argument_handling.py new file mode 100644 index 00000000..02d28b2e --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_argument_handling.py @@ -0,0 +1,77 @@ +""" +Tests for $gte argument handling. + +Covers argument count variations and error cases. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.error_codes import EXPRESSION_TYPE_MISMATCH_ERROR +from documentdb_tests.framework.parametrize import pytest_params + +GTE_ARG_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "no_args", + expression={"$gte": []}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for empty arguments", + ), + ExpressionTestCase( + "single_arg", + expression={"$gte": [1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for single argument", + ), + ExpressionTestCase( + "non_array_arg", + expression={"$gte": 1}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array argument", + ), + ExpressionTestCase( + "non_array_string", + expression={"$gte": "string"}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for string argument", + ), + ExpressionTestCase( + "three_args", + expression={"$gte": [3, 2, 1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for three arguments", + ), + ExpressionTestCase( + "two_args_gt", + expression={"$gte": [2, 1]}, + expected=True, + msg="Should return true when first > second", + ), + ExpressionTestCase( + "two_args_eq", + expression={"$gte": [1, 1]}, + expected=True, + msg="Should return true when equal", + ), + ExpressionTestCase( + "two_args_lt", + expression={"$gte": [1, 2]}, + expected=False, + msg="Should return false when first < second", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(GTE_ARG_TESTS)) +def test_gte_argument_handling(collection, test): + """Test $gte argument count variations.""" + result = execute_expression(collection, test.expression) + assert_expression_result( + result, expected=test.expected, error_code=test.error_code, msg=test.msg + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_boundary_precision.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_boundary_precision.py new file mode 100644 index 00000000..6f32e2ea --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_boundary_precision.py @@ -0,0 +1,190 @@ +""" +Tests for $gte numeric boundaries and double precision. + +Covers INT32/INT64 boundaries, double subnormals, and large number precision edge cases. +""" + +import pytest +from bson import Decimal128, 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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DOUBLE_MIN_NEGATIVE_SUBNORMAL, + DOUBLE_MIN_SUBNORMAL, + DOUBLE_NEAR_MAX, + DOUBLE_NEAR_MIN, + INT32_MAX, + INT32_MIN, + INT64_MAX, + INT64_MIN, +) + +INT32_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int32_max_gte_max_minus1", + expression={"$gte": [INT32_MAX, INT32_MAX - 1]}, + expected=True, + msg="INT32_MAX >= INT32_MAX-1", + ), + ExpressionTestCase( + "int32_max_minus1_not_gte_max", + expression={"$gte": [INT32_MAX - 1, INT32_MAX]}, + expected=False, + msg="INT32_MAX-1 not >= INT32_MAX", + ), + ExpressionTestCase( + "int32_max_self", + expression={"$gte": [INT32_MAX, INT32_MAX]}, + expected=True, + msg="INT32_MAX >= INT32_MAX", + ), + ExpressionTestCase( + "int32_min_plus1_gte_min", + expression={"$gte": [INT32_MIN + 1, INT32_MIN]}, + expected=True, + msg="INT32_MIN+1 >= INT32_MIN", + ), + ExpressionTestCase( + "int32_min_self", + expression={"$gte": [INT32_MIN, INT32_MIN]}, + expected=True, + msg="INT32_MIN >= INT32_MIN", + ), + ExpressionTestCase( + "long_above_int32_gte_int32_max", + expression={"$gte": [Int64(INT32_MAX + 1), INT32_MAX]}, + expected=True, + msg="long(INT32_MAX+1) >= int(INT32_MAX)", + ), + ExpressionTestCase( + "int32_max_not_gte_long_above", + expression={"$gte": [INT32_MAX, Int64(INT32_MAX + 1)]}, + expected=False, + msg="int(INT32_MAX) not >= long(INT32_MAX+1)", + ), + ExpressionTestCase( + "long_below_int32_gte_int32_min", + expression={"$gte": [Int64(INT32_MIN - 1), INT32_MIN]}, + expected=False, + msg="long(INT32_MIN-1) not >= int(INT32_MIN)", + ), +] + +INT64_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_max_gte_max_minus1", + expression={"$gte": [INT64_MAX, Int64(INT64_MAX - 1)]}, + expected=True, + msg="INT64_MAX >= INT64_MAX-1", + ), + ExpressionTestCase( + "int64_max_self", + expression={"$gte": [INT64_MAX, INT64_MAX]}, + expected=True, + msg="INT64_MAX >= INT64_MAX", + ), + ExpressionTestCase( + "int64_min_plus1_gte_min", + expression={"$gte": [Int64(INT64_MIN + 1), INT64_MIN]}, + expected=True, + msg="INT64_MIN+1 >= INT64_MIN", + ), + ExpressionTestCase( + "int64_min_self", + expression={"$gte": [INT64_MIN, INT64_MIN]}, + expected=True, + msg="INT64_MIN >= INT64_MIN", + ), +] + +DOUBLE_PRECISION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "near_max_self", + expression={"$gte": [DOUBLE_NEAR_MAX, DOUBLE_NEAR_MAX]}, + expected=True, + msg="DOUBLE_NEAR_MAX >= itself", + ), + ExpressionTestCase( + "subnormal_gte_zero", + expression={"$gte": [DOUBLE_MIN_SUBNORMAL, 0.0]}, + expected=True, + msg="min subnormal >= 0.0", + ), + ExpressionTestCase( + "zero_gte_subnormal", + expression={"$gte": [0.0, DOUBLE_MIN_SUBNORMAL]}, + expected=False, + msg="0.0 not >= min subnormal", + ), + ExpressionTestCase( + "neg_subnormal_gte_zero", + expression={"$gte": [DOUBLE_MIN_NEGATIVE_SUBNORMAL, 0.0]}, + expected=False, + msg="neg subnormal not >= 0.0", + ), + ExpressionTestCase( + "zero_gte_neg_subnormal", + expression={"$gte": [0.0, DOUBLE_MIN_NEGATIVE_SUBNORMAL]}, + expected=True, + msg="0.0 >= neg subnormal", + ), + ExpressionTestCase( + "near_min_gte_neg_subnormal", + expression={"$gte": [DOUBLE_NEAR_MIN, DOUBLE_MIN_NEGATIVE_SUBNORMAL]}, + expected=True, + msg="DOUBLE_NEAR_MIN >= neg subnormal", + ), +] + +LARGE_NUMBER_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_same", + expression={"$gte": [Int64(9007199254740993), Int64(9007199254740993)]}, + expected=True, + msg="int64 beyond double precision self", + ), + ExpressionTestCase( + "int64_beyond_double_precision", + expression={"$gte": [Int64(9007199254740993), Int64(9007199254740992)]}, + expected=True, + msg="int64 beyond double precision", + ), + ExpressionTestCase( + "dec_34_digit_self", + expression={ + "$gte": [ + Decimal128("9999999999999999999999999999999999"), + Decimal128("9999999999999999999999999999999999"), + ] + }, + expected=True, + msg="34-digit decimal self", + ), + ExpressionTestCase( + "dec_34_digit", + expression={ + "$gte": [ + Decimal128("9999999999999999999999999999999999"), + Decimal128("9999999999999999999999999999999998"), + ] + }, + expected=True, + msg="34-digit decimal comparison", + ), +] + +ALL_TESTS = INT32_TESTS + INT64_TESTS + DOUBLE_PRECISION_TESTS + LARGE_NUMBER_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gte_boundary_precision(collection, test): + """Test $gte boundary values and precision.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_bson_wiring.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_bson_wiring.py new file mode 100644 index 00000000..18343bca --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_bson_wiring.py @@ -0,0 +1,135 @@ +""" +Representative BSON comparison engine wiring tests for $gte. + +A small sample of cross-type and special value comparisons to confirm $gte +delegates to the BSON comparison engine correctly. Not an exhaustive matrix — +full BSON ordering coverage lives in /core/data_types/bson_types/. +""" + +from datetime import datetime, timezone + +import pytest +from bson import Decimal128, Int64, MaxKey, MinKey + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import DOUBLE_NEGATIVE_ZERO, FLOAT_NAN + +BSON_WIRING_TESTS: list[ExpressionTestCase] = [ + # Cross-type ordering: number < string < object < array + ExpressionTestCase( + "string_gte_number", expression={"$gte": ["a", 1]}, expected=True, msg="string >= number" + ), + ExpressionTestCase( + "number_not_gte_string", + expression={"$gte": [1, "a"]}, + expected=False, + msg="number not >= string", + ), + ExpressionTestCase( + "object_gte_string", + expression={"$gte": [{"a": 1}, "a"]}, + expected=True, + msg="object >= string", + ), + ExpressionTestCase( + "string_not_gte_object", + expression={"$gte": ["a", {"a": 1}]}, + expected=False, + msg="string not >= object", + ), + ExpressionTestCase( + "array_gte_object", + expression={"$gte": [[1], {"a": 1}]}, + expected=True, + msg="array >= object", + ), + ExpressionTestCase( + "object_not_gte_array", + expression={"$gte": [{"a": 1}, [1]]}, + expected=False, + msg="object not >= array", + ), + # bool vs number (bool > number in BSON ordering) + ExpressionTestCase( + "bool_gte_number", + expression={"$gte": [True, 1]}, + expected=True, + msg="bool >= number", + ), + ExpressionTestCase( + "number_not_gte_bool", + expression={"$gte": [1, True]}, + expected=False, + msg="number not >= bool", + ), + # date vs string (date > string in BSON ordering) + ExpressionTestCase( + "date_gte_string", + expression={"$gte": [datetime(2024, 1, 1, tzinfo=timezone.utc), "z"]}, + expected=True, + msg="date >= string", + ), + ExpressionTestCase( + "string_not_gte_date", + expression={"$gte": ["z", datetime(2024, 1, 1, tzinfo=timezone.utc)]}, + expected=False, + msg="string not >= date", + ), + # MinKey / MaxKey extremes + ExpressionTestCase( + "maxkey_gte_number", + expression={"$gte": [MaxKey(), 1]}, + expected=True, + msg="MaxKey >= number", + ), + ExpressionTestCase( + "minkey_not_gte_number", + expression={"$gte": [MinKey(), 1]}, + expected=False, + msg="MinKey not >= number", + ), + # Numeric equivalence across types + ExpressionTestCase( + "int_gte_equivalent_long", + expression={"$gte": [1, Int64(1)]}, + expected=True, + msg="int(1) >= long(1)", + ), + ExpressionTestCase( + "int_gte_equivalent_decimal", + expression={"$gte": [1, Decimal128("1")]}, + expected=True, + msg="int(1) >= decimal(1)", + ), + # Negative zero == positive zero + ExpressionTestCase( + "zero_gte_neg_zero", + expression={"$gte": [0.0, DOUBLE_NEGATIVE_ZERO]}, + expected=True, + msg="0.0 >= -0.0", + ), + # NaN ordering + ExpressionTestCase( + "nan_gte_nan", + expression={"$gte": [FLOAT_NAN, FLOAT_NAN]}, + expected=True, + msg="NaN >= NaN (equal)", + ), + ExpressionTestCase( + "zero_gte_nan", expression={"$gte": [0, FLOAT_NAN]}, expected=True, msg="0 >= NaN" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(BSON_WIRING_TESTS)) +def test_gte_bson_wiring(collection, test): + """Smoke test: confirm $gte is wired to the BSON comparison engine.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_expression_types.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_expression_types.py new file mode 100644 index 00000000..f88f483d --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_expression_types.py @@ -0,0 +1,148 @@ +""" +Tests for $gte expression type smoke tests. + +Covers literal, field reference, expression operator, array expression, +object expression, composite array, and basic behavior with documents. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +LITERAL_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "literal_true", expression={"$gte": [5, 3]}, expected=True, msg="Literal 5 >= 3" + ), + ExpressionTestCase( + "literal_equal", expression={"$gte": [5, 5]}, expected=True, msg="Literal 5 >= 5" + ), + ExpressionTestCase( + "literal_false", + expression={"$gte": ["hello", "world"]}, + expected=False, + msg="hello not >= world", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LITERAL_TESTS)) +def test_gte_expression_types_literal(collection, test): + """Test $gte with literal expression inputs.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +FIELD_REFERENCE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "field_ref_true", + expression={"$gte": ["$a", "$b"]}, + doc={"a": 10, "b": 5}, + expected=True, + msg="Field a >= field b", + ), + ExpressionTestCase( + "field_ref_equal", + expression={"$gte": ["$a", "$b"]}, + doc={"a": 5, "b": 5}, + expected=True, + msg="Field a >= field b (equal)", + ), + ExpressionTestCase( + "field_ref_false", + expression={"$gte": ["$a", "$b"]}, + doc={"a": 5, "b": 10}, + expected=False, + msg="Field a not >= field b", + ), + ExpressionTestCase( + "field_ref_string", + expression={"$gte": ["$x", "ABC"]}, + doc={"x": "abc"}, + expected=True, + msg="Field path 'abc' >= 'ABC'", + ), +] + +EXPRESSION_OPERATOR_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "expr_operator", + expression={"$gte": [{"$abs": "$a"}, "$b"]}, + doc={"a": -5, "b": 3}, + expected=True, + msg="abs(-5)=5 >= 3", + ), + ExpressionTestCase( + "expr_add", + expression={"$gte": [{"$add": ["$a", "$b"]}, "$c"]}, + doc={"a": 3, "b": 4, "c": 6}, + expected=True, + msg="add(3,4)=7 >= 6", + ), + ExpressionTestCase( + "expr_subtract", + expression={"$gte": [{"$subtract": ["$a", "$b"]}, "$c"]}, + doc={"a": 10, "b": 3, "c": 6}, + expected=True, + msg="subtract(10,3)=7 >= 6", + ), +] + +ARRAY_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_expr", + expression={"$gte": ["$a", [1, 2]]}, + doc={"a": [1, 2]}, + expected=True, + msg="Array expression [1,2] >= [1,2] (equal)", + ), +] + +OBJECT_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "object_expr", + expression={"$gte": ["$a", {"x": 1}]}, + doc={"a": {"x": 1}}, + expected=True, + msg="Object expression {x:1} >= {x:1} (equal)", + ), +] + +COMPOSITE_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "composite_array", + expression={"$gte": ["$a.b", [1]]}, + doc={"a": [{"b": 1}, {"b": 2}]}, + expected=True, + msg="Composite array $a.b [1,2] >= [1]", + ), + ExpressionTestCase( + "composite_missing_path", + expression={"$gte": ["$a.b.c.x", None]}, + doc={"a": {"b": {"c": {"d": 1}}}}, + expected=False, + msg="Missing composite path not >= null", + ), +] + +ALL_INSERT_TESTS = ( + FIELD_REFERENCE_TESTS + + EXPRESSION_OPERATOR_TESTS + + ARRAY_EXPRESSION_TESTS + + OBJECT_EXPRESSION_TESTS + + COMPOSITE_ARRAY_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_INSERT_TESTS)) +def test_gte_expression_types_insert(collection, test): + """Test $gte with field reference and expression inputs requiring documents.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_field_lookup.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_field_lookup.py new file mode 100644 index 00000000..a2f5c2c4 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_field_lookup.py @@ -0,0 +1,100 @@ +""" +Tests for $gte field lookup variations. + +Covers simple, nested, and non-existent field paths. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +SIMPLE_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "simple_field", + expression={"$gte": ["$a", 5]}, + doc={"a": 10}, + expected=True, + msg="Simple field 10 >= 5", + ), + ExpressionTestCase( + "simple_field_equal", + expression={"$gte": ["$a", 10]}, + doc={"a": 10}, + expected=True, + msg="Simple field 10 >= 10 (equal)", + ), + ExpressionTestCase( + "nonexistent_field", + expression={"$gte": ["$a", 5]}, + doc={"x": 1}, + expected=False, + msg="Missing field not >= 5", + ), +] + +NESTED_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_field", + expression={"$gte": ["$a.b", 10]}, + doc={"a": {"b": 10}}, + expected=True, + msg="Nested field a.b=10 >= 10 (equal)", + ), + ExpressionTestCase( + "deeply_nested_field", + expression={"$gte": ["$a.b.c.d", 10]}, + doc={"a": {"b": {"c": {"d": 10}}}}, + expected=True, + msg="Deeply nested a.b.c.d=10 >= 10 (equal)", + ), + ExpressionTestCase( + "deeply_nested_self", + expression={"$gte": ["$a.b.c.d", "$a.b.c.d"]}, + doc={"a": {"b": {"c": {"d": 10}}}}, + expected=True, + msg="Same deeply nested field >= itself (equal)", + ), +] + +ARRAY_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_field_vs_array", + expression={"$gte": ["$a", [1, 2]]}, + doc={"a": [1, 2, 3]}, + expected=True, + msg="[1,2,3] >= [1,2]", + ), +] + +NULL_MISSING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_field_vs_missing", + expression={"$gte": ["$a", "$nonexistent"]}, + doc={"a": None}, + expected=True, + msg="null field >= missing (null == missing → equal → true)", + ), + ExpressionTestCase( + "missing_vs_null", + expression={"$gte": ["$nonexistent", None]}, + doc={}, + expected=False, + msg="missing field not >= null literal", + ), +] + +ALL_TESTS = SIMPLE_FIELD_TESTS + NESTED_FIELD_TESTS + ARRAY_FIELD_TESTS + NULL_MISSING_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gte_field_lookup(collection, test): + """Test $gte field lookup variations.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_nan_infinity.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_nan_infinity.py new file mode 100644 index 00000000..da37d800 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_nan_infinity.py @@ -0,0 +1,98 @@ +""" +Tests for $gte Infinity handling. + +Covers Infinity comparisons. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DOUBLE_NEAR_MAX, + FLOAT_INFINITY, + FLOAT_NAN, + FLOAT_NEGATIVE_INFINITY, +) + +INF_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "inf_gte_1", expression={"$gte": [FLOAT_INFINITY, 1]}, expected=True, msg="Inf >= 1" + ), + ExpressionTestCase( + "1_gte_inf", expression={"$gte": [1, FLOAT_INFINITY]}, expected=False, msg="1 not >= Inf" + ), + ExpressionTestCase( + "neg_inf_gte_1", + expression={"$gte": [FLOAT_NEGATIVE_INFINITY, 1]}, + expected=False, + msg="-Inf not >= 1", + ), + ExpressionTestCase( + "1_gte_neg_inf", + expression={"$gte": [1, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="1 >= -Inf", + ), + ExpressionTestCase( + "inf_gte_neg_inf", + expression={"$gte": [FLOAT_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="Inf >= -Inf", + ), + ExpressionTestCase( + "neg_inf_gte_inf", + expression={"$gte": [FLOAT_NEGATIVE_INFINITY, FLOAT_INFINITY]}, + expected=False, + msg="-Inf not >= Inf", + ), + ExpressionTestCase( + "inf_self", + expression={"$gte": [FLOAT_INFINITY, FLOAT_INFINITY]}, + expected=True, + msg="Inf >= Inf (equal)", + ), + ExpressionTestCase( + "neg_inf_self", + expression={"$gte": [FLOAT_NEGATIVE_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="-Inf >= -Inf (equal)", + ), + ExpressionTestCase( + "inf_gte_near_max", + expression={"$gte": [FLOAT_INFINITY, DOUBLE_NEAR_MAX]}, + expected=True, + msg="Inf >= DOUBLE_NEAR_MAX", + ), +] + + +NAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nan_gte_int", expression={"$gte": [FLOAT_NAN, 1]}, expected=False, msg="NaN not >= int" + ), + ExpressionTestCase( + "int_gte_nan", expression={"$gte": [1, FLOAT_NAN]}, expected=True, msg="int >= NaN" + ), + ExpressionTestCase( + "nan_gte_inf", + expression={"$gte": [FLOAT_NAN, FLOAT_INFINITY]}, + expected=False, + msg="NaN not >= Infinity", + ), +] + +ALL_TESTS = INF_TESTS + NAN_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gte_nan_infinity(collection, test): + """Test $gte Infinity handling.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_null_missing.py new file mode 100644 index 00000000..22a7745f --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_null_missing.py @@ -0,0 +1,94 @@ +""" +Tests for $gte null and missing field handling. + +Covers null propagation, missing field behavior, and null/missing equivalence. +""" + +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 ( # noqa: E501 + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +NULL_LITERAL_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_null", expression={"$gte": [None, None]}, expected=True, msg="null >= null" + ), + ExpressionTestCase( + "null_int", expression={"$gte": [None, 1]}, expected=False, msg="null not >= int" + ), + ExpressionTestCase( + "int_null", expression={"$gte": [1, None]}, expected=True, msg="int >= null" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NULL_LITERAL_TESTS)) +def test_gte_null_literals(collection, test): + """Test $gte with null literal values.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +MISSING_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "missing_pos1", + expression={"$gte": ["$a", "$b"]}, + doc={"b": 5}, + expected=False, + msg="missing in position 1 → null not >= 5", + ), + ExpressionTestCase( + "missing_pos2", + expression={"$gte": ["$a", "$b"]}, + doc={"a": 5}, + expected=True, + msg="missing in position 2 → 5 >= null", + ), + ExpressionTestCase( + "both_missing", + expression={"$gte": ["$a", "$b"]}, + doc={}, + expected=True, + msg="both missing → null >= null (equal)", + ), +] + +NULL_MISSING_EQUIV_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_field_vs_missing", + expression={"$gte": ["$a", "$nonexistent"]}, + doc={"a": None}, + expected=True, + msg="null field >= missing (null == missing → equal → true)", + ), + ExpressionTestCase( + "missing_vs_null_literal", + expression={"$gte": ["$nonexistent", None]}, + doc={}, + expected=False, + msg="missing not >= null literal", + ), + ExpressionTestCase( + "null_vs_null_fields", + expression={"$gte": ["$a", "$b"]}, + doc={"a": None, "b": None}, + expected=True, + msg="null field >= null field (equal)", + ), +] + +ALL_TESTS = MISSING_FIELD_TESTS + NULL_MISSING_EQUIV_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gte_null_missing(collection, test): + """Test $gte null and missing field handling.""" + result = execute_expression_with_insert(collection, test.expression, test.doc) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_same_type_comparisons.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_same_type_comparisons.py new file mode 100644 index 00000000..7ef0f315 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/gte/test_gte_same_type_comparisons.py @@ -0,0 +1,374 @@ +""" +Tests for $gte same-type comparisons and within-type ordering. + +Covers string, object, array, date, and boolean comparisons, +and $gte vs $gt equality edge cases. +""" + +from datetime import datetime + +import pytest +from bson import Binary, 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 ( # noqa: E501 + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params + +CORE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("greater", expression={"$gte": [300, 250]}, expected=True, msg="300 >= 250"), + ExpressionTestCase( + "less", expression={"$gte": [200, 250]}, expected=False, msg="200 not >= 250" + ), + ExpressionTestCase( + "equal", + expression={"$gte": [250, 250]}, + expected=True, + msg="250 >= 250 (KEY: equal returns true)", + ), +] + +STRING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "banana_gte_apple", + expression={"$gte": ["banana", "apple"]}, + expected=True, + msg="banana >= apple", + ), + ExpressionTestCase( + "apple_gte_banana", + expression={"$gte": ["apple", "banana"]}, + expected=False, + msg="apple not >= banana", + ), + ExpressionTestCase( + "apple_self", expression={"$gte": ["apple", "apple"]}, expected=True, msg="apple >= apple" + ), + ExpressionTestCase( + "upper_A_gte_lower_a", + expression={"$gte": ["Apple", "apple"]}, + expected=False, + msg="uppercase < lowercase", + ), + ExpressionTestCase( + "lower_a_gte_upper_A", + expression={"$gte": ["apple", "Apple"]}, + expected=True, + msg="lowercase >= uppercase", + ), + ExpressionTestCase("a_gte_A", expression={"$gte": ["a", "A"]}, expected=True, msg="a >= A"), + ExpressionTestCase( + "empty_gte_a", expression={"$gte": ["", "a"]}, expected=False, msg="empty not >= a" + ), + ExpressionTestCase( + "a_gte_empty", expression={"$gte": ["a", ""]}, expected=True, msg="a >= empty" + ), + ExpressionTestCase( + "empty_self", expression={"$gte": ["", ""]}, expected=True, msg="empty >= empty" + ), + ExpressionTestCase( + "abc_gte_ab", expression={"$gte": ["abc", "ab"]}, expected=True, msg="longer prefix wins" + ), + ExpressionTestCase( + "ab_gte_abc", expression={"$gte": ["ab", "abc"]}, expected=False, msg="ab not >= abc" + ), + ExpressionTestCase( + "abd_gte_abc", + expression={"$gte": ["abd", "abc"]}, + expected=True, + msg="last char comparison", + ), + ExpressionTestCase( + "abc_gte_abd", expression={"$gte": ["abc", "abd"]}, expected=False, msg="abc not >= abd" + ), + ExpressionTestCase("z_gte_Z", expression={"$gte": ["z", "Z"]}, expected=True, msg="z >= Z"), + ExpressionTestCase( + "digit_0_gte_9", expression={"$gte": ["0", "9"]}, expected=False, msg="0 not >= 9" + ), + ExpressionTestCase( + "digit_9_gte_0", expression={"$gte": ["9", "0"]}, expected=True, msg="9 >= 0" + ), + ExpressionTestCase( + "space_gte_empty", expression={"$gte": [" ", ""]}, expected=True, msg="space >= empty" + ), +] + +OBJECT_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "obj_a2_gte_a1", + expression={"$gte": [{"a": 2}, {"a": 1}]}, + expected=True, + msg="{a:2} >= {a:1}", + ), + ExpressionTestCase( + "obj_a1_self", + expression={"$gte": [{"a": 1}, {"a": 1}]}, + expected=True, + msg="{a:1} >= {a:1}", + ), + ExpressionTestCase( + "obj_ab_gte_a", + expression={"$gte": [{"a": 1, "b": 1}, {"a": 1}]}, + expected=True, + msg="more fields >= fewer", + ), + ExpressionTestCase( + "obj_a_gte_ab", + expression={"$gte": [{"a": 1}, {"a": 1, "b": 1}]}, + expected=False, + msg="fewer not >= more", + ), + ExpressionTestCase( + "obj_b_gte_a", + expression={"$gte": [{"b": 1}, {"a": 1}]}, + expected=True, + msg="field b >= field a", + ), + ExpressionTestCase( + "obj_a_gte_b", + expression={"$gte": [{"a": 1}, {"b": 1}]}, + expected=False, + msg="field a not >= field b", + ), + ExpressionTestCase( + "empty_obj_self", expression={"$gte": [{}, {}]}, expected=True, msg="{} >= {}" + ), + ExpressionTestCase( + "obj_gte_empty", expression={"$gte": [{"a": 1}, {}]}, expected=True, msg="{a:1} >= {}" + ), + ExpressionTestCase( + "empty_gte_obj", expression={"$gte": [{}, {"a": 1}]}, expected=False, msg="{} not >= {a:1}" + ), + ExpressionTestCase( + "obj_a1_gte_a2", + expression={"$gte": [{"a": 1}, {"a": 2}]}, + expected=False, + msg="{a:1} not >= {a:2}", + ), +] + +ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "arr_2_gte_1", expression={"$gte": [[2], [1]]}, expected=True, msg="[2] >= [1]" + ), + ExpressionTestCase( + "arr_1_gte_2", expression={"$gte": [[1], [2]]}, expected=False, msg="[1] not >= [2]" + ), + ExpressionTestCase( + "arr_1_self", expression={"$gte": [[1], [1]]}, expected=True, msg="[1] >= [1]" + ), + ExpressionTestCase( + "arr_12_gte_1", expression={"$gte": [[1, 2], [1]]}, expected=True, msg="[1,2] >= [1]" + ), + ExpressionTestCase( + "arr_1_gte_12", expression={"$gte": [[1], [1, 2]]}, expected=False, msg="[1] not >= [1,2]" + ), + ExpressionTestCase( + "arr_12_self", expression={"$gte": [[1, 2], [1, 2]]}, expected=True, msg="[1,2] >= [1,2]" + ), + ExpressionTestCase( + "empty_arr_self", expression={"$gte": [[], []]}, expected=True, msg="[] >= []" + ), + ExpressionTestCase( + "arr_1_gte_empty", expression={"$gte": [[1], []]}, expected=True, msg="[1] >= []" + ), + ExpressionTestCase( + "empty_gte_arr_1", expression={"$gte": [[], [1]]}, expected=False, msg="[] not >= [1]" + ), + ExpressionTestCase( + "arr_2_gte_1_999", + expression={"$gte": [[2], [1, 999]]}, + expected=True, + msg="first element wins", + ), + ExpressionTestCase( + "arr_null_gte_empty", expression={"$gte": [[None], []]}, expected=True, msg="[null] >= []" + ), +] + +NESTED_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_1_gte_0", expression={"$gte": [[[1]], [[0]]]}, expected=True, msg="[[1]] >= [[0]]" + ), + ExpressionTestCase( + "null_1_gte_null_0", + expression={"$gte": [[None, 1], [None, 0]]}, + expected=True, + msg="second element comparison", + ), + ExpressionTestCase( + "arr_1_null_gte_1", + expression={"$gte": [[1, None], [1]]}, + expected=True, + msg="longer with same prefix", + ), +] + +DATE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "later_date", + expression={"$gte": [datetime(2025, 6, 1), datetime(2025, 1, 1)]}, + expected=True, + msg="later date >= earlier", + ), + ExpressionTestCase( + "earlier_date", + expression={"$gte": [datetime(2025, 1, 1), datetime(2025, 6, 1)]}, + expected=False, + msg="earlier not >= later", + ), + ExpressionTestCase( + "same_date", + expression={"$gte": [datetime(2025, 1, 1), datetime(2025, 1, 1)]}, + expected=True, + msg="same date >= same date", + ), + ExpressionTestCase( + "millisecond_precision", + expression={"$gte": [datetime(2025, 1, 1, 0, 0, 0, 1000), datetime(2025, 1, 1)]}, + expected=True, + msg="1ms later >= earlier", + ), +] + +BOOLEAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "true_gte_false", expression={"$gte": [True, False]}, expected=True, msg="true >= false" + ), + ExpressionTestCase( + "false_gte_true", + expression={"$gte": [False, True]}, + expected=False, + msg="false not >= true", + ), + ExpressionTestCase( + "true_self", expression={"$gte": [True, True]}, expected=True, msg="true >= true" + ), + ExpressionTestCase( + "false_self", expression={"$gte": [False, False]}, expected=True, msg="false >= false" + ), +] + +EQUALITY_EDGE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("zero_self", expression={"$gte": [0, 0]}, expected=True, msg="0 >= 0"), +] + +OBJECTID_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "objectid_gt", + expression={ + "$gte": [ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa"), ObjectId("000000000000000000000000")] + }, + expected=True, + msg="higher ObjectId >= lower ObjectId", + ), + ExpressionTestCase( + "objectid_equal", + expression={ + "$gte": [ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa"), ObjectId("aaaaaaaaaaaaaaaaaaaaaaaa")] + }, + expected=True, + msg="same ObjectId >= same ObjectId", + ), +] + +BINDATA_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "bindata_longer_gte_shorter", + expression={"$gte": [Binary(b"\x01\x02\x03", 0), Binary(b"\x01\x02", 0)]}, + expected=True, + msg="longer BinData >= shorter BinData", + ), + ExpressionTestCase( + "bindata_higher_bytes", + expression={"$gte": [Binary(b"\x02", 0), Binary(b"\x01", 0)]}, + expected=True, + msg="same length, higher bytes >= lower bytes", + ), + ExpressionTestCase( + "bindata_equal", + expression={"$gte": [Binary(b"\x01", 0), Binary(b"\x01", 0)]}, + expected=True, + msg="equal BinData >= equal BinData", + ), + ExpressionTestCase( + "bindata_diff_subtype", + expression={"$gte": [Binary(b"\x01", 5), Binary(b"\x01", 0)]}, + expected=True, + msg="higher subtype >= lower subtype (same data)", + ), +] + +TIMESTAMP_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "ts_lower_seconds", + expression={"$gte": [Timestamp(50, 1), Timestamp(100, 1)]}, + expected=False, + msg="lower seconds not >= higher seconds", + ), + ExpressionTestCase( + "ts_equal", + expression={"$gte": [Timestamp(100, 1), Timestamp(100, 1)]}, + expected=True, + msg="equal timestamp >= equal timestamp", + ), + ExpressionTestCase( + "ts_same_seconds_higher_ordinal", + expression={"$gte": [Timestamp(100, 2), Timestamp(100, 1)]}, + expected=True, + msg="same seconds, higher ordinal >= lower ordinal", + ), +] + +REGEX_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "regex_higher_pattern", + expression={"$gte": [Regex("def"), Regex("abc")]}, + expected=True, + msg="higher pattern >= lower pattern", + ), + ExpressionTestCase( + "regex_equal", + expression={"$gte": [Regex("abc"), Regex("abc")]}, + expected=True, + msg="same regex >= same regex", + ), + ExpressionTestCase( + "regex_flags_gte_no_flags", + expression={"$gte": [Regex("abc", "i"), Regex("abc")]}, + expected=True, + msg="regex with flags >= without flags", + ), + ExpressionTestCase( + "regex_lower_pattern", + expression={"$gte": [Regex("abc"), Regex("def")]}, + expected=False, + msg="lower pattern not >= higher pattern", + ), +] + +ALL_TESTS = ( + CORE_TESTS + + STRING_TESTS + + OBJECT_TESTS + + ARRAY_TESTS + + NESTED_ARRAY_TESTS + + DATE_TESTS + + BOOLEAN_TESTS + + EQUALITY_EDGE_TESTS + + OBJECTID_TESTS + + BINDATA_TESTS + + TIMESTAMP_TESTS + + REGEX_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_gte_same_type_comparisons(collection, test): + """Test $gte same-type comparisons and within-type ordering.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg)