diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_argument_handling.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_argument_handling.py new file mode 100644 index 00000000..e0aade4a --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_argument_handling.py @@ -0,0 +1,83 @@ +""" +Tests for $lt 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 + +LT_ARG_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "no_args", + expression={"$lt": []}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for empty arguments", + ), + ExpressionTestCase( + "single_arg", + expression={"$lt": [1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for single argument", + ), + ExpressionTestCase( + "non_array_arg", + expression={"$lt": 1}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array argument", + ), + ExpressionTestCase( + "non_array_string", + expression={"$lt": "string"}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for string argument", + ), + ExpressionTestCase( + "non_array_field_ref", + expression={"$lt": "$field"}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array field reference argument", + ), + ExpressionTestCase( + "three_args", + expression={"$lt": [1, 2, 3]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for three arguments", + ), + ExpressionTestCase( + "two_args_lt", + expression={"$lt": [1, 2]}, + expected=True, + msg="Should return true when first < second", + ), + ExpressionTestCase( + "two_args_eq", + expression={"$lt": [1, 1]}, + expected=False, + msg="Should return false when equal", + ), + ExpressionTestCase( + "two_args_gt", + expression={"$lt": [2, 1]}, + expected=False, + msg="Should return false when first > second", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LT_ARG_TESTS)) +def test_lt_argument_handling(collection, test): + """Test $lt 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/lt/test_lt_boundary_precision.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_boundary_precision.py new file mode 100644 index 00000000..44dc17e2 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_boundary_precision.py @@ -0,0 +1,164 @@ +""" +Tests for $lt 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_minus1_lt_max", + expression={"$lt": [INT32_MAX - 1, INT32_MAX]}, + expected=True, + msg="INT32_MAX-1 < INT32_MAX", + ), + ExpressionTestCase( + "int32_max_self", + expression={"$lt": [INT32_MAX, INT32_MAX]}, + expected=False, + msg="INT32_MAX not < INT32_MAX", + ), + ExpressionTestCase( + "int32_min_lt_min_plus1", + expression={"$lt": [INT32_MIN, INT32_MIN + 1]}, + expected=True, + msg="INT32_MIN < INT32_MIN+1", + ), + ExpressionTestCase( + "int32_min_self", + expression={"$lt": [INT32_MIN, INT32_MIN]}, + expected=False, + msg="INT32_MIN not < INT32_MIN", + ), + ExpressionTestCase( + "int32_max_lt_long_above", + expression={"$lt": [INT32_MAX, Int64(INT32_MAX + 1)]}, + expected=True, + msg="int(INT32_MAX) < long(INT32_MAX+1)", + ), +] + + +INT64_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_max_minus1_lt_max", + expression={"$lt": [Int64(INT64_MAX - 1), INT64_MAX]}, + expected=True, + msg="INT64_MAX-1 < INT64_MAX", + ), + ExpressionTestCase( + "int64_max_self", + expression={"$lt": [INT64_MAX, INT64_MAX]}, + expected=False, + msg="INT64_MAX not < INT64_MAX", + ), + ExpressionTestCase( + "int64_min_lt_min_plus1", + expression={"$lt": [INT64_MIN, Int64(INT64_MIN + 1)]}, + expected=True, + msg="INT64_MIN < INT64_MIN+1", + ), + ExpressionTestCase( + "int64_min_self", + expression={"$lt": [INT64_MIN, INT64_MIN]}, + expected=False, + msg="INT64_MIN not < INT64_MIN", + ), +] + +DOUBLE_PRECISION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "near_max_self", + expression={"$lt": [DOUBLE_NEAR_MAX, DOUBLE_NEAR_MAX]}, + expected=False, + msg="DOUBLE_NEAR_MAX not < itself", + ), + ExpressionTestCase( + "zero_lt_subnormal", + expression={"$lt": [0.0, DOUBLE_MIN_SUBNORMAL]}, + expected=True, + msg="0.0 < min subnormal", + ), + ExpressionTestCase( + "subnormal_lt_zero", + expression={"$lt": [DOUBLE_MIN_SUBNORMAL, 0.0]}, + expected=False, + msg="min subnormal not < 0.0", + ), + ExpressionTestCase( + "neg_subnormal_lt_zero", + expression={"$lt": [DOUBLE_MIN_NEGATIVE_SUBNORMAL, 0.0]}, + expected=True, + msg="neg subnormal < 0.0", + ), + ExpressionTestCase( + "zero_lt_neg_subnormal", + expression={"$lt": [0.0, DOUBLE_MIN_NEGATIVE_SUBNORMAL]}, + expected=False, + msg="0.0 not < neg subnormal", + ), + ExpressionTestCase( + "near_min_lt_near_max", + expression={"$lt": [DOUBLE_NEAR_MIN, DOUBLE_NEAR_MAX]}, + expected=True, + msg="DOUBLE_NEAR_MIN < DOUBLE_NEAR_MAX", + ), +] + + +LARGE_NUMBER_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_beyond_double_precision", + expression={"$lt": [Int64(9007199254740992), Int64(9007199254740993)]}, + expected=True, + msg="int64 beyond double precision", + ), + ExpressionTestCase( + "dec_34_digit", + expression={ + "$lt": [ + Decimal128("9999999999999999999999999999999998"), + Decimal128("9999999999999999999999999999999999"), + ] + }, + expected=True, + msg="34-digit decimal comparison", + ), + ExpressionTestCase( + "int64_max_vs_double_int64_max", + expression={"$lt": [INT64_MAX, float(INT64_MAX)]}, + expected=True, + msg="INT64_MAX < double(INT64_MAX) due to precision loss rounding up", + ), +] + + +ALL_TESTS = INT32_TESTS + INT64_TESTS + DOUBLE_PRECISION_TESTS + LARGE_NUMBER_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lt_boundary_precision(collection, test): + """Test $lt numeric boundaries and double 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/lt/test_lt_bson_wiring.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_bson_wiring.py new file mode 100644 index 00000000..b84a4f9f --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_bson_wiring.py @@ -0,0 +1,75 @@ +""" +Representative BSON comparison engine wiring tests for $lt. + +A small sample of cross-type and special value comparisons to confirm $lt +delegates to the BSON comparison engine correctly. Not an exhaustive matrix — +full BSON ordering coverage lives in /core/data_types/bson_types/. +""" + +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] = [ + ExpressionTestCase( + "number_lt_string", expression={"$lt": [1, "a"]}, expected=True, msg="number < string" + ), + ExpressionTestCase( + "string_not_lt_number", + expression={"$lt": ["a", 1]}, + expected=False, + msg="string not < number", + ), + ExpressionTestCase( + "minkey_lt_number", expression={"$lt": [MinKey(), 1]}, expected=True, msg="MinKey < number" + ), + ExpressionTestCase( + "maxkey_not_lt_number", + expression={"$lt": [MaxKey(), 1]}, + expected=False, + msg="MaxKey not < number", + ), + ExpressionTestCase( + "int_not_lt_equivalent_long", + expression={"$lt": [1, Int64(1)]}, + expected=False, + msg="int(1) not < long(1)", + ), + ExpressionTestCase( + "int_not_lt_equivalent_decimal", + expression={"$lt": [1, Decimal128("1")]}, + expected=False, + msg="int(1) not < decimal(1)", + ), + ExpressionTestCase( + "neg_zero_not_lt_zero", + expression={"$lt": [DOUBLE_NEGATIVE_ZERO, 0.0]}, + expected=False, + msg="-0.0 not < 0.0", + ), + ExpressionTestCase( + "nan_not_lt_nan", + expression={"$lt": [FLOAT_NAN, FLOAT_NAN]}, + expected=False, + msg="NaN not < NaN (equal)", + ), + ExpressionTestCase( + "nan_lt_zero", expression={"$lt": [FLOAT_NAN, 0]}, expected=True, msg="NaN < 0" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(BSON_WIRING_TESTS)) +def test_lt_bson_wiring(collection, test): + """Smoke test: confirm $lt 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/lt/test_lt_expression_types.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_expression_types.py new file mode 100644 index 00000000..97d3b254 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_expression_types.py @@ -0,0 +1,123 @@ +""" +Tests for $lt expression type smoke tests. + +Covers literal, field reference, expression operator, array expression, +and object expression. +""" + +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={"$lt": [3, 5]}, expected=True, msg="Literal 3 < 5" + ), + ExpressionTestCase( + "literal_false", expression={"$lt": [5, 3]}, expected=False, msg="5 not < 3" + ), + ExpressionTestCase( + "literal_equal", expression={"$lt": [5, 5]}, expected=False, msg="5 not < 5" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LITERAL_TESTS)) +def test_lt_expression_types_literal(collection, test): + """Test $lt 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={"$lt": ["$a", "$b"]}, + doc={"a": 5, "b": 10}, + expected=True, + msg="Field a < field b", + ), + ExpressionTestCase( + "field_ref_false", + expression={"$lt": ["$a", "$b"]}, + doc={"a": 10, "b": 5}, + expected=False, + msg="Field a not < field b", + ), + ExpressionTestCase( + "field_ref_string", + expression={"$lt": ["$x", "abc"]}, + doc={"x": "ABC"}, + expected=True, + msg="Field path 'ABC' < 'abc'", + ), +] + +EXPRESSION_OPERATOR_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "expr_operator", + expression={"$lt": [{"$abs": "$a"}, 10]}, + doc={"a": -5}, + expected=True, + msg="abs(-5)=5 < 10", + ), + ExpressionTestCase( + "system_var_current", + expression={"$lt": ["$$CURRENT.a", 2]}, + doc={"a": 1}, + expected=True, + msg="$$CURRENT.a equivalent to $a", + ), +] + +COMPOSITE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "composite_missing_path", + expression={"$lt": ["$a.b.c.x", None]}, + doc={"a": {"b": {"c": {"d": 1}}}}, + expected=True, + msg="Missing composite path < null", + ), +] + +ARRAY_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_expr", + expression={"$lt": ["$a", [1, 2]]}, + doc={"a": [1, 1]}, + expected=True, + msg="Array [1,1] < [1,2]", + ), +] + +OBJECT_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "object_expr", + expression={"$lt": ["$a", {"x": 2}]}, + doc={"a": {"x": 1}}, + expected=True, + msg="Object {x:1} < {x:2}", + ), +] +ALL_INSERT_TESTS = ( + FIELD_REFERENCE_TESTS + + EXPRESSION_OPERATOR_TESTS + + ARRAY_EXPRESSION_TESTS + + OBJECT_EXPRESSION_TESTS + + COMPOSITE_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_INSERT_TESTS)) +def test_lt_expression_types_insert(collection, test): + """Test $lt 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/lt/test_lt_field_lookup.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_field_lookup.py new file mode 100644 index 00000000..4461ce29 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_field_lookup.py @@ -0,0 +1,94 @@ +""" +Tests for $lt field lookup variations. + +Covers simple, nested, array, non-existent, composite array, array index, +deeply nested paths, and field resolving to array vs scalar. +""" + +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={"$lt": ["$a", 10]}, + doc={"a": 5}, + expected=True, + msg="Simple field 5 < 10", + ), + ExpressionTestCase( + "simple_field_equal", + expression={"$lt": ["$a", 5]}, + doc={"a": 5}, + expected=False, + msg="Simple field 5 not < 5", + ), + ExpressionTestCase( + "nonexistent_field", + expression={"$lt": ["$a", 5]}, + doc={"x": 1}, + expected=True, + msg="Missing field (null) < 5", + ), +] + +NESTED_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_field", + expression={"$lt": ["$a.b", 10]}, + doc={"a": {"b": 5}}, + expected=True, + msg="Nested field a.b=5 < 10", + ), + ExpressionTestCase( + "deeply_nested_field", + expression={"$lt": ["$a.b.c.d", 10]}, + doc={"a": {"b": {"c": {"d": 5}}}}, + expected=True, + msg="Deeply nested a.b.c.d=5 < 10", + ), + ExpressionTestCase( + "deeply_nested_equal", + expression={"$lt": ["$a.b.c.d", 5]}, + doc={"a": {"b": {"c": {"d": 5}}}}, + expected=False, + msg="Deeply nested 5 not < 5 (equal)", + ), +] + +NULL_MISSING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "missing_vs_null", + expression={"$lt": ["$nonexistent", None]}, + doc={}, + expected=True, + msg="missing < null literal", + ), +] + +ARRAY_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_field_vs_array", + expression={"$lt": ["$a", [1, 2, 3]]}, + doc={"a": [1, 2, 3]}, + expected=False, + msg="array not < same array", + ), +] + +ALL_TESTS = SIMPLE_FIELD_TESTS + NESTED_FIELD_TESTS + NULL_MISSING_TESTS + ARRAY_FIELD_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lt_field_lookup(collection, test): + """Test $lt 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/lt/test_lt_nan_infinity.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_nan_infinity.py new file mode 100644 index 00000000..41255da5 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_nan_infinity.py @@ -0,0 +1,98 @@ +""" +Tests for $lt 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( + "1_lt_inf", expression={"$lt": [1, FLOAT_INFINITY]}, expected=True, msg="1 < Inf" + ), + ExpressionTestCase( + "inf_lt_1", expression={"$lt": [FLOAT_INFINITY, 1]}, expected=False, msg="Inf not < 1" + ), + ExpressionTestCase( + "neg_inf_lt_1", + expression={"$lt": [FLOAT_NEGATIVE_INFINITY, 1]}, + expected=True, + msg="-Inf < 1", + ), + ExpressionTestCase( + "1_lt_neg_inf", + expression={"$lt": [1, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="1 not < -Inf", + ), + ExpressionTestCase( + "neg_inf_lt_inf", + expression={"$lt": [FLOAT_NEGATIVE_INFINITY, FLOAT_INFINITY]}, + expected=True, + msg="-Inf < Inf", + ), + ExpressionTestCase( + "inf_lt_neg_inf", + expression={"$lt": [FLOAT_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="Inf not < -Inf", + ), + ExpressionTestCase( + "inf_self", + expression={"$lt": [FLOAT_INFINITY, FLOAT_INFINITY]}, + expected=False, + msg="Inf not < Inf", + ), + ExpressionTestCase( + "neg_inf_self", + expression={"$lt": [FLOAT_NEGATIVE_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="-Inf not < -Inf", + ), + ExpressionTestCase( + "near_max_lt_inf", + expression={"$lt": [DOUBLE_NEAR_MAX, FLOAT_INFINITY]}, + expected=True, + msg="DOUBLE_NEAR_MAX < Inf", + ), +] + + +NAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nan_lt_int", expression={"$lt": [FLOAT_NAN, 1]}, expected=True, msg="NaN < int" + ), + ExpressionTestCase( + "int_lt_nan", expression={"$lt": [1, FLOAT_NAN]}, expected=False, msg="int not < NaN" + ), + ExpressionTestCase( + "nan_lt_neg_inf", + expression={"$lt": [FLOAT_NAN, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="NaN < -Infinity in BSON order", + ), +] + +ALL_TESTS = INF_TESTS + NAN_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lt_nan_infinity(collection, test): + """Test $lt 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/lt/test_lt_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_null_missing.py new file mode 100644 index 00000000..5f449c4c --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_null_missing.py @@ -0,0 +1,94 @@ +""" +Tests for $lt 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={"$lt": [None, None]}, expected=False, msg="null not < null" + ), + ExpressionTestCase("null_int", expression={"$lt": [None, 1]}, expected=True, msg="null < int"), + ExpressionTestCase( + "int_null", expression={"$lt": [1, None]}, expected=False, msg="int not < null" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NULL_LITERAL_TESTS)) +def test_lt_null_missing_literal(collection, test): + """Test $lt null literal comparisons.""" + 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={"$lt": ["$a", "$b"]}, + doc={"b": 5}, + expected=True, + msg="missing in position 1 → null < 5", + ), + ExpressionTestCase( + "missing_pos2", + expression={"$lt": ["$a", "$b"]}, + doc={"a": 5}, + expected=False, + msg="missing in position 2 → 5 not < null", + ), + ExpressionTestCase( + "both_missing", + expression={"$lt": ["$a", "$b"]}, + doc={}, + expected=False, + msg="both missing → null not < null", + ), +] + + +NULL_MISSING_EQUIV_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_field_vs_missing", + expression={"$lt": ["$a", "$nonexistent"]}, + doc={"a": None}, + expected=False, + msg="null field not < missing", + ), + ExpressionTestCase( + "missing_vs_null_literal", + expression={"$lt": ["$nonexistent", None]}, + doc={}, + expected=True, + msg="missing field < null literal in expression context", + ), + ExpressionTestCase( + "null_vs_null_fields", + expression={"$lt": ["$a", "$b"]}, + doc={"a": None, "b": None}, + expected=False, + msg="null field not < null field", + ), +] + + +ALL_INSERT_TESTS = MISSING_FIELD_TESTS + NULL_MISSING_EQUIV_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_INSERT_TESTS)) +def test_lt_null_missing_insert(collection, test): + """Test $lt null and missing field handling with 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/lt/test_lt_same_type_comparisons.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_same_type_comparisons.py new file mode 100644 index 00000000..1cfc0d56 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lt/test_lt_same_type_comparisons.py @@ -0,0 +1,247 @@ +""" +Tests for $lt same-type comparisons and within-type ordering. + +Covers string, object, array, date, and boolean comparisons. +""" + +from datetime import datetime + +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 + +NUMERIC_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("int_lt", expression={"$lt": [1, 2]}, expected=True, msg="int 1 < 2"), + ExpressionTestCase("int_eq", expression={"$lt": [1, 1]}, expected=False, msg="int 1 not < 1"), + ExpressionTestCase("int_gt", expression={"$lt": [2, 1]}, expected=False, msg="int 2 not < 1"), + ExpressionTestCase( + "long_lt", expression={"$lt": [Int64(1), Int64(2)]}, expected=True, msg="long 1 < 2" + ), + ExpressionTestCase( + "long_eq", expression={"$lt": [Int64(1), Int64(1)]}, expected=False, msg="long 1 not < 1" + ), + ExpressionTestCase( + "double_lt", expression={"$lt": [1.0, 1.5]}, expected=True, msg="double 1.0 < 1.5" + ), + ExpressionTestCase( + "double_eq", expression={"$lt": [1.0, 1.0]}, expected=False, msg="double 1.0 not < 1.0" + ), + ExpressionTestCase( + "dec_lt", + expression={"$lt": [Decimal128("1"), Decimal128("2")]}, + expected=True, + msg="dec 1 < 2", + ), + ExpressionTestCase( + "dec_eq", + expression={"$lt": [Decimal128("1"), Decimal128("1")]}, + expected=False, + msg="dec 1 not < 1", + ), + ExpressionTestCase("neg_lt_pos", expression={"$lt": [-1, 1]}, expected=True, msg="-1 < 1"), + ExpressionTestCase("pos_lt_neg", expression={"$lt": [1, -1]}, expected=False, msg="1 not < -1"), + ExpressionTestCase("neg_lt_zero", expression={"$lt": [-1, 0]}, expected=True, msg="-1 < 0"), + ExpressionTestCase( + "zero_lt_neg", expression={"$lt": [0, -1]}, expected=False, msg="0 not < -1" + ), + ExpressionTestCase( + "neg_lt_neg", expression={"$lt": [-1, -1]}, expected=False, msg="-1 not < -1" + ), +] + + +STRING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "apple_lt_banana", + expression={"$lt": ["apple", "banana"]}, + expected=True, + msg="apple < banana", + ), + ExpressionTestCase( + "banana_lt_apple", + expression={"$lt": ["banana", "apple"]}, + expected=False, + msg="banana not < apple", + ), + ExpressionTestCase( + "apple_self", + expression={"$lt": ["apple", "apple"]}, + expected=False, + msg="apple not < apple", + ), + ExpressionTestCase( + "Apple_lt_apple", + expression={"$lt": ["Apple", "apple"]}, + expected=True, + msg="uppercase < lowercase", + ), + ExpressionTestCase( + "apple_lt_Apple", + expression={"$lt": ["apple", "Apple"]}, + expected=False, + msg="lowercase not < uppercase", + ), + ExpressionTestCase("empty_lt_a", expression={"$lt": ["", "a"]}, expected=True, msg="empty < a"), + ExpressionTestCase( + "a_lt_empty", expression={"$lt": ["a", ""]}, expected=False, msg="a not < empty" + ), + ExpressionTestCase( + "empty_self", expression={"$lt": ["", ""]}, expected=False, msg="empty not < empty" + ), + ExpressionTestCase( + "ab_lt_abc", expression={"$lt": ["ab", "abc"]}, expected=True, msg="shorter prefix < longer" + ), + ExpressionTestCase( + "abc_lt_ab", + expression={"$lt": ["abc", "ab"]}, + expected=False, + msg="longer not < shorter prefix", + ), +] + +OBJECT_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "obj_a1_lt_a2", expression={"$lt": [{"a": 1}, {"a": 2}]}, expected=True, msg="{a:1} < {a:2}" + ), + ExpressionTestCase( + "obj_a1_self", + expression={"$lt": [{"a": 1}, {"a": 1}]}, + expected=False, + msg="{a:1} not < {a:1}", + ), + ExpressionTestCase( + "obj_a_lt_ab", + expression={"$lt": [{"a": 1}, {"a": 1, "b": 1}]}, + expected=True, + msg="fewer fields < more", + ), + ExpressionTestCase( + "empty_obj_self", expression={"$lt": [{}, {}]}, expected=False, msg="{} not < {}" + ), + ExpressionTestCase( + "empty_lt_obj", expression={"$lt": [{}, {"a": 1}]}, expected=True, msg="{} < {a:1}" + ), +] + + +ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "arr_1_lt_2", expression={"$lt": [[1], [2]]}, expected=True, msg="[1] < [2]" + ), + ExpressionTestCase( + "arr_1_self", expression={"$lt": [[1], [1]]}, expected=False, msg="[1] not < [1]" + ), + ExpressionTestCase( + "arr_1_lt_12", expression={"$lt": [[1], [1, 2]]}, expected=True, msg="[1] < [1,2]" + ), + ExpressionTestCase( + "arr_12_self", expression={"$lt": [[1, 2], [1, 2]]}, expected=False, msg="[1,2] not < [1,2]" + ), + ExpressionTestCase( + "empty_arr_self", expression={"$lt": [[], []]}, expected=False, msg="[] not < []" + ), + ExpressionTestCase( + "empty_lt_arr_1", expression={"$lt": [[], [1]]}, expected=True, msg="[] < [1]" + ), + ExpressionTestCase( + "arr_5_100_200_lt_4_15", + expression={"$lt": [[5, 100, 200], [4, 15, 300, 400, 500]]}, + expected=False, + msg="first elem 5 > 4", + ), + ExpressionTestCase( + "arr_5_14_lt_5_15", + expression={"$lt": [[5, 14], [5, 15]]}, + expected=True, + msg="second elem 14 < 15", + ), + ExpressionTestCase( + "arr_5_14_100_lt_5_15_0", + expression={"$lt": [[5, 14, 100], [5, 15, 0]]}, + expected=True, + msg="second elem 14 < 15", + ), +] + + +NESTED_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_0_lt_1", expression={"$lt": [[[0]], [[1]]]}, expected=True, msg="[[0]] < [[1]]" + ), + ExpressionTestCase( + "null_0_lt_null_1", + expression={"$lt": [[None, 0], [None, 1]]}, + expected=True, + msg="second element comparison", + ), + ExpressionTestCase( + "arr_1_lt_1_null", + expression={"$lt": [[1], [1, None]]}, + expected=True, + msg="shorter < longer with same prefix", + ), +] + + +DATE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "date_earlier_lt_later", + expression={"$lt": [datetime(2025, 1, 1), datetime(2025, 6, 1)]}, + expected=True, + msg="earlier < later", + ), + ExpressionTestCase( + "date_later_lt_earlier", + expression={"$lt": [datetime(2025, 6, 1), datetime(2025, 1, 1)]}, + expected=False, + msg="later not < earlier", + ), + ExpressionTestCase( + "date_self", + expression={"$lt": [datetime(2025, 1, 1), datetime(2025, 1, 1)]}, + expected=False, + msg="same date not < itself", + ), +] + + +BOOLEAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "false_lt_true", expression={"$lt": [False, True]}, expected=True, msg="false < true" + ), + ExpressionTestCase( + "true_lt_false", expression={"$lt": [True, False]}, expected=False, msg="true not < false" + ), + ExpressionTestCase( + "true_self", expression={"$lt": [True, True]}, expected=False, msg="true not < true" + ), + ExpressionTestCase( + "false_self", expression={"$lt": [False, False]}, expected=False, msg="false not < false" + ), +] + + +ALL_TESTS = ( + NUMERIC_TESTS + + STRING_TESTS + + OBJECT_TESTS + + ARRAY_TESTS + + NESTED_ARRAY_TESTS + + DATE_TESTS + + BOOLEAN_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lt_same_type_comparisons(collection, test): + """Test $lt 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/lte/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_argument_handling.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_argument_handling.py new file mode 100644 index 00000000..6a739c41 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_argument_handling.py @@ -0,0 +1,77 @@ +""" +Tests for $lte 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 + +LTE_ARG_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "no_args", + expression={"$lte": []}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for empty arguments", + ), + ExpressionTestCase( + "single_arg", + expression={"$lte": [1]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for single argument", + ), + ExpressionTestCase( + "non_array_int", + expression={"$lte": 1}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array int argument", + ), + ExpressionTestCase( + "non_array_string", + expression={"$lte": "$field"}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for non-array string argument", + ), + ExpressionTestCase( + "three_args", + expression={"$lte": [1, 2, 3]}, + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Should error for three arguments", + ), + ExpressionTestCase( + "two_args_lt", + expression={"$lte": [1, 2]}, + expected=True, + msg="Should return true when first < second", + ), + ExpressionTestCase( + "two_args_eq", + expression={"$lte": [1, 1]}, + expected=True, + msg="Should return true when equal", + ), + ExpressionTestCase( + "two_args_gt", + expression={"$lte": [2, 1]}, + expected=False, + msg="Should return false when first > second", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LTE_ARG_TESTS)) +def test_lte_argument_handling(collection, test): + """Test $lte 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/lte/test_lte_boundary_precision.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_boundary_precision.py new file mode 100644 index 00000000..c025faa9 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_boundary_precision.py @@ -0,0 +1,137 @@ +""" +Tests for $lte boundary values and double precision. + +Covers integer boundaries, double precision, and large integer precision edge cases. +""" + +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 ( # 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, +) + +INT_BOUNDARY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int32_max_eq", + expression={"$lte": [INT32_MAX, INT32_MAX]}, + expected=True, + msg="INT32_MAX <= INT32_MAX", + ), + ExpressionTestCase( + "int32_min_eq", + expression={"$lte": [INT32_MIN, INT32_MIN]}, + expected=True, + msg="INT32_MIN <= INT32_MIN", + ), + ExpressionTestCase( + "int32_min_lte_max", + expression={"$lte": [INT32_MIN, INT32_MAX]}, + expected=True, + msg="INT32_MIN <= INT32_MAX", + ), + ExpressionTestCase( + "int32_max_lte_min", + expression={"$lte": [INT32_MAX, INT32_MIN]}, + expected=False, + msg="INT32_MAX not <= INT32_MIN", + ), + ExpressionTestCase( + "int64_max_eq", + expression={"$lte": [INT64_MAX, INT64_MAX]}, + expected=True, + msg="INT64_MAX <= INT64_MAX", + ), + ExpressionTestCase( + "int64_min_eq", + expression={"$lte": [INT64_MIN, INT64_MIN]}, + expected=True, + msg="INT64_MIN <= INT64_MIN", + ), + ExpressionTestCase( + "int64_min_lte_max", + expression={"$lte": [INT64_MIN, INT64_MAX]}, + expected=True, + msg="INT64_MIN <= INT64_MAX", + ), + ExpressionTestCase( + "int64_max_lte_min", + expression={"$lte": [INT64_MAX, INT64_MIN]}, + expected=False, + msg="INT64_MAX not <= INT64_MIN", + ), +] + +DOUBLE_PRECISION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "near_max_eq", + expression={"$lte": [DOUBLE_NEAR_MAX, DOUBLE_NEAR_MAX]}, + expected=True, + msg="DOUBLE_NEAR_MAX <= DOUBLE_NEAR_MAX", + ), + ExpressionTestCase( + "subnormal_lte_zero", + expression={"$lte": [DOUBLE_MIN_SUBNORMAL, 0.0]}, + expected=False, + msg="subnormal not <= 0", + ), + ExpressionTestCase( + "zero_lte_subnormal", + expression={"$lte": [0.0, DOUBLE_MIN_SUBNORMAL]}, + expected=True, + msg="0 <= subnormal", + ), + ExpressionTestCase( + "neg_subnormal_lte_zero", + expression={"$lte": [DOUBLE_MIN_NEGATIVE_SUBNORMAL, 0.0]}, + expected=True, + msg="negative subnormal <= 0", + ), + ExpressionTestCase( + "near_min_lte_near_max", + expression={"$lte": [DOUBLE_NEAR_MIN, DOUBLE_NEAR_MAX]}, + expected=True, + msg="DOUBLE_NEAR_MIN <= DOUBLE_NEAR_MAX", + ), +] + +LARGE_INT_VS_DOUBLE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "int64_beyond_double_mantissa", + expression={"$lte": [Int64(9007199254740993), 9007199254740992.0]}, + expected=False, + msg="int64 beyond double mantissa: 9007199254740993 not <= 9007199254740992.0", + ), + ExpressionTestCase( + "int64_exactly_representable", + expression={"$lte": [Int64(9007199254740992), 9007199254740992.0]}, + expected=True, + msg="exactly representable int64 == double", + ), +] + +ALL_TESTS: list[ExpressionTestCase] = ( + INT_BOUNDARY_TESTS + DOUBLE_PRECISION_TESTS + LARGE_INT_VS_DOUBLE_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lte_boundary_precision(collection, test): + """Test $lte 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/lte/test_lte_bson_wiring.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_bson_wiring.py new file mode 100644 index 00000000..86327e22 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_bson_wiring.py @@ -0,0 +1,78 @@ +""" +Representative BSON comparison engine wiring tests for $lte. + +A small sample of cross-type and special value comparisons to confirm $lte +delegates to the BSON comparison engine correctly. Not an exhaustive matrix — +full BSON ordering coverage lives in /core/data_types/bson_types/. +""" + +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] = [ + ExpressionTestCase( + "number_lte_string", expression={"$lte": [1, "a"]}, expected=True, msg="number <= string" + ), + ExpressionTestCase( + "string_not_lte_number", + expression={"$lte": ["a", 1]}, + expected=False, + msg="string not <= number", + ), + ExpressionTestCase( + "minkey_lte_number", + expression={"$lte": [MinKey(), 1]}, + expected=True, + msg="MinKey <= number", + ), + ExpressionTestCase( + "maxkey_not_lte_number", + expression={"$lte": [MaxKey(), 1]}, + expected=False, + msg="MaxKey not <= number", + ), + ExpressionTestCase( + "int_lte_equivalent_long", + expression={"$lte": [1, Int64(1)]}, + expected=True, + msg="int(1) <= long(1)", + ), + ExpressionTestCase( + "int_lte_equivalent_decimal", + expression={"$lte": [1, Decimal128("1")]}, + expected=True, + msg="int(1) <= decimal(1)", + ), + ExpressionTestCase( + "neg_zero_lte_zero", + expression={"$lte": [DOUBLE_NEGATIVE_ZERO, 0.0]}, + expected=True, + msg="-0.0 <= 0.0", + ), + ExpressionTestCase( + "nan_lte_nan", + expression={"$lte": [FLOAT_NAN, FLOAT_NAN]}, + expected=True, + msg="NaN <= NaN (equal)", + ), + ExpressionTestCase( + "nan_lte_zero", expression={"$lte": [FLOAT_NAN, 0]}, expected=True, msg="NaN <= 0" + ), +] + + +@pytest.mark.parametrize("test", pytest_params(BSON_WIRING_TESTS)) +def test_lte_bson_wiring(collection, test): + """Smoke test: confirm $lte 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/lte/test_lte_expression_types.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_expression_types.py new file mode 100644 index 00000000..51e6f7be --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_expression_types.py @@ -0,0 +1,129 @@ +""" +Tests for $lte expression types, array index paths, and system variables. + +Covers literal, field path, expression operator, array expression, object +expression, composite array, missing paths, and system variable inputs. +""" + +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_int", expression={"$lte": [1, 2]}, expected=True, msg="literal 1 <= 2" + ), + ExpressionTestCase( + "literal_string", expression={"$lte": ["a", "b"]}, expected=True, msg="literal 'a' <= 'b'" + ), + ExpressionTestCase( + "literal_equal", expression={"$lte": [5, 5]}, expected=True, msg="literal 5 <= 5" + ), + ExpressionTestCase( + "literal_false", expression={"$lte": [5, 3]}, expected=False, msg="5 not <= 3" + ), +] + +FIELD_PATH_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "field_path_true", + expression={"$lte": ["$a", "$b"]}, + doc={"a": 5, "b": 10}, + expected=True, + msg="field $a(5) <= $b(10)", + ), + ExpressionTestCase( + "field_path_equal", + expression={"$lte": ["$a", "$b"]}, + doc={"a": 5, "b": 5}, + expected=True, + msg="field $a(5) <= $b(5) (equal)", + ), + ExpressionTestCase( + "field_path_false", + expression={"$lte": ["$a", "$b"]}, + doc={"a": 10, "b": 5}, + expected=False, + msg="field $a(10) not <= $b(5)", + ), + ExpressionTestCase( + "field_ref_string", + expression={"$lte": ["$x", "abc"]}, + doc={"x": "ABC"}, + expected=True, + msg="Field path 'ABC' <= 'abc'", + ), + ExpressionTestCase( + "expression_operator_input", + expression={"$lte": [{"$abs": "$a"}, "$b"]}, + doc={"a": -2, "b": 3}, + expected=True, + msg="abs(-2)=2 <= 3", + ), + ExpressionTestCase( + "system_var_current", + expression={"$lte": ["$$CURRENT.a", 1]}, + doc={"a": 1}, + expected=True, + msg="$$CURRENT.a equivalent to $a", + ), +] + +ARRAY_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "array_expr", + expression={"$lte": ["$a", [1, 2]]}, + doc={"a": [1, 2]}, + expected=True, + msg="Array [1,2] <= [1,2] (equal)", + ), +] + +OBJECT_EXPRESSION_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "object_expr", + expression={"$lte": ["$a", {"x": 1}]}, + doc={"a": {"x": 1}}, + expected=True, + msg="Object {x:1} <= {x:1} (equal)", + ), +] + +COMPOSITE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "composite_missing_path", + expression={"$lte": ["$a.b.c.x", None]}, + doc={"a": {"b": {"c": {"d": 1}}}}, + expected=True, + msg="Missing composite path <= null", + ), +] + +ALL_LITERAL_TESTS = LITERAL_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_LITERAL_TESTS)) +def test_lte_expression_types_literal(collection, test): + """Test $lte with literal expression inputs.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +ALL_INSERT_TESTS = ( + FIELD_PATH_TESTS + ARRAY_EXPRESSION_TESTS + OBJECT_EXPRESSION_TESTS + COMPOSITE_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_INSERT_TESTS)) +def test_lte_expression_types_insert(collection, test): + """Test $lte 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/lte/test_lte_field_lookup.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_field_lookup.py new file mode 100644 index 00000000..3040cf3b --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_field_lookup.py @@ -0,0 +1,69 @@ +""" +Tests for $lte field lookup variations. + +Covers simple, nested, array, non-existent, composite array, deeply nested +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 + +ALL_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "simple_field", + expression={"$lte": ["$a", "$b"]}, + doc={"a": 1, "b": 2}, + expected=True, + msg="$a(1) <= $b(2)", + ), + ExpressionTestCase( + "nested_field", + expression={"$lte": ["$a.b", "$c"]}, + doc={"a": {"b": 1}, "c": 2}, + expected=True, + msg="$a.b(1) <= $c(2)", + ), + ExpressionTestCase( + "deeply_nested_field", + expression={"$lte": ["$a.b.c.d", 10]}, + doc={"a": {"b": {"c": {"d": 5}}}}, + expected=True, + msg="deeply nested 5 <= 10", + ), + ExpressionTestCase( + "deeply_nested_self", + expression={"$lte": ["$a.b.c.d", 5]}, + doc={"a": {"b": {"c": {"d": 5}}}}, + expected=True, + msg="deeply nested 5 <= 5 (equal)", + ), + ExpressionTestCase( + "nonexistent_field", + expression={"$lte": ["$nonexistent", 1]}, + doc={"a": 1}, + expected=True, + msg="missing field (null) <= 1", + ), + ExpressionTestCase( + "array_field_vs_array", + expression={"$lte": ["$a", [1, 2, 3]]}, + doc={"a": [1, 2, 3]}, + expected=True, + msg="array <= same array", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lte_field_lookup(collection, test): + """Test $lte 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/lte/test_lte_nan_infinity.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_nan_infinity.py new file mode 100644 index 00000000..1964d3b0 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_nan_infinity.py @@ -0,0 +1,115 @@ +""" +Tests for $lte 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 ( + FLOAT_INFINITY, + FLOAT_NAN, + FLOAT_NEGATIVE_INFINITY, + INT32_MAX, +) + +INFINITY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "inf_lte_inf", + expression={"$lte": [FLOAT_INFINITY, FLOAT_INFINITY]}, + expected=True, + msg="Infinity <= Infinity", + ), + ExpressionTestCase( + "neg_inf_lte_neg_inf", + expression={"$lte": [FLOAT_NEGATIVE_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="-Infinity <= -Infinity", + ), + ExpressionTestCase( + "neg_inf_lte_inf", + expression={"$lte": [FLOAT_NEGATIVE_INFINITY, FLOAT_INFINITY]}, + expected=True, + msg="-Infinity <= Infinity", + ), + ExpressionTestCase( + "inf_lte_neg_inf", + expression={"$lte": [FLOAT_INFINITY, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="Infinity not <= -Infinity", + ), + ExpressionTestCase( + "inf_lte_0", + expression={"$lte": [FLOAT_INFINITY, 0]}, + expected=False, + msg="Infinity not <= 0", + ), + ExpressionTestCase( + "0_lte_inf", expression={"$lte": [0, FLOAT_INFINITY]}, expected=True, msg="0 <= Infinity" + ), + ExpressionTestCase( + "inf_lte_1", + expression={"$lte": [FLOAT_INFINITY, 1]}, + expected=False, + msg="Infinity not <= 1", + ), + ExpressionTestCase( + "1_lte_inf", expression={"$lte": [1, FLOAT_INFINITY]}, expected=True, msg="1 <= Infinity" + ), + ExpressionTestCase( + "neg_inf_lte_0", + expression={"$lte": [FLOAT_NEGATIVE_INFINITY, 0]}, + expected=True, + msg="-Infinity <= 0", + ), + ExpressionTestCase( + "0_lte_neg_inf", + expression={"$lte": [0, FLOAT_NEGATIVE_INFINITY]}, + expected=False, + msg="0 not <= -Infinity", + ), + ExpressionTestCase( + "inf_lte_int32_max", + expression={"$lte": [FLOAT_INFINITY, INT32_MAX]}, + expected=False, + msg="Infinity not <= INT32_MAX", + ), + ExpressionTestCase( + "int32_max_lte_inf", + expression={"$lte": [INT32_MAX, FLOAT_INFINITY]}, + expected=True, + msg="INT32_MAX <= Infinity", + ), +] + +NAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nan_lte_int", expression={"$lte": [FLOAT_NAN, 1]}, expected=True, msg="NaN <= int" + ), + ExpressionTestCase( + "int_lte_nan", expression={"$lte": [1, FLOAT_NAN]}, expected=False, msg="int not <= NaN" + ), + ExpressionTestCase( + "nan_lte_neg_inf", + expression={"$lte": [FLOAT_NAN, FLOAT_NEGATIVE_INFINITY]}, + expected=True, + msg="NaN <= -Infinity in BSON order", + ), +] + +ALL_TESTS = INFINITY_TESTS + NAN_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lte_nan_infinity(collection, test): + """Test $lte 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/lte/test_lte_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_null_missing.py new file mode 100644 index 00000000..d5d934f1 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_null_missing.py @@ -0,0 +1,89 @@ +""" +Tests for $lte null and missing field behavior. + +Covers null propagation, missing field resolution, and null vs BSON types. +""" + +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_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_lte_int", expression={"$lte": [None, 1]}, expected=True, msg="null <= number" + ), + ExpressionTestCase( + "int_lte_null", expression={"$lte": [1, None]}, expected=False, msg="number not <= null" + ), + ExpressionTestCase( + "null_lte_null", expression={"$lte": [None, None]}, expected=True, msg="null <= null" + ), +] + +MISSING_FIELD_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "missing_field_pos1", + expression={"$lte": ["$missing_field", 5]}, + doc={"b": 5}, + expected=True, + msg="missing field resolves to null, null <= 5", + ), + ExpressionTestCase( + "missing_field_pos2", + expression={"$lte": [5, "$missing_field"]}, + doc={"a": 5}, + expected=False, + msg="5 not <= null (missing)", + ), + ExpressionTestCase( + "both_missing", + expression={"$lte": ["$missing1", "$missing2"]}, + doc={"x": 1}, + expected=True, + msg="both missing resolve to null, null <= null", + ), +] + +NULL_MISSING_EQUIV_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "null_field_vs_missing", + expression={"$lte": ["$a", "$nonexistent"]}, + doc={"a": None}, + expected=False, + msg="null field not <= missing", + ), + ExpressionTestCase( + "missing_vs_null_literal", + expression={"$lte": ["$nonexistent", None]}, + doc={}, + expected=True, + msg="missing field <= null literal", + ), + ExpressionTestCase( + "null_vs_null_fields", + expression={"$lte": ["$a", "$b"]}, + doc={"a": None, "b": None}, + expected=True, + msg="null field <= null field (equal)", + ), +] + +ALL_TESTS: list[ExpressionTestCase] = NULL_TESTS + MISSING_FIELD_TESTS + NULL_MISSING_EQUIV_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lte_null_missing(collection, test): + """Test $lte null and missing field behavior.""" + if test.doc: + result = execute_expression_with_insert(collection, test.expression, test.doc) + else: + 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/lte/test_lte_same_type_comparisons.py b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_same_type_comparisons.py new file mode 100644 index 00000000..91371e0e --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/comparisons/lte/test_lte_same_type_comparisons.py @@ -0,0 +1,248 @@ +""" +Tests for $lte same-type comparisons. + +Covers numeric, string, boolean, date, object, array, and sign handling. +""" + +from datetime import datetime + +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 + +NUMERIC_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "greater", + expression={"$lte": [300, 250]}, + expected=False, + msg="300 <= 250", + ), + ExpressionTestCase("less", expression={"$lte": [200, 250]}, expected=True, msg="200 <= 250"), + ExpressionTestCase( + "int_lt", expression={"$lte": [1, 2]}, expected=True, msg="int(1) <= int(2)" + ), + ExpressionTestCase( + "int_gt", expression={"$lte": [2, 1]}, expected=False, msg="int(2) not <= int(1)" + ), + ExpressionTestCase( + "int_eq", expression={"$lte": [1, 1]}, expected=True, msg="int(1) <= int(1)" + ), + ExpressionTestCase( + "long_lt", + expression={"$lte": [Int64(1), Int64(2)]}, + expected=True, + msg="long(1) <= long(2)", + ), + ExpressionTestCase( + "double_lt", + expression={"$lte": [1.5, 2.5]}, + expected=True, + msg="double(1.5) <= double(2.5)", + ), + ExpressionTestCase( + "decimal_lt", + expression={"$lte": [Decimal128("1"), Decimal128("2")]}, + expected=True, + msg="decimal(1) <= decimal(2)", + ), + ExpressionTestCase("zero_self", expression={"$lte": [0, 0]}, expected=True, msg="0 <= 0"), +] + +STRING_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("a_lte_b", expression={"$lte": ["a", "b"]}, expected=True, msg="'a' <= 'b'"), + ExpressionTestCase( + "b_lte_a", expression={"$lte": ["b", "a"]}, expected=False, msg="'b' not <= 'a'" + ), + ExpressionTestCase("a_lte_a", expression={"$lte": ["a", "a"]}, expected=True, msg="'a' <= 'a'"), + ExpressionTestCase( + "abc_lte_abd", expression={"$lte": ["abc", "abd"]}, expected=True, msg="'abc' <= 'abd'" + ), + ExpressionTestCase( + "empty_lte_a", expression={"$lte": ["", "a"]}, expected=True, msg="'' <= 'a'" + ), + ExpressionTestCase( + "a_lte_empty", expression={"$lte": ["a", ""]}, expected=False, msg="'a' not <= ''" + ), + ExpressionTestCase( + "str_empty_lte_empty", expression={"$lte": ["", ""]}, expected=True, msg="'' <= ''" + ), + ExpressionTestCase( + "apple_self", expression={"$lte": ["apple", "apple"]}, expected=True, msg="apple <= apple" + ), + ExpressionTestCase( + "a_lte_A", expression={"$lte": ["a", "A"]}, expected=False, msg="lowercase > uppercase" + ), + ExpressionTestCase( + "abc_lte_ab", expression={"$lte": ["abc", "ab"]}, expected=False, msg="abc not <= ab" + ), + ExpressionTestCase( + "ab_lte_abc", expression={"$lte": ["ab", "abc"]}, expected=True, msg="ab <= abc" + ), + ExpressionTestCase( + "z_lte_Z", expression={"$lte": ["z", "Z"]}, expected=False, msg="z not <= Z" + ), + ExpressionTestCase( + "digit_0_lte_9", expression={"$lte": ["0", "9"]}, expected=True, msg="0 <= 9" + ), + ExpressionTestCase( + "digit_9_lte_0", expression={"$lte": ["9", "0"]}, expected=False, msg="9 not <= 0" + ), + ExpressionTestCase( + "space_lte_empty", expression={"$lte": [" ", ""]}, expected=False, msg="space not <= empty" + ), +] + +BOOLEAN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "false_lte_true", expression={"$lte": [False, True]}, expected=True, msg="false <= true" + ), + ExpressionTestCase( + "true_lte_false", + expression={"$lte": [True, False]}, + expected=False, + msg="true not <= false", + ), + ExpressionTestCase( + "false_lte_false", expression={"$lte": [False, False]}, expected=True, msg="false <= false" + ), + ExpressionTestCase( + "true_lte_true", expression={"$lte": [True, True]}, expected=True, msg="true <= true" + ), +] + +DATE_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "date_lt", + expression={"$lte": [datetime(2024, 1, 1), datetime(2024, 12, 31)]}, + expected=True, + msg="earlier date <= later date", + ), + ExpressionTestCase( + "date_gt", + expression={"$lte": [datetime(2024, 12, 31), datetime(2024, 1, 1)]}, + expected=False, + msg="later date not <= earlier date", + ), + ExpressionTestCase( + "date_eq", + expression={"$lte": [datetime(2024, 1, 1), datetime(2024, 1, 1)]}, + expected=True, + msg="same date <= same date", + ), +] + +OBJECT_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "obj_lt", expression={"$lte": [{"a": 1}, {"a": 2}]}, expected=True, msg="{a:1} <= {a:2}" + ), + ExpressionTestCase( + "obj_gt", + expression={"$lte": [{"a": 2}, {"a": 1}]}, + expected=False, + msg="{a:2} not <= {a:1}", + ), + ExpressionTestCase( + "obj_eq", expression={"$lte": [{"a": 1}, {"a": 1}]}, expected=True, msg="{a:1} <= {a:1}" + ), + ExpressionTestCase( + "empty_lte_obj", expression={"$lte": [{}, {"a": 1}]}, expected=True, msg="{} <= {a:1}" + ), + ExpressionTestCase( + "obj_lte_empty", expression={"$lte": [{"a": 1}, {}]}, expected=False, msg="{a:1} not <= {}" + ), + ExpressionTestCase( + "obj_empty_lte_empty", expression={"$lte": [{}, {}]}, expected=True, msg="{} <= {}" + ), +] + +ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("arr_lt", expression={"$lte": [[1], [2]]}, expected=True, msg="[1] <= [2]"), + ExpressionTestCase( + "arr_gt", expression={"$lte": [[2], [1]]}, expected=False, msg="[2] not <= [1]" + ), + ExpressionTestCase("arr_eq", expression={"$lte": [[1], [1]]}, expected=True, msg="[1] <= [1]"), + ExpressionTestCase( + "empty_lte_arr", expression={"$lte": [[], [1]]}, expected=True, msg="[] <= [1]" + ), + ExpressionTestCase( + "arr_1_lte_empty", expression={"$lte": [[1], []]}, expected=False, msg="[1] not <= []" + ), + ExpressionTestCase( + "prefix_lte_longer", + expression={"$lte": [[1, 2], [1, 2, 3]]}, + expected=True, + msg="shorter prefix <= longer", + ), + ExpressionTestCase( + "element_wise", + expression={"$lte": [[1, 3], [1, 2, 999]]}, + expected=False, + msg="[1,3] not <= [1,2,999] element-wise", + ), + ExpressionTestCase( + "empty_arr_self", expression={"$lte": [[], []]}, expected=True, msg="[] <= []" + ), + ExpressionTestCase( + "arr_12_lte_1", expression={"$lte": [[1, 2], [1]]}, expected=False, msg="[1,2] not <= [1]" + ), + ExpressionTestCase( + "arr_1_lte_12", expression={"$lte": [[1], [1, 2]]}, expected=True, msg="[1] <= [1,2]" + ), + ExpressionTestCase( + "arr_12_self", expression={"$lte": [[1, 2], [1, 2]]}, expected=True, msg="[1,2] <= [1,2]" + ), + ExpressionTestCase( + "arr_null_lte_empty", + expression={"$lte": [[None], []]}, + expected=False, + msg="[null] not <= []", + ), +] + +SIGN_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase("neg_lte_pos", expression={"$lte": [-1, 1]}, expected=True, msg="-1 <= 1"), + ExpressionTestCase( + "pos_lte_neg", expression={"$lte": [1, -1]}, expected=False, msg="1 not <= -1" + ), + ExpressionTestCase("neg_lte_neg", expression={"$lte": [-1, -1]}, expected=True, msg="-1 <= -1"), + ExpressionTestCase("zero_lte_zero", expression={"$lte": [0, 0]}, expected=True, msg="0 <= 0"), + ExpressionTestCase("neg_lte_zero", expression={"$lte": [-1, 0]}, expected=True, msg="-1 <= 0"), + ExpressionTestCase( + "zero_lte_neg", expression={"$lte": [0, -1]}, expected=False, msg="0 not <= -1" + ), +] + +NESTED_ARRAY_TESTS: list[ExpressionTestCase] = [ + ExpressionTestCase( + "nested_1_lte_0", + expression={"$lte": [[[1]], [[0]]]}, + expected=False, + msg="[[1]] not <= [[0]]", + ), +] + +ALL_TESTS: list[ExpressionTestCase] = ( + NUMERIC_TESTS + + STRING_TESTS + + BOOLEAN_TESTS + + DATE_TESTS + + OBJECT_TESTS + + ARRAY_TESTS + + SIGN_TESTS + + NESTED_ARRAY_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_TESTS)) +def test_lte_same_type_comparisons(collection, test): + """Test $lte same-type comparisons.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, expected=test.expected, msg=test.msg)