diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_core.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_core.py new file mode 100644 index 00000000..68c97f78 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_core.py @@ -0,0 +1,304 @@ +""" +Tests for $allElementsTrue core behavior. + +Covers mixed arrays, nested array behavior (does not descend), +element order independence, falsy position testing, scale testing, +large mixed-type arrays, falsy position testing, and scale testing. +""" + +from datetime import datetime + +import pytest +from bson import Binary, Decimal128, Int64, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.allElementsTrue.utils.allElementsTrue_utils import ( # noqa: E501 + AllElementsTrueTest, + build_expr, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params + +_LARGE_TRUTHY = [ + True, + 1, + Int64(1), + 1.5, + Decimal128("1"), + "str", + {"a": 1}, + [1], + ObjectId("507f1f77bcf86cd799439011"), + datetime(2017, 1, 1), + Timestamp(1, 1), + Binary(b"", 0), + Regex("re"), +] + +ALL_ELEMENTS_TRUE_CORE_TESTS: list[AllElementsTrueTest] = [ + # --- Mixed arrays — all truthy --- + AllElementsTrueTest( + "true_1_string", + array=[True, 1, "someString"], + expected=True, + msg="Should return true for mixed truthy types", + ), + AllElementsTrueTest( + "true_seven", + array=[True, "seven"], + expected=True, + msg="Should return true for bool and string", + ), + AllElementsTrueTest( + "all_numeric_nonzero", + array=[1, Int64(2), 3.5, Decimal128("4")], + expected=True, + msg="Should return true for all non-zero numerics", + ), + AllElementsTrueTest( + "mixed_bson_truthy", + array=[ + True, + 1, + Int64(1), + 1.5, + Decimal128("1"), + "str", + {"a": 1}, + [1], + ObjectId("507f1f77bcf86cd799439011"), + datetime(2017, 1, 1), + Timestamp(1, 1), + Binary(b"", 0), + Regex("re"), + ], + expected=True, + msg="Should return true for all BSON truthy types", + ), + # --- Mixed truthy and falsy — returns false --- + AllElementsTrueTest( + "true_false", + array=[True, False], + expected=False, + msg="Should return false for true and false", + ), + AllElementsTrueTest("1_0", array=[1, 0], expected=False, msg="Should return false for 1 and 0"), + AllElementsTrueTest( + "true_null", array=[True, None], expected=False, msg="Should return false for true and null" + ), + AllElementsTrueTest( + "hello_0_true", + array=["hello", 0, True], + expected=False, + msg="Should return false for string with zero", + ), + AllElementsTrueTest( + "null_false_0", + array=[None, False, 0], + expected=False, + msg="Should return false when all elements are falsy", + ), + # --- Single falsy among many truthy --- + AllElementsTrueTest( + "false_at_end", + array=[True, 1, "a", {}, [], False], + expected=False, + msg="Should return false when false is at end", + ), + AllElementsTrueTest( + "null_at_end", + array=[True, 1, "a", {}, [], None], + expected=False, + msg="Should return false when null is at end", + ), + AllElementsTrueTest( + "zero_at_end", + array=[True, 1, "a", {}, [], 0], + expected=False, + msg="Should return false when zero is at end", + ), + # --- Large mixed-type array --- + AllElementsTrueTest( + "all_truthy_large", + array=_LARGE_TRUTHY, + expected=True, + msg="Should return true for large all-truthy array", + ), + AllElementsTrueTest( + "plus_false", + array=_LARGE_TRUTHY + [False], + expected=False, + msg="Should return false for large array with false appended", + ), + AllElementsTrueTest( + "plus_null", + array=_LARGE_TRUTHY + [None], + expected=False, + msg="Should return false for large array with null appended", + ), + AllElementsTrueTest( + "plus_zero", + array=_LARGE_TRUTHY + [0], + expected=False, + msg="Should return false for large array with zero appended", + ), + # --- Nested array behavior — does NOT descend --- + AllElementsTrueTest( + "nested_false", + array=[[False]], + expected=True, + msg="Should return true for nested false element", + ), + AllElementsTrueTest( + "nested_zero", array=[[0]], expected=True, msg="Should return true for nested zero element" + ), + AllElementsTrueTest( + "nested_null", + array=[[None]], + expected=True, + msg="Should return true for nested null element", + ), + AllElementsTrueTest( + "nested_empty", + array=[[]], + expected=True, + msg="Should return true for nested empty array element", + ), + AllElementsTrueTest( + "nested_all_falsy", + array=[[False, 0, None]], + expected=True, + msg="Should return true for nested array with all falsy", + ), + AllElementsTrueTest( + "two_nested_arrays", + array=[[True], [False]], + expected=True, + msg="Should return true for two nested arrays", + ), + AllElementsTrueTest( + "two_empty_nested", + array=[[], []], + expected=True, + msg="Should return true for two empty nested arrays", + ), + AllElementsTrueTest( + "deeply_nested", + array=[[[[False]]]], + expected=True, + msg="Should return true for deeply nested false", + ), + AllElementsTrueTest( + "deeply_nested_zero", + array=[[[[[[0]]]]]], + expected=True, + msg="Should return true for deeply nested 0", + ), + AllElementsTrueTest( + "mixed_nesting_depths", + array=[[False], [[None]], [[[0]]]], + expected=True, + msg="Should return true for mixed nesting depths", + ), + # --- Element order independence --- + AllElementsTrueTest( + "true_1_string_order1", + array=[True, 1, "string"], + expected=True, + msg="Should return true for order 1", + ), + AllElementsTrueTest( + "true_1_string_order2", + array=[1, "string", True], + expected=True, + msg="Should return true for order 2", + ), + AllElementsTrueTest( + "false_1_string_order1", + array=[False, 1, "string"], + expected=False, + msg="Should return false for order 1 with false", + ), + AllElementsTrueTest( + "false_1_string_order2", + array=[1, "string", False], + expected=False, + msg="Should return false for order 2 with false", + ), + # --- Comparison with $anyElementTrue — allElementsTrue side --- + AllElementsTrueTest( + "cmp_empty", array=[], expected=True, msg="Should return true for empty array" + ), + AllElementsTrueTest( + "cmp_true_only", + array=[True], + expected=True, + msg="Should return true for single true element", + ), + AllElementsTrueTest( + "cmp_false_only", + array=[False], + expected=False, + msg="Should return false for single false element", + ), + AllElementsTrueTest( + "cmp_true_false", + array=[True, False], + expected=False, + msg="Should return false for true and false elements", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ALL_ELEMENTS_TRUE_CORE_TESTS)) +def test_allElementsTrue_core(collection, test): + """Test $allElementsTrue core behavior with literal arrays.""" + result = execute_expression(collection, build_expr(test)) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +# --------------------------------------------------------------------------- +# Nested arrays via field reference (requires insert) +# --------------------------------------------------------------------------- +def test_allElementsTrue_field_array_of_arrays_all_truthy(collection): + """Test $allElementsTrue with field containing array of arrays — all truthy as elements.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$arr"]}, {"arr": [[True], [False], [None]]} + ) + assert_expression_result( + result, expected=True, msg="Should return true when each element is an array" + ) + + +def test_allElementsTrue_field_array_with_bare_false(collection): + """Test $allElementsTrue with field containing array with bare false element.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$arr"]}, {"arr": [[True], False]} + ) + assert_expression_result( + result, expected=False, msg="Should return false when array contains bare false element" + ) + + +# --------------------------------------------------------------------------- +# Boolean result type verification +# --------------------------------------------------------------------------- +def test_allElementsTrue_returns_boolean_true(collection): + """Test $allElementsTrue returns exactly boolean true, not 1.""" + result = execute_expression(collection, {"$allElementsTrue": [[True]]}) + assert_expression_result(result, expected=True, msg="Should return boolean true") + + +def test_allElementsTrue_returns_boolean_false(collection): + """Test $allElementsTrue returns exactly boolean false, not 0.""" + result = execute_expression(collection, {"$allElementsTrue": [[False]]}) + assert_expression_result(result, expected=False, msg="Should return boolean false") + + +def test_allElementsTrue_empty_returns_boolean_true(collection): + """Test $allElementsTrue on empty array returns exactly boolean true.""" + result = execute_expression(collection, {"$allElementsTrue": [[]]}) + assert_expression_result(result, expected=True, msg="Empty array should return boolean true") diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_expressions.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_expressions.py new file mode 100644 index 00000000..9808c69f --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_expressions.py @@ -0,0 +1,189 @@ +""" +Tests for $allElementsTrue expression types and field lookups. + +Covers literal/field/expression operator/array expression/composite array inputs +and field path resolution. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR + + +def test_allElementsTrue_literal(collection): + """Test $allElementsTrue with literal array expression.""" + result = execute_expression(collection, {"$allElementsTrue": [[True, 1]]}) + assert_expression_result( + result, expected=True, msg="Should return true for literal truthy array" + ) + + +def test_allElementsTrue_field_path_true(collection): + """Test $allElementsTrue with field path resolving to all-truthy array.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$arr"]}, {"arr": [True, 1]} + ) + assert_expression_result( + result, expected=True, msg="Should return true for field with all truthy values" + ) + + +def test_allElementsTrue_field_path_false(collection): + """Test $allElementsTrue with field path resolving to mixed array.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$arr"]}, {"arr": [True, False]} + ) + assert_expression_result( + result, expected=False, msg="Should return false for field with mixed truthy and falsy" + ) + + +def test_allElementsTrue_nested_field_path(collection): + """Test $allElementsTrue with nested field path.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.b"]}, {"a": {"b": [True, 1]}} + ) + assert_expression_result(result, expected=True, msg="Nested field path should work") + + +def test_allElementsTrue_expression_operator_input(collection): + """Test $allElementsTrue with expression operator ($literal) as input.""" + result = execute_expression(collection, {"$allElementsTrue": [{"$literal": [True, 1]}]}) + assert_expression_result(result, expected=True, msg="Should handle literal expression input") + + +def test_allElementsTrue_array_expression_input_truthy(collection): + """Test $allElementsTrue with array expression input — fields resolve to truthy.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": [["$x", "$y"]]}, {"x": True, "y": 1} + ) + assert_expression_result( + result, expected=True, msg="Array expression with truthy fields should return true" + ) + + +def test_allElementsTrue_array_expression_input_falsy(collection): + """Test $allElementsTrue with array expression input — one field resolves to falsy.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": [["$x", "$y"]]}, {"x": True, "y": 0} + ) + assert_expression_result( + result, expected=False, msg="Array expression with falsy field should return false" + ) + + +def test_allElementsTrue_object_expression_input_error(collection): + """Test $allElementsTrue with object expression input errors (not array).""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": [{"a": "$x"}]}, {"x": 1} + ) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Object expression should error" + ) + + +def test_allElementsTrue_composite_array_path_false(collection): + """Test $allElementsTrue with composite array path resolving to [true, false].""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.b"]}, {"a": [{"b": True}, {"b": False}]} + ) + assert_expression_result( + result, + expected=False, + msg="Should return false for composite path with mixed truthy and falsy", + ) + + +def test_allElementsTrue_composite_array_path_true(collection): + """Test $allElementsTrue with composite array path resolving to [true, 1].""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.b"]}, {"a": [{"b": True}, {"b": 1}]} + ) + assert_expression_result( + result, expected=True, msg="Should return true for composite path with all truthy values" + ) + + +# --------------------------------------------------------------------------- +# Field lookup patterns +# --------------------------------------------------------------------------- +def test_allElementsTrue_simple_field(collection): + """Test $allElementsTrue with simple field path.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$arr"]}, {"arr": [True]} + ) + assert_expression_result(result, expected=True, msg="Simple field should work") + + +def test_allElementsTrue_deep_nested_field(collection): + """Test $allElementsTrue with deep nested field path.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.b.c"]}, {"a": {"b": {"c": [True]}}} + ) + assert_expression_result(result, expected=True, msg="Deep nested field should work") + + +def test_allElementsTrue_composite_array_truthy(collection): + """Test $allElementsTrue with composite array path — all truthy.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.b"]}, {"a": [{"b": 1}, {"b": 2}]} + ) + assert_expression_result( + result, expected=True, msg="Should return true for composite path with all non-zero values" + ) + + +def test_allElementsTrue_array_index_path(collection): + """Test $allElementsTrue with array index path in expression context.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.0"]}, {"a": [[True], [False]]} + ) + # In aggregation expression context, $a.0 on array resolves to [] + assert_expression_result( + result, + expected=True, + msg="Should return true for array index path resolving to empty array", + ) + + +def test_allElementsTrue_numeric_index_on_object_key(collection): + """Test $allElementsTrue with numeric index path on object key.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$a.0"]}, {"a": {"0": [True, 1]}} + ) + assert_expression_result( + result, expected=True, msg="Numeric index on object key should resolve to the field" + ) + + +# --------------------------------------------------------------------------- +# Shorthand syntax (without outer array wrapper) +# --------------------------------------------------------------------------- +def test_allElementsTrue_shorthand_field(collection): + """Test $allElementsTrue shorthand syntax without outer array wrapper.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": "$arr"}, {"arr": [True, 1]} + ) + assert_expression_result(result, expected=True, msg="Shorthand syntax should work") + + +def test_allElementsTrue_shorthand_field_falsy(collection): + """Test $allElementsTrue shorthand syntax with falsy element.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": "$arr"}, {"arr": [True, 0]} + ) + assert_expression_result(result, expected=False, msg="Shorthand with falsy should return false") + + +# --------------------------------------------------------------------------- +# $let variable +# --------------------------------------------------------------------------- +def test_allElementsTrue_let_variable(collection): + """Test $allElementsTrue with $let variable.""" + result = execute_expression( + collection, {"$let": {"vars": {"x": [True, 1]}, "in": {"$allElementsTrue": ["$$x"]}}} + ) + assert_expression_result(result, expected=True, msg="Should handle let variable input") diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_invalid.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_invalid.py new file mode 100644 index 00000000..ff261d07 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_invalid.py @@ -0,0 +1,373 @@ +""" +Tests for $allElementsTrue argument validation, error codes, and invalid inputs. + +Covers argument count errors, non-array argument types, null/missing field +handling, system variable errors, and literal format validation. + +Error 5159200: wrong argument count (0 or 2+ args) +Error 17041: non-array argument at runtime (field resolves to non-array) +""" + +from datetime import datetime + +import pytest +from bson import Binary, Decimal128, Int64, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.allElementsTrue.utils.allElementsTrue_utils import ( # noqa: E501 + AllElementsTrueTest, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ( + ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + EXPRESSION_TYPE_MISMATCH_ERROR, +) +from documentdb_tests.framework.parametrize import pytest_params + +# --------------------------------------------------------------------------- +# Wrong argument count +# --------------------------------------------------------------------------- +WRONG_ARG_COUNT_TESTS: list[AllElementsTrueTest] = [ + AllElementsTrueTest( + "no_args", + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="No arguments should error", + ), + AllElementsTrueTest( + "two_args", + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Two arguments should error", + ), + AllElementsTrueTest( + "three_args", + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Three arguments should error", + ), + AllElementsTrueTest( + "six_args", + error_code=EXPRESSION_TYPE_MISMATCH_ERROR, + msg="Six arguments should error", + ), +] + +_WRONG_ARG_EXPRESSIONS = { + "no_args": {"$allElementsTrue": []}, + "two_args": {"$allElementsTrue": [[True], [False]]}, + "three_args": {"$allElementsTrue": [[True], [False], [1]]}, + "six_args": {"$allElementsTrue": [[], [], [], [], [], []]}, +} + + +@pytest.mark.parametrize("test", pytest_params(WRONG_ARG_COUNT_TESTS)) +def test_allElementsTrue_wrong_arg_count(collection, test): + """Test $allElementsTrue rejects wrong argument counts.""" + result = execute_expression(collection, _WRONG_ARG_EXPRESSIONS[test.id]) + assert_expression_result(result, error_code=test.error_code, msg=test.msg) + + +# --------------------------------------------------------------------------- +# Literal non-array wrapping — [true] is interpreted as bool arg, not array +# --------------------------------------------------------------------------- +def test_allElementsTrue_unwrapped_bool_literal(collection): + """Test $allElementsTrue with [true] (bool, not array) errors.""" + result = execute_expression(collection, {"$allElementsTrue": [True]}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Bool literal should error" + ) + + +def test_allElementsTrue_unwrapped_int_literals(collection): + """Test $allElementsTrue with [1, 2, 3] interpreted as 3 args errors.""" + result = execute_expression(collection, {"$allElementsTrue": [1, 2, 3]}) + assert_expression_result( + result, error_code=EXPRESSION_TYPE_MISMATCH_ERROR, msg="Multiple int literals should error" + ) + + +def test_allElementsTrue_wrapped_int_literals(collection): + """Test $allElementsTrue with [[1, 2, 3]] (wrapped) succeeds.""" + result = execute_expression(collection, {"$allElementsTrue": [[1, 2, 3]]}) + assert_expression_result(result, expected=True, msg="Wrapped literal array should succeed") + + +# --------------------------------------------------------------------------- +# Non-array field types — field resolves to non-array +# --------------------------------------------------------------------------- +NON_ARRAY_FIELD_TESTS: list[AllElementsTrueTest] = [ + AllElementsTrueTest( + "string_field", + document={"a": "hello"}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="String field should error", + ), + AllElementsTrueTest( + "int_field", + document={"a": 1}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Int field should error", + ), + AllElementsTrueTest( + "long_field", + document={"a": Int64(1)}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Long field should error", + ), + AllElementsTrueTest( + "double_field", + document={"a": 1.5}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Double field should error", + ), + AllElementsTrueTest( + "decimal_field", + document={"a": Decimal128("1")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal128 field should error", + ), + AllElementsTrueTest( + "bool_field", + document={"a": True}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Bool field should error", + ), + AllElementsTrueTest( + "null_field", + document={"a": None}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Null field should error", + ), + AllElementsTrueTest( + "object_field", + document={"a": {"x": 1}}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Object field should error", + ), + AllElementsTrueTest( + "objectid_field", + document={"a": ObjectId("507f1f77bcf86cd799439011")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="ObjectId field should error", + ), + AllElementsTrueTest( + "date_field", + document={"a": datetime(2017, 1, 1)}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Date field should error", + ), + AllElementsTrueTest( + "timestamp_field", + document={"a": Timestamp(1, 1)}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Timestamp field should error", + ), + AllElementsTrueTest( + "regex_field", + document={"a": Regex("pattern")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Regex field should error", + ), + AllElementsTrueTest( + "bindata_field", + document={"a": Binary(b"", 0)}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="BinData field should error", + ), + AllElementsTrueTest( + "nan_field", + document={"a": float("nan")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="NaN field should error", + ), + AllElementsTrueTest( + "decimal_nan_field", + document={"a": Decimal128("NaN")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal128 NaN field should error", + ), + AllElementsTrueTest( + "inf_field", + document={"a": float("inf")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Infinity field should error", + ), + AllElementsTrueTest( + "decimal_inf_field", + document={"a": Decimal128("Infinity")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal128 Infinity field should error", + ), + AllElementsTrueTest( + "decimal_neg_inf_field", + document={"a": Decimal128("-Infinity")}, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal128 -Infinity field should error", + ), + AllElementsTrueTest( + "array_field_succeeds", + document={"a": [True]}, + expected=True, + msg="Array field should succeed", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NON_ARRAY_FIELD_TESTS)) +def test_allElementsTrue_non_array_field(collection, test): + """Test $allElementsTrue with field resolving to non-array type.""" + result = execute_expression_with_insert(collection, {"$allElementsTrue": ["$a"]}, test.document) + assert_expression_result( + result, expected=test.expected, error_code=test.error_code, msg=test.msg + ) + + +# --------------------------------------------------------------------------- +# Null/missing field handling +# --------------------------------------------------------------------------- +def test_allElementsTrue_null_field(collection): + """Test $allElementsTrue with field resolving to null errors.""" + result = execute_expression_with_insert(collection, {"$allElementsTrue": ["$a"]}, {"a": None}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null field should error" + ) + + +def test_allElementsTrue_missing_field(collection): + """Test $allElementsTrue with missing field errors.""" + result = execute_expression_with_insert(collection, {"$allElementsTrue": ["$a"]}, {"b": 1}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Missing field should error" + ) + + +def test_allElementsTrue_missing_field_empty_doc(collection): + """Test $allElementsTrue with missing field on empty doc errors.""" + result = execute_expression_with_insert(collection, {"$allElementsTrue": ["$nonexistent"]}, {}) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Missing field on empty doc should error", + ) + + +def test_allElementsTrue_missing_field_with_other_fields(collection): + """Test $allElementsTrue with missing field when doc has other fields.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$missing_field"]}, {"a": [1, 2]} + ) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Missing field should error even when doc has other fields", + ) + + +# --------------------------------------------------------------------------- +# Null literal argument +# --------------------------------------------------------------------------- +def test_allElementsTrue_null_literal_arg(collection): + """Test $allElementsTrue with null literal argument errors.""" + result = execute_expression(collection, {"$allElementsTrue": [None]}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null literal arg should error" + ) + + +def test_allElementsTrue_null_inside_array_arg(collection): + """Test $allElementsTrue with [None] inside array argument — null as element, not arg.""" + result = execute_expression(collection, {"$allElementsTrue": [[None]]}) + assert_expression_result( + result, expected=False, msg="Should return false when null is a falsy element" + ) + + +# --------------------------------------------------------------------------- +# Non-array literal arguments +# --------------------------------------------------------------------------- +def test_allElementsTrue_string_literal(collection): + """Test $allElementsTrue with string literal errors.""" + result = execute_expression(collection, {"$allElementsTrue": "not_an_array"}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="String literal should error" + ) + + +def test_allElementsTrue_int_literal(collection): + """Test $allElementsTrue with int literal errors.""" + result = execute_expression(collection, {"$allElementsTrue": 123}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Int literal should error" + ) + + +def test_allElementsTrue_null_top_level(collection): + """Test $allElementsTrue with null top-level errors.""" + result = execute_expression(collection, {"$allElementsTrue": None}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null top-level should error" + ) + + +# --------------------------------------------------------------------------- +# System variables — resolve to non-array +# --------------------------------------------------------------------------- +def test_allElementsTrue_root_variable(collection): + """Test $allElementsTrue with $$ROOT (object, not array) errors.""" + result = execute_expression_with_insert(collection, {"$allElementsTrue": ["$$ROOT"]}, {"a": 1}) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when ROOT variable resolves to object", + ) + + +def test_allElementsTrue_current_variable(collection): + """Test $allElementsTrue with $$CURRENT (object, not array) errors.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$$CURRENT"]}, {"a": 1} + ) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when CURRENT variable resolves to object", + ) + + +def test_allElementsTrue_remove_variable(collection): + """Test $allElementsTrue with $$REMOVE (missing) errors.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$$REMOVE"]}, {"a": 1} + ) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when REMOVE variable resolves to missing", + ) + + +# --------------------------------------------------------------------------- +# Object expression input — not array +# --------------------------------------------------------------------------- +def test_allElementsTrue_object_expression_input(collection): + """Test $allElementsTrue with object expression (not array) errors.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": [{"a": "$x"}]}, {"x": 1} + ) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Object expression should error" + ) + + +# --------------------------------------------------------------------------- +# Missing field path in array argument +# --------------------------------------------------------------------------- +def test_allElementsTrue_missing_field_path_in_array(collection): + """Test $allElementsTrue with ["$not_exist"] (missing field in array arg) errors.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$not_exist"]}, {"a": 1} + ) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Missing field path should error" + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_null_missing.py new file mode 100644 index 00000000..8194b785 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_null_missing.py @@ -0,0 +1,80 @@ +""" +Tests for $allElementsTrue null and missing field propagation. + +Covers null as array element, null as array argument, +and missing field behavior. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR + + +# --------------------------------------------------------------------------- +# Null as array element (falsy) +# --------------------------------------------------------------------------- +def test_allElementsTrue_null_sole_element(collection): + """Test $allElementsTrue with [null] returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[None]]}) + assert_expression_result( + result, expected=False, msg="Should return false for array with only null" + ) + + +def test_allElementsTrue_null_null(collection): + """Test $allElementsTrue with [null, null] returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[None, None]]}) + assert_expression_result( + result, expected=False, msg="Should return false for array with two nulls" + ) + + +def test_allElementsTrue_null_true(collection): + """Test $allElementsTrue with [null, true] returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[None, True]]}) + assert_expression_result( + result, expected=False, msg="Should return false for null combined with true" + ) + + +def test_allElementsTrue_null_1(collection): + """Test $allElementsTrue with [null, 1] returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[None, 1]]}) + assert_expression_result( + result, expected=False, msg="Should return false for null combined with one" + ) + + +def test_allElementsTrue_many_nulls(collection): + """Test $allElementsTrue with 10 nulls returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[None] * 10]}) + assert_expression_result(result, expected=False, msg="Should return false for ten nulls") + + +# --------------------------------------------------------------------------- +# Null as array argument (error — not an array) +# --------------------------------------------------------------------------- +def test_allElementsTrue_null_argument(collection): + """Test $allElementsTrue with null as argument errors.""" + result = execute_expression(collection, {"$allElementsTrue": [None]}) + assert_expression_result( + result, error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null argument should error" + ) + + +# --------------------------------------------------------------------------- +# Missing field as array argument +# --------------------------------------------------------------------------- +def test_allElementsTrue_missing_field_argument(collection): + """Test $allElementsTrue with missing field as argument errors.""" + result = execute_expression_with_insert( + collection, {"$allElementsTrue": ["$missing_field"]}, {"x": 1} + ) + assert_expression_result( + result, + error_code=ALL_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Missing field argument should error", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_scale.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_scale.py new file mode 100644 index 00000000..1b3d7647 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_scale.py @@ -0,0 +1,75 @@ +""" +Tests for $allElementsTrue with large arrays. + +Covers scale testing with 1000+ element arrays, +falsy at different positions, and heavy duplication. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, +) + + +def test_allElementsTrue_large_all_truthy(collection): + """Test $allElementsTrue with 1000 ones returns true.""" + result = execute_expression(collection, {"$allElementsTrue": [[1] * 1000]}) + assert_expression_result(result, expected=True, msg="1000 ones should return true") + + +def test_allElementsTrue_large_falsy_at_end(collection): + """Test $allElementsTrue with 999 ones followed by 0 returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[1] * 999 + [0]]}) + assert_expression_result(result, expected=False, msg="Single falsy at end should return false") + + +def test_allElementsTrue_large_falsy_at_start(collection): + """Test $allElementsTrue with 0 followed by 999 ones returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[0] + [1] * 999]}) + assert_expression_result( + result, expected=False, msg="Single falsy at start should return false" + ) + + +def test_allElementsTrue_large_falsy_at_middle(collection): + """Test $allElementsTrue with falsy at index 500 of 1000-element array.""" + result = execute_expression(collection, {"$allElementsTrue": [[1] * 500 + [0] + [1] * 499]}) + assert_expression_result( + result, expected=False, msg="Single falsy at middle should return false" + ) + + +def test_allElementsTrue_large_all_falsy(collection): + """Test $allElementsTrue with 1000 zeros returns false.""" + result = execute_expression(collection, {"$allElementsTrue": [[0] * 1000]}) + assert_expression_result(result, expected=False, msg="1000 zeros should return false") + + +def test_allElementsTrue_large_range(collection): + """Test $allElementsTrue with array [0..999] returns false (contains zero).""" + result = execute_expression(collection, {"$allElementsTrue": [list(range(1000))]}) + assert_expression_result( + result, expected=False, msg="Should return false for range containing zero" + ) + + +def test_allElementsTrue_large_range_no_zero(collection): + """Test $allElementsTrue with array [1..1000] returns true.""" + result = execute_expression(collection, {"$allElementsTrue": [list(range(1, 1001))]}) + assert_expression_result(result, expected=True, msg="Should return true for range without zero") + + +def test_allElementsTrue_scale_10k(collection): + """Test $allElementsTrue with 10000-element all-truthy array.""" + result = execute_expression(collection, {"$allElementsTrue": [list(range(1, 10001))]}) + assert_expression_result( + result, expected=True, msg="Should return true for 10000 non-zero elements" + ) + + +def test_allElementsTrue_scale_10k_last_falsy(collection): + """Test $allElementsTrue with 10000 elements, last is falsy.""" + result = execute_expression(collection, {"$allElementsTrue": [[1] * 9999 + [0]]}) + assert_expression_result( + result, expected=False, msg="10000 elements with last falsy should return false" + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_truthiness.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_truthiness.py new file mode 100644 index 00000000..59c550d9 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/test_allElementsTrue_truthiness.py @@ -0,0 +1,99 @@ +""" +Tests for $allElementsTrue truthiness — divergent behavior only. + +Common single-element truthiness is tested in test_set_common_truthiness.py. +This file covers multi-element arrays and empty array where $allElementsTrue +differs from $anyElementTrue. +""" + +import pytest +from bson import Decimal128 + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.allElementsTrue.utils.allElementsTrue_utils import ( # noqa: E501 + AllElementsTrueTest, + build_expr, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DECIMAL128_NAN, + DECIMAL128_NEGATIVE_ZERO, + DOUBLE_NEGATIVE_ZERO, + FLOAT_INFINITY, + FLOAT_NAN, +) + +# --------------------------------------------------------------------------- +# Empty array — vacuous truth (differs from $anyElementTrue which returns false) +# --------------------------------------------------------------------------- + + +def test_allElementsTrue_empty_array(collection): + """Test $allElementsTrue with empty array returns true (vacuous truth).""" + result = execute_expression(collection, {"$allElementsTrue": [[]]}) + assert_expression_result(result, expected=True, msg="Empty array should return true") + + +# --------------------------------------------------------------------------- +# Multi-element mixed arrays — all must be truthy +# --------------------------------------------------------------------------- +MULTI_ELEMENT_TESTS: list[AllElementsTrueTest] = [ + AllElementsTrueTest( + "nan_with_zero", + array=[FLOAT_NAN, 0], + expected=False, + msg="Should return false for NaN combined with zero", + ), + AllElementsTrueTest( + "nan_with_null", + array=[FLOAT_NAN, None], + expected=False, + msg="Should return false for NaN combined with null", + ), + AllElementsTrueTest( + "nan_both", + array=[FLOAT_NAN, DECIMAL128_NAN], + expected=True, + msg="Should return true for both NaN types", + ), + AllElementsTrueTest( + "inf_with_zero", + array=[FLOAT_INFINITY, 0], + expected=False, + msg="Should return false for Infinity combined with zero", + ), + AllElementsTrueTest( + "truthy_with_neg_zero", + array=[1, DOUBLE_NEGATIVE_ZERO], + expected=False, + msg="Should return false for truthy with -0.0", + ), + AllElementsTrueTest( + "neg_zero_both", + array=[DOUBLE_NEGATIVE_ZERO, DECIMAL128_NEGATIVE_ZERO], + expected=False, + msg="Should return false for both neg zeros", + ), + AllElementsTrueTest( + "decimal_1_0_truthy", + array=[Decimal128("1.0")], + expected=True, + msg="Should return true for Decimal128 1.0", + ), + AllElementsTrueTest( + "decimal_1_many_zeros_truthy", + array=[Decimal128("1.00000000000000000000000000000000")], + expected=True, + msg="Should return true for Decimal128 1 with trailing zeros", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(MULTI_ELEMENT_TESTS)) +def test_allElementsTrue_multi_element(collection, test): + """Test $allElementsTrue with multi-element arrays (divergent from $anyElementTrue).""" + result = execute_expression(collection, build_expr(test)) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/utils/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/utils/allElementsTrue_utils.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/utils/allElementsTrue_utils.py new file mode 100644 index 00000000..6cc8e370 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/allElementsTrue/utils/allElementsTrue_utils.py @@ -0,0 +1,43 @@ +"""Shared test case class and helpers for $allElementsTrue tests.""" + +from dataclasses import dataclass +from typing import Any + +from documentdb_tests.framework.test_case import BaseTestCase + +_OMIT = object() + + +@dataclass(frozen=True) +class AllElementsTrueTest(BaseTestCase): + """Test case for $allElementsTrue operator. + + Fields: + array: The array to evaluate. Used for both literal and field-ref tests. + document: Document to insert for field-ref tests. If provided, array is ignored + and the expression uses "$arr" field reference. + expression: Raw expression override. If provided, used as-is. + """ + + array: Any = _OMIT + document: Any = _OMIT + expression: Any = _OMIT + + +def build_expr(test: AllElementsTrueTest): + """Build $allElementsTrue expression with literal array value.""" + if test.expression is not _OMIT: + return test.expression + return {"$allElementsTrue": [test.array]} + + +def build_expr_with_field_ref(test: AllElementsTrueTest): + """Build $allElementsTrue expression with $arr field reference.""" + return {"$allElementsTrue": ["$arr"]} + + +def build_insert_doc(test: AllElementsTrueTest): + """Build document for insert tests.""" + if test.document is not _OMIT: + return test.document + return {"arr": test.array} diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_core.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_core.py new file mode 100644 index 00000000..3dd4e7cf --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_core.py @@ -0,0 +1,395 @@ +""" +Tests for $anyElementTrue core behavior. + +Covers mixed arrays, all-falsy/all-truthy combinations, empty/nested arrays, +BSON type distinction, shorthand syntax, boolean result type, +order independence, and argument handling. +""" + +from datetime import datetime + +import pytest +from bson import Binary, Decimal128, Int64, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.anyElementTrue.utils.anyElementTrue_utils import ( # noqa: E501 + AnyElementTrueTest, + build_expr, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DECIMAL128_NAN, + DOUBLE_NEGATIVE_ZERO, + FLOAT_INFINITY, +) + +ANYELEMENTTRUE_CORE_TESTS: list[AnyElementTrueTest] = [ + # --- All falsy combined --- + AnyElementTrueTest( + "false_0_null", + array=[False, 0, None], + expected=False, + msg="Should return false when all elements are falsy", + ), + AnyElementTrueTest( + "all_falsy_types", + array=[False, 0, Int64(0), 0.0, Decimal128("0"), None], + expected=False, + msg="Should return false when all falsy types are combined", + ), + AnyElementTrueTest( + "all_nulls", + array=[None, None, None], + expected=False, + msg="Should return false when all elements are null", + ), + AnyElementTrueTest( + "all_false", + array=[False, False, False], + expected=False, + msg="Should return false when all elements are false", + ), + AnyElementTrueTest( + "all_zeros", + array=[0, 0, 0], + expected=False, + msg="Should return false when all elements are zero", + ), + # --- Mixed falsy and truthy --- + AnyElementTrueTest( + "false_true", + array=[False, True], + expected=True, + msg="Should return true for one true among false", + ), + AnyElementTrueTest( + "0_1", array=[0, 1], expected=True, msg="Should return true for one 1 among 0" + ), + AnyElementTrueTest( + "null_hello", + array=[None, "hello"], + expected=True, + msg="Should return true for string among null", + ), + AnyElementTrueTest( + "falsy_plus_1", + array=[False, 0, None, 1], + expected=True, + msg="Should return true for one truthy among many falsy", + ), + AnyElementTrueTest( + "falsy_plus_empty_str", + array=[False, 0, None, ""], + expected=True, + msg="Should return true for empty string among falsy", + ), + AnyElementTrueTest( + "falsy_plus_empty_arr", + array=[False, 0, None, []], + expected=True, + msg="Should return true for empty array among falsy", + ), + AnyElementTrueTest( + "falsy_plus_empty_obj", + array=[False, 0, None, {}], + expected=True, + msg="Should return true for empty object among falsy", + ), + AnyElementTrueTest( + "falsy_plus_objectid", + array=[False, 0, None, ObjectId("507f1f77bcf86cd799439011")], + expected=True, + msg="Should return true for ObjectId among falsy", + ), + AnyElementTrueTest( + "falsy_plus_date", + array=[False, 0, None, datetime(2017, 1, 1)], + expected=True, + msg="Should return true for date among falsy", + ), + AnyElementTrueTest( + "falsy_plus_timestamp", + array=[False, 0, None, Timestamp(0, 1)], + expected=True, + msg="Should return true for Timestamp among falsy", + ), + AnyElementTrueTest( + "falsy_plus_bindata", + array=[False, 0, None, Binary(b"", 0)], + expected=True, + msg="Should return true for BinData among falsy", + ), + AnyElementTrueTest( + "falsy_plus_regex", + array=[False, 0, None, Regex("regex")], + expected=True, + msg="Should return true for regex among falsy", + ), + AnyElementTrueTest( + "falsy_plus_object", + array=[False, 0, None, {"a": 1}], + expected=True, + msg="Should return true for object among falsy", + ), + AnyElementTrueTest( + "all_falsy_plus_empty_str", + array=[False, 0, Int64(0), 0.0, Decimal128("0"), None, ""], + expected=True, + msg="Should return true when empty string added to falsy", + ), + # --- Empty array and nested arrays --- + AnyElementTrueTest( + "empty_array", array=[], expected=False, msg="Should return false for empty array" + ), + AnyElementTrueTest( + "nested_false", + array=[[False]], + expected=True, + msg="Should return true for nested false element", + ), + AnyElementTrueTest( + "nested_zero", array=[[0]], expected=True, msg="Should return true for nested zero element" + ), + AnyElementTrueTest( + "nested_null", + array=[[None]], + expected=True, + msg="Should return true for nested null element", + ), + AnyElementTrueTest( + "nested_all_falsy", + array=[[False, 0, None]], + expected=True, + msg="Should return true for nested array with all falsy", + ), + AnyElementTrueTest( + "empty_arr_and_false", + array=[[], False], + expected=True, + msg="Should return true for empty array element", + ), + AnyElementTrueTest( + "nested_false_and_false", + array=[[False], False], + expected=True, + msg="Should return true for nested false array element", + ), + AnyElementTrueTest( + "multiple_nested", + array=[[False], [0], [None]], + expected=True, + msg="Should return true for multiple nested arrays", + ), + AnyElementTrueTest( + "deeply_nested_false", + array=[[[[[[False]]]]]], + expected=True, + msg="Should return true for deeply nested false", + ), + AnyElementTrueTest( + "deeply_nested_zero", + array=[[[[[[0]]]]]], + expected=True, + msg="Should return true for deeply nested zero", + ), + AnyElementTrueTest( + "deeply_nested_empty", + array=[[[[[[]]]]]], + expected=True, + msg="Should return true for deeply nested empty", + ), + AnyElementTrueTest( + "nested_nan", + array=[[DECIMAL128_NAN]], + expected=True, + msg="Should return true for nested NaN array", + ), + AnyElementTrueTest( + "nested_inf", + array=[[FLOAT_INFINITY]], + expected=True, + msg="Should return true for nested Infinity array", + ), + AnyElementTrueTest( + "mixed_nesting_depths", + array=[[False], [[None]], [[[0]]]], + expected=True, + msg="Should return true for mixed nesting depths", + ), + # --- Order independence --- + AnyElementTrueTest( + "false_true_order", + array=[False, True], + expected=True, + msg="Should return true regardless of element order", + ), + AnyElementTrueTest( + "true_false_order", + array=[True, False], + expected=True, + msg="Should return true regardless of reversed element order", + ), + AnyElementTrueTest( + "0_1_null", + array=[0, 1, None], + expected=True, + msg="Should return true when array has mixed falsy and truthy", + ), + AnyElementTrueTest( + "null_0_1", + array=[None, 0, 1], + expected=True, + msg="Should return true when truthy element is at end", + ), + AnyElementTrueTest( + "1_null_0", + array=[1, None, 0], + expected=True, + msg="Should return true when truthy element is at start", + ), + # --- BSON type distinction --- + AnyElementTrueTest( + "false_and_0", array=[False, 0], expected=False, msg="Should return false for false and 0" + ), + AnyElementTrueTest( + "false_and_null", + array=[False, None], + expected=False, + msg="Should return false for false and null", + ), + AnyElementTrueTest( + "empty_str_and_null", + array=["", None], + expected=True, + msg="Should return true for empty string with null", + ), + AnyElementTrueTest( + "neg_zero_and_zero", + array=[DOUBLE_NEGATIVE_ZERO, 0.0], + expected=False, + msg="Should return false for -0.0 and 0.0", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ANYELEMENTTRUE_CORE_TESTS)) +def test_anyElementTrue_core(collection, test): + """Test $anyElementTrue core behavior with literal arrays.""" + result = execute_expression(collection, build_expr(test)) + assert_expression_result(result, expected=test.expected, msg=test.msg) + + +# --------------------------------------------------------------------------- +# Boolean result type verification +# --------------------------------------------------------------------------- +def test_anyElementTrue_returns_boolean_true(collection): + """Test $anyElementTrue returns exactly boolean true, not 1.""" + result = execute_expression(collection, {"$anyElementTrue": [[True]]}) + assert_expression_result(result, expected=True, msg="Should return boolean true") + + +def test_anyElementTrue_returns_boolean_false(collection): + """Test $anyElementTrue returns exactly boolean false, not 0.""" + result = execute_expression(collection, {"$anyElementTrue": [[False]]}) + assert_expression_result(result, expected=False, msg="Should return boolean false") + + +def test_anyElementTrue_empty_returns_boolean_false(collection): + """Test $anyElementTrue on empty array returns exactly boolean false.""" + result = execute_expression(collection, {"$anyElementTrue": [[]]}) + assert_expression_result(result, expected=False, msg="Empty array should return boolean false") + + +# --------------------------------------------------------------------------- +# Shorthand syntax +# --------------------------------------------------------------------------- +def test_anyElementTrue_shorthand_field(collection): + """Test $anyElementTrue shorthand syntax without outer array wrapper.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": "$arr"}, {"arr": [True, False]} + ) + assert_expression_result(result, expected=True, msg="Shorthand syntax should work") + + +def test_anyElementTrue_shorthand_field_all_falsy(collection): + """Test $anyElementTrue shorthand syntax with all falsy array.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": "$arr"}, {"arr": [0, False]} + ) + assert_expression_result( + result, expected=False, msg="Shorthand with all falsy should return false" + ) + + +# --------------------------------------------------------------------------- +# Argument handling +# --------------------------------------------------------------------------- +def test_anyElementTrue_single_arg_literal(collection): + """Test $anyElementTrue with single literal array argument.""" + result = execute_expression(collection, {"$anyElementTrue": [[True, False]]}) + assert_expression_result(result, expected=True, msg="Single literal array argument should work") + + +def test_anyElementTrue_single_arg_field(collection): + """Test $anyElementTrue with single field path argument.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [True]} + ) + assert_expression_result(result, expected=True, msg="Single field path argument should work") + + +# --------------------------------------------------------------------------- +# Field path resolution +# --------------------------------------------------------------------------- +def test_anyElementTrue_field_all_true(collection): + """Test $anyElementTrue with field path resolving to all-true array.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [True, True]} + ) + assert_expression_result(result, expected=True, msg="All true field should return true") + + +def test_anyElementTrue_field_some_true(collection): + """Test $anyElementTrue with field path resolving to mixed array.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [True, False]} + ) + assert_expression_result(result, expected=True, msg="Some true field should return true") + + +def test_anyElementTrue_field_none_true(collection): + """Test $anyElementTrue with field path resolving to all-false array.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [0, False]} + ) + assert_expression_result(result, expected=False, msg="None true field should return false") + + +def test_anyElementTrue_field_null_input(collection): + """Test $anyElementTrue with field containing [null].""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [None]} + ) + assert_expression_result( + result, expected=False, msg="Should return false for array with only null" + ) + + +def test_anyElementTrue_field_null_true(collection): + """Test $anyElementTrue with field containing [null, true].""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [None, True]} + ) + assert_expression_result( + result, expected=True, msg="Should return true for array with null and true" + ) + + +def test_anyElementTrue_field_empty(collection): + """Test $anyElementTrue with field containing empty array.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$arr"]}, {"arr": []}) + assert_expression_result(result, expected=False, msg="Empty array field should return false") diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_expressions.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_expressions.py new file mode 100644 index 00000000..09bf8d0d --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_expressions.py @@ -0,0 +1,123 @@ +""" +Tests for $anyElementTrue expression type coverage and field lookup. + +Covers literal, field path, nested field path, expression operator input, +system variables, $let variables, composite array paths, and array index paths. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR + + +# --------------------------------------------------------------------------- +# Expression type smoke tests +# --------------------------------------------------------------------------- +def test_anyElementTrue_literal(collection): + """Test $anyElementTrue with literal array.""" + result = execute_expression(collection, {"$anyElementTrue": [[True, False]]}) + assert_expression_result(result, expected=True, msg="Literal array should work") + + +def test_anyElementTrue_field_path(collection): + """Test $anyElementTrue with field path.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$arr"]}, {"arr": [True, False]} + ) + assert_expression_result(result, expected=True, msg="Field path should work") + + +def test_anyElementTrue_nested_field_path(collection): + """Test $anyElementTrue with nested field path.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$a.b"]}, {"a": {"b": [True, False]}} + ) + assert_expression_result(result, expected=True, msg="Nested field path should work") + + +def test_anyElementTrue_expression_operator(collection): + """Test $anyElementTrue with expression operator as argument.""" + result = execute_expression( + collection, {"$anyElementTrue": [{"$concatArrays": [[True], [False]]}]} + ) + assert_expression_result(result, expected=True, msg="Expression operator argument should work") + + +def test_anyElementTrue_system_var_root(collection): + """Test $anyElementTrue with $$ROOT.arr system variable.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$$ROOT.arr"]}, {"arr": [True]} + ) + assert_expression_result( + result, expected=True, msg="Should handle ROOT variable with field path" + ) + + +def test_anyElementTrue_system_var_current(collection): + """Test $anyElementTrue with $$CURRENT.arr system variable.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$$CURRENT.arr"]}, {"arr": [True]} + ) + assert_expression_result( + result, expected=True, msg="Should handle CURRENT variable with field path" + ) + + +def test_anyElementTrue_let_variable(collection): + """Test $anyElementTrue with $let variable.""" + result = execute_expression( + collection, {"$let": {"vars": {"x": [True, False]}, "in": {"$anyElementTrue": ["$$x"]}}} + ) + assert_expression_result(result, expected=True, msg="Should handle let variable input") + + +# --------------------------------------------------------------------------- +# Field lookup coverage +# --------------------------------------------------------------------------- +def test_anyElementTrue_simple_field(collection): + """Test $anyElementTrue with simple field lookup.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$arr"]}, {"arr": [1]}) + assert_expression_result(result, expected=True, msg="Simple field should work") + + +def test_anyElementTrue_nested_field(collection): + """Test $anyElementTrue with nested object field lookup.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$a.b"]}, {"a": {"b": [1]}} + ) + assert_expression_result(result, expected=True, msg="Nested field should work") + + +def test_anyElementTrue_nonexistent_field(collection): + """Test $anyElementTrue with non-existent field.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$nonexistent"]}, {"x": 1} + ) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Non-existent field should error" + ) + + +def test_anyElementTrue_composite_array_path(collection): + """Test $anyElementTrue with composite array path.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$a.b"]}, {"a": [{"b": [1]}, {"b": [2]}]} + ) + assert_expression_result( + result, expected=True, msg="Should return true for composite array path" + ) + + +def test_anyElementTrue_array_index_path(collection): + """Test $anyElementTrue with array index path in expression context.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$a.0"]}, {"a": [[1, 2], [3, 4]]} + ) + assert_expression_result( + result, + expected=False, + msg="Should return false for array index path resolving to empty array", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_invalid.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_invalid.py new file mode 100644 index 00000000..fb19699f --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_invalid.py @@ -0,0 +1,357 @@ +""" +Tests for $anyElementTrue error handling and invalid inputs. + +Covers non-array argument errors, wrong argument count, +field path resolving to non-array, and missing field errors. +""" + +from datetime import datetime + +import pytest +from bson import Binary, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.anyElementTrue.utils.anyElementTrue_utils import ( # noqa: E501 + AnyElementTrueTest, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ( + ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + EXPRESSION_TYPE_MISMATCH_ERROR, +) +from documentdb_tests.framework.parametrize import pytest_params + +# --------------------------------------------------------------------------- +# Non-array argument errors (error code 17041) +# --------------------------------------------------------------------------- +NON_ARRAY_FIELD_TESTS: list[AnyElementTrueTest] = [ + AnyElementTrueTest( + "string_field", + document={"A": "red"}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="String field should error", + ), + AnyElementTrueTest( + "int_field", + document={"A": 1}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Int field should error", + ), + AnyElementTrueTest( + "long_field", + document={"A": Int64(1)}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Long field should error", + ), + AnyElementTrueTest( + "double_field", + document={"A": 1.5}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Double field should error", + ), + AnyElementTrueTest( + "decimal_field", + document={"A": Decimal128("1")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal128 field should error", + ), + AnyElementTrueTest( + "bool_field", + document={"A": True}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Bool field should error", + ), + AnyElementTrueTest( + "object_field", + document={"A": {"a": 1}}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Object field should error", + ), + AnyElementTrueTest( + "objectid_field", + document={"A": ObjectId("507f1f77bcf86cd799439011")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="ObjectId field should error", + ), + AnyElementTrueTest( + "date_field", + document={"A": datetime(2017, 1, 1)}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Date field should error", + ), + AnyElementTrueTest( + "timestamp_field", + document={"A": Timestamp(315532800, 0)}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Timestamp field should error", + ), + AnyElementTrueTest( + "bindata_field", + document={"A": Binary(b"\x62\x25", 2)}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="BinData field should error", + ), + AnyElementTrueTest( + "regex_field", + document={"A": Regex("[a-m]")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Regex field should error", + ), + AnyElementTrueTest( + "nan_double_field", + document={"A": float("nan")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="NaN double field should error", + ), + AnyElementTrueTest( + "nan_decimal_field", + document={"A": Decimal128("NaN")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal NaN field should error", + ), + AnyElementTrueTest( + "inf_field", + document={"A": float("inf")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Infinity field should error", + ), + AnyElementTrueTest( + "decimal_inf_field", + document={"A": Decimal128("Infinity")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal Infinity field should error", + ), + AnyElementTrueTest( + "decimal_neg_inf_field", + document={"A": Decimal128("-Infinity")}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Decimal -Infinity field should error", + ), + AnyElementTrueTest( + "minkey_field", + document={"A": MinKey()}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="MinKey field should error", + ), + AnyElementTrueTest( + "maxkey_field", + document={"A": MaxKey()}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="MaxKey field should error", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NON_ARRAY_FIELD_TESTS)) +def test_anyElementTrue_non_array_field(collection, test): + """Test $anyElementTrue errors when field resolves to non-array type.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$A"]}, test.document) + assert_expression_result(result, error_code=test.error_code, msg=test.msg) + + +# --------------------------------------------------------------------------- +# Non-array literal argument errors +# --------------------------------------------------------------------------- +NON_ARRAY_LITERAL_TESTS: list[AnyElementTrueTest] = [ + AnyElementTrueTest( + "literal_string", + expression={"$anyElementTrue": ["hello"]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal string should error", + ), + AnyElementTrueTest( + "literal_int", + expression={"$anyElementTrue": [1]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal int should error", + ), + AnyElementTrueTest( + "literal_long", + expression={"$anyElementTrue": [Int64(1)]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal long should error", + ), + AnyElementTrueTest( + "literal_double", + expression={"$anyElementTrue": [1.5]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal double should error", + ), + AnyElementTrueTest( + "literal_decimal", + expression={"$anyElementTrue": [Decimal128("1")]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal decimal should error", + ), + AnyElementTrueTest( + "literal_bool", + expression={"$anyElementTrue": [True]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal bool should error", + ), + AnyElementTrueTest( + "literal_null", + expression={"$anyElementTrue": [None]}, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Literal null should error", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NON_ARRAY_LITERAL_TESTS)) +def test_anyElementTrue_non_array_literal(collection, test): + """Test $anyElementTrue errors when argument is non-array literal.""" + result = execute_expression(collection, test.expression) + assert_expression_result(result, error_code=test.error_code, msg=test.msg) + + +# --------------------------------------------------------------------------- +# Wrong argument count +# --------------------------------------------------------------------------- +def test_anyElementTrue_no_arguments(collection): + """Test $anyElementTrue with no arguments errors.""" + result = execute_expression(collection, {"$anyElementTrue": []}) + assert_expression_result( + result, error_code=EXPRESSION_TYPE_MISMATCH_ERROR, msg="No arguments should error" + ) + + +def test_anyElementTrue_two_arguments(collection): + """Test $anyElementTrue with two arguments errors.""" + result = execute_expression(collection, {"$anyElementTrue": [[1], [2]]}) + assert_expression_result( + result, error_code=EXPRESSION_TYPE_MISMATCH_ERROR, msg="Two arguments should error" + ) + + +# --------------------------------------------------------------------------- +# Field path resolving to non-array +# --------------------------------------------------------------------------- +def test_anyElementTrue_field_int(collection): + """Test $anyElementTrue errors when field path resolves to int.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$x"]}, {"x": 5}) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Int field should error" + ) + + +def test_anyElementTrue_field_string(collection): + """Test $anyElementTrue errors when field path resolves to string.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$x"]}, {"x": "hello"}) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="String field should error" + ) + + +def test_anyElementTrue_field_bool(collection): + """Test $anyElementTrue errors when field path resolves to bool.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$x"]}, {"x": True}) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Bool field should error" + ) + + +def test_anyElementTrue_field_object(collection): + """Test $anyElementTrue errors when field path resolves to object.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$x"]}, {"x": {"a": 1}} + ) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Object field should error" + ) + + +def test_anyElementTrue_field_null(collection): + """Test $anyElementTrue errors when field path resolves to null.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$x"]}, {"x": None}) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null field should error" + ) + + +# --------------------------------------------------------------------------- +# Missing field errors +# --------------------------------------------------------------------------- +def test_anyElementTrue_missing_field(collection): + """Test $anyElementTrue errors when field does not exist.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$nonexistent"]}, {"x": 1} + ) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Missing field should error" + ) + + +def test_anyElementTrue_missing_field_shorthand(collection): + """Test $anyElementTrue shorthand errors when field does not exist.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": "$not_exist"}, {"x": 1}) + assert_expression_result( + result, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Missing field shorthand should error", + ) + + +# --------------------------------------------------------------------------- +# Non-existent field in literal array (resolves to missing/null, not error) +# --------------------------------------------------------------------------- +def test_anyElementTrue_nonexistent_in_literal_array(collection): + """Test $anyElementTrue with non-existent field inside literal array returns false.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": [["$non_existent_field"]]}, {"x": 1} + ) + assert_expression_result( + result, + expected=False, + msg="Should return false for non-existent field in literal array resolving to missing", + ) + + +def test_anyElementTrue_nonexistent_plus_true_in_literal_array(collection): + """Test $anyElementTrue with non-existent field plus true in literal array returns true.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": [["$non_existent_field", True]]}, {"x": 1} + ) + assert_expression_result( + result, + expected=True, + msg="Should return true for non-existent field combined with true in literal array", + ) + + +# --------------------------------------------------------------------------- +# System variables — resolve to non-array +# --------------------------------------------------------------------------- +def test_anyElementTrue_root_variable(collection): + """Test $anyElementTrue with $$ROOT (object, not array) errors.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$$ROOT"]}, {"a": 1}) + assert_expression_result( + result, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when ROOT variable resolves to object", + ) + + +def test_anyElementTrue_current_variable(collection): + """Test $anyElementTrue with $$CURRENT (object, not array) errors.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$$CURRENT"]}, {"a": 1} + ) + assert_expression_result( + result, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when CURRENT variable resolves to object", + ) + + +def test_anyElementTrue_remove_variable(collection): + """Test $anyElementTrue with $$REMOVE (missing) errors.""" + result = execute_expression_with_insert(collection, {"$anyElementTrue": ["$$REMOVE"]}, {"a": 1}) + assert_expression_result( + result, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Should error when REMOVE variable resolves to missing", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_null_missing.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_null_missing.py new file mode 100644 index 00000000..00421e26 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_null_missing.py @@ -0,0 +1,96 @@ +""" +Tests for $anyElementTrue null and missing field propagation. + +Covers null as array element, null as array argument, +and missing field behavior. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.error_codes import ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR + + +# --------------------------------------------------------------------------- +# Null as array element (falsy) +# --------------------------------------------------------------------------- +def test_anyElementTrue_null_sole_element(collection): + """Test $anyElementTrue with [null] returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None]]}) + assert_expression_result( + result, expected=False, msg="Should return false for array with only null" + ) + + +def test_anyElementTrue_null_null(collection): + """Test $anyElementTrue with [null, null] returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None, None]]}) + assert_expression_result( + result, expected=False, msg="Should return false for array with two nulls" + ) + + +def test_anyElementTrue_null_true(collection): + """Test $anyElementTrue with [null, true] returns true.""" + result = execute_expression(collection, {"$anyElementTrue": [[None, True]]}) + assert_expression_result( + result, expected=True, msg="Should return true for null combined with true" + ) + + +def test_anyElementTrue_null_false(collection): + """Test $anyElementTrue with [null, false] returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None, False]]}) + assert_expression_result( + result, expected=False, msg="Should return false for null combined with false" + ) + + +def test_anyElementTrue_null_1(collection): + """Test $anyElementTrue with [null, 1] returns true.""" + result = execute_expression(collection, {"$anyElementTrue": [[None, 1]]}) + assert_expression_result( + result, expected=True, msg="Should return true for null combined with one" + ) + + +def test_anyElementTrue_null_0(collection): + """Test $anyElementTrue with [null, 0] returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None, 0]]}) + assert_expression_result( + result, expected=False, msg="Should return false for null combined with zero" + ) + + +def test_anyElementTrue_many_nulls(collection): + """Test $anyElementTrue with 10 nulls returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None] * 10]}) + assert_expression_result(result, expected=False, msg="Should return false for ten nulls") + + +# --------------------------------------------------------------------------- +# Null as array argument (error — not an array) +# --------------------------------------------------------------------------- +def test_anyElementTrue_null_argument(collection): + """Test $anyElementTrue with null as argument errors.""" + result = execute_expression(collection, {"$anyElementTrue": [None]}) + assert_expression_result( + result, error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, msg="Null argument should error" + ) + + +# --------------------------------------------------------------------------- +# Missing field as array argument +# --------------------------------------------------------------------------- +def test_anyElementTrue_missing_field_argument(collection): + """Test $anyElementTrue with missing field as argument errors.""" + result = execute_expression_with_insert( + collection, {"$anyElementTrue": ["$missing_field"]}, {"x": 1} + ) + assert_expression_result( + result, + error_code=ANY_ELEMENTS_TRUE_NON_ARRAY_ERROR, + msg="Missing field argument should error", + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_scale.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_scale.py new file mode 100644 index 00000000..a5723bae --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_scale.py @@ -0,0 +1,65 @@ +""" +Tests for $anyElementTrue with large arrays. + +Covers scale testing with 1000+ element arrays, +truthy at different positions, and heavy duplication. +""" + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, +) + + +def test_anyElementTrue_large_all_falsy(collection): + """Test $anyElementTrue with 1000 zeros returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[0] * 1000]}) + assert_expression_result(result, expected=False, msg="1000 zeros should return false") + + +def test_anyElementTrue_large_truthy_at_end(collection): + """Test $anyElementTrue with 999 zeros followed by 1 returns true.""" + result = execute_expression(collection, {"$anyElementTrue": [[0] * 999 + [1]]}) + assert_expression_result(result, expected=True, msg="Single truthy at end should return true") + + +def test_anyElementTrue_large_truthy_at_start(collection): + """Test $anyElementTrue with 1 followed by 999 zeros returns true.""" + result = execute_expression(collection, {"$anyElementTrue": [[1] + [0] * 999]}) + assert_expression_result(result, expected=True, msg="Single truthy at start should return true") + + +def test_anyElementTrue_large_all_truthy(collection): + """Test $anyElementTrue with 1000 ones returns true.""" + result = execute_expression(collection, {"$anyElementTrue": [[1] * 1000]}) + assert_expression_result(result, expected=True, msg="1000 ones should return true") + + +def test_anyElementTrue_large_heavy_duplication(collection): + """Test $anyElementTrue with 500 nulls and 500 false values returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[None] * 500 + [False] * 500]}) + assert_expression_result( + result, expected=False, msg="500 nulls + 500 false should return false" + ) + + +def test_anyElementTrue_large_range(collection): + """Test $anyElementTrue with array [0..999] returns true (contains non-zero).""" + result = execute_expression(collection, {"$anyElementTrue": [list(range(1000))]}) + assert_expression_result( + result, expected=True, msg="Should return true for range containing non-zero values" + ) + + +def test_anyElementTrue_scale_10k_all_falsy(collection): + """Test $anyElementTrue with 10000 zeros returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[0] * 10000]}) + assert_expression_result(result, expected=False, msg="10000 zeros should return false") + + +def test_anyElementTrue_scale_10k_last_truthy(collection): + """Test $anyElementTrue with 10000 elements, last is truthy.""" + result = execute_expression(collection, {"$anyElementTrue": [[0] * 9999 + [1]]}) + assert_expression_result( + result, expected=True, msg="10000 elements with last truthy should return true" + ) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_truthiness.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_truthiness.py new file mode 100644 index 00000000..d2a9a129 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/test_anyElementTrue_truthiness.py @@ -0,0 +1,99 @@ +""" +Tests for $anyElementTrue truthiness — divergent behavior only. + +Common single-element truthiness is tested in test_set_common_truthiness.py. +This file covers multi-element arrays and empty array where $anyElementTrue +differs from $allElementsTrue. +""" + +import pytest +from bson import Decimal128 + +from documentdb_tests.compatibility.tests.core.operator.expressions.set.anyElementTrue.utils.anyElementTrue_utils import ( # noqa: E501 + AnyElementTrueTest, + build_expr, +) +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, +) +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import ( + DECIMAL128_NAN, + DECIMAL128_NEGATIVE_ZERO, + DOUBLE_NEGATIVE_ZERO, + FLOAT_INFINITY, + FLOAT_NAN, +) + +# --------------------------------------------------------------------------- +# Empty array — returns false (differs from $allElementsTrue which returns true) +# --------------------------------------------------------------------------- + + +def test_anyElementTrue_empty_array(collection): + """Test $anyElementTrue with empty array returns false.""" + result = execute_expression(collection, {"$anyElementTrue": [[]]}) + assert_expression_result(result, expected=False, msg="Empty array should return false") + + +# --------------------------------------------------------------------------- +# Multi-element mixed arrays — any truthy is enough +# --------------------------------------------------------------------------- +MULTI_ELEMENT_TESTS: list[AnyElementTrueTest] = [ + AnyElementTrueTest( + "nan_with_zero", + array=[FLOAT_NAN, 0], + expected=True, + msg="Should return true for NaN combined with zero", + ), + AnyElementTrueTest( + "nan_with_null", + array=[FLOAT_NAN, None], + expected=True, + msg="Should return true for NaN combined with null", + ), + AnyElementTrueTest( + "nan_both", + array=[FLOAT_NAN, DECIMAL128_NAN], + expected=True, + msg="Should return true for both NaN types", + ), + AnyElementTrueTest( + "inf_with_zero", + array=[FLOAT_INFINITY, 0], + expected=True, + msg="Should return true for Infinity combined with zero", + ), + AnyElementTrueTest( + "truthy_with_neg_zero", + array=[1, DOUBLE_NEGATIVE_ZERO], + expected=True, + msg="Should return true for truthy with -0.0", + ), + AnyElementTrueTest( + "neg_zero_both", + array=[DOUBLE_NEGATIVE_ZERO, DECIMAL128_NEGATIVE_ZERO], + expected=False, + msg="Should return false for both neg zeros", + ), + AnyElementTrueTest( + "decimal_1_0_truthy", + array=[Decimal128("1.0")], + expected=True, + msg="Should return true for Decimal128 1.0", + ), + AnyElementTrueTest( + "decimal_1_many_zeros_truthy", + array=[Decimal128("1.00000000000000000000000000000000")], + expected=True, + msg="Should return true for Decimal128 1 with trailing zeros", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(MULTI_ELEMENT_TESTS)) +def test_anyElementTrue_multi_element(collection, test): + """Test $anyElementTrue with multi-element arrays (divergent from $allElementsTrue).""" + result = execute_expression(collection, build_expr(test)) + assert_expression_result(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/utils/__init__.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/utils/anyElementTrue_utils.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/utils/anyElementTrue_utils.py new file mode 100644 index 00000000..11790244 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/anyElementTrue/utils/anyElementTrue_utils.py @@ -0,0 +1,43 @@ +"""Shared test case class and helpers for $anyElementTrue tests.""" + +from dataclasses import dataclass +from typing import Any + +from documentdb_tests.framework.test_case import BaseTestCase + +_OMIT = object() + + +@dataclass(frozen=True) +class AnyElementTrueTest(BaseTestCase): + """Test case for $anyElementTrue operator. + + Fields: + array: The array to evaluate. Used for both literal and field-ref tests. + document: Document to insert for field-ref tests. If provided, array is ignored + and the expression uses "$arr" field reference. + expression: Raw expression override. If provided, used as-is. + """ + + array: Any = _OMIT + document: Any = _OMIT + expression: Any = _OMIT + + +def build_expr(test: AnyElementTrueTest): + """Build $anyElementTrue expression with literal array value.""" + if test.expression is not _OMIT: + return test.expression + return {"$anyElementTrue": [test.array]} + + +def build_expr_with_field_ref(test: AnyElementTrueTest): + """Build $anyElementTrue expression with $arr field reference.""" + return {"$anyElementTrue": ["$arr"]} + + +def build_insert_doc(test: AnyElementTrueTest): + """Build document for insert tests.""" + if test.document is not _OMIT: + return test.document + return {"arr": test.array} diff --git a/documentdb_tests/compatibility/tests/core/operator/expressions/set/test_set_elementTrue_common_truthiness.py b/documentdb_tests/compatibility/tests/core/operator/expressions/set/test_set_elementTrue_common_truthiness.py new file mode 100644 index 00000000..9aa725e3 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/operator/expressions/set/test_set_elementTrue_common_truthiness.py @@ -0,0 +1,362 @@ +""" +Common truthiness tests for $allElementsTrue and $anyElementTrue. + +Single-element arrays produce identical results for both operators. +This file tests the shared truthiness engine once, covering falsy values, +truthy values, NaN, Infinity, negative zero, Decimal128 zero variants, +numeric boundaries, per-type coverage, and duplicates. + +Divergent behavior (multi-element arrays, empty arrays) is tested +in each operator's own truthiness file. +""" + +from datetime import datetime + +import pytest +from bson import Binary, Code, Decimal128, Int64, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.core.operator.expressions.utils.utils import ( + assert_expression_result, + execute_expression, + execute_expression_with_insert, +) +from documentdb_tests.framework.test_constants import ( + DECIMAL128_INFINITY, + DECIMAL128_LARGE_EXPONENT, + DECIMAL128_MAX, + DECIMAL128_MAX_NEGATIVE, + DECIMAL128_MIN, + DECIMAL128_MIN_POSITIVE, + DECIMAL128_NAN, + DECIMAL128_NEGATIVE_INFINITY, + DECIMAL128_NEGATIVE_ZERO, + DECIMAL128_SMALL_EXPONENT, + DOUBLE_MIN_NEGATIVE_SUBNORMAL, + DOUBLE_MIN_SUBNORMAL, + DOUBLE_NEAR_MAX, + DOUBLE_NEAR_MIN, + DOUBLE_NEGATIVE_ZERO, + FLOAT_INFINITY, + FLOAT_NAN, + FLOAT_NEGATIVE_INFINITY, + INT32_MAX, + INT32_MIN, + INT64_MAX, + INT64_MIN, + NUMERIC, + ZERO_NUMERIC, +) + +OPERATORS = ["$allElementsTrue", "$anyElementTrue"] + + +def _build_expr(op, array): + return {op: [array]} + + +def _id(op): + return "all" if op == "$allElementsTrue" else "any" + + +# --------------------------------------------------------------------------- +# Falsy values — sole element returns false for both operators +# --------------------------------------------------------------------------- +FALSY_VALUES = [ + ("false", [False]), + ("int_zero", [0]), + ("long_zero", [Int64(0)]), + ("double_zero", [0.0]), + ("decimal_zero", [Decimal128("0")]), + ("null", [None]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", FALSY_VALUES, ids=[f[0] for f in FALSY_VALUES]) +def test_set_common_falsy(collection, op, name, array): + """Test both set operators return false for sole falsy element.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=False, msg=f"{op} {name} should be false") + + +# --------------------------------------------------------------------------- +# Truthy values — sole element returns true for both operators +# --------------------------------------------------------------------------- +TRUTHY_VALUES = [ + ("true", [True]), + ("int_1", [1]), + ("int_neg1", [-1]), + ("long_1", [Int64(1)]), + ("long_neg1", [Int64(-1)]), + ("double_1_5", [1.5]), + ("double_neg1_5", [-1.5]), + ("decimal_1", [Decimal128("1")]), + ("decimal_neg1", [Decimal128("-1")]), + ("empty_string", [""]), + ("string", ["hello"]), + ("empty_nested_array", [[]]), + ("empty_object", [{}]), + ("object", [{"a": 1}]), + ("objectid", [ObjectId("507f1f77bcf86cd799439011")]), + ("date", [datetime(2017, 1, 1)]), + ("timestamp", [Timestamp(1, 1)]), + ("bindata", [Binary(b"", 0)]), + ("regex", [Regex("pattern")]), + ("javascript", [Code("function(){}")]), + ("timestamp_zero", [Timestamp(0, 0)]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", TRUTHY_VALUES, ids=[t[0] for t in TRUTHY_VALUES]) +def test_set_common_truthy(collection, op, name, array): + """Test both set operators return true for sole truthy element.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=True, msg=f"{op} {name} should be true") + + +# --------------------------------------------------------------------------- +# MinKey/MaxKey truthy — must use field reference +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("op", OPERATORS) +def test_set_common_minkey_truthy(collection, op): + """Test both set operators treat MinKey as truthy.""" + from bson import MinKey + + result = execute_expression_with_insert(collection, {op: ["$arr"]}, {"arr": [MinKey()]}) + assert_expression_result(result, expected=True, msg=f"{op} MinKey should be true") + + +@pytest.mark.parametrize("op", OPERATORS) +def test_set_common_maxkey_truthy(collection, op): + """Test both set operators treat MaxKey as truthy.""" + from bson import MaxKey + + result = execute_expression_with_insert(collection, {op: ["$arr"]}, {"arr": [MaxKey()]}) + assert_expression_result(result, expected=True, msg=f"{op} MaxKey should be true") + + +# --------------------------------------------------------------------------- +# NaN is truthy (sole element) +# --------------------------------------------------------------------------- +NAN_VALUES = [ + ("nan_double", [FLOAT_NAN]), + ("nan_decimal", [DECIMAL128_NAN]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", NAN_VALUES, ids=[n[0] for n in NAN_VALUES]) +def test_set_common_nan_truthy(collection, op, name, array): + """Test both set operators treat NaN as truthy.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=True, msg=f"{op} {name} should be true") + + +# --------------------------------------------------------------------------- +# Infinity is truthy (sole element) +# --------------------------------------------------------------------------- +INF_VALUES = [ + ("inf_double", [FLOAT_INFINITY]), + ("neg_inf_double", [FLOAT_NEGATIVE_INFINITY]), + ("inf_decimal", [DECIMAL128_INFINITY]), + ("neg_inf_decimal", [DECIMAL128_NEGATIVE_INFINITY]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", INF_VALUES, ids=[i[0] for i in INF_VALUES]) +def test_set_common_infinity_truthy(collection, op, name, array): + """Test both set operators treat Infinity as truthy.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=True, msg=f"{op} {name} should be true") + + +# --------------------------------------------------------------------------- +# Negative zero is falsy (sole element) +# --------------------------------------------------------------------------- +NEG_ZERO_VALUES = [ + ("neg_zero_double", [DOUBLE_NEGATIVE_ZERO]), + ("neg_zero_decimal", [DECIMAL128_NEGATIVE_ZERO]), + ("neg_zero_decimal_0_0", [Decimal128("-0.0")]), + ("neg_zero_decimal_E1", [Decimal128("-0E+1")]), + ("neg_zero_decimal_E_1", [Decimal128("-0E-1")]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", NEG_ZERO_VALUES, ids=[n[0] for n in NEG_ZERO_VALUES]) +def test_set_common_negative_zero_falsy(collection, op, name, array): + """Test both set operators treat negative zero as falsy.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=False, msg=f"{op} {name} should be false") + + +# --------------------------------------------------------------------------- +# Decimal128 zero variants are falsy +# --------------------------------------------------------------------------- +DECIMAL_ZERO_VALUES = [ + ("decimal_0_0", [Decimal128("0.0")]), + ("decimal_0_many_zeros", [Decimal128("0.00000000000000000000000000000000")]), + ("decimal_0E6144", [Decimal128("0E+6144")]), + ("decimal_neg0E_6143", [Decimal128("-0E-6143")]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", DECIMAL_ZERO_VALUES, ids=[d[0] for d in DECIMAL_ZERO_VALUES]) +def test_set_common_decimal_zero_falsy(collection, op, name, array): + """Test both set operators treat Decimal128 zero variants as falsy.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=False, msg=f"{op} {name} should be false") + + +# --------------------------------------------------------------------------- +# Numeric boundary values from NUMERIC dataset +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("value", NUMERIC) +def test_set_common_numeric_boundary(collection, op, value): + """Test both set operators with each NUMERIC boundary value.""" + expected = value not in ZERO_NUMERIC + result = execute_expression_with_insert(collection, {op: ["$arr"]}, {"arr": [value]}) + assert_expression_result(result, expected=expected, msg=f"{op} {repr(value)} truthiness") + + +# --------------------------------------------------------------------------- +# Non-zero boundary values are truthy +# --------------------------------------------------------------------------- +BOUNDARY_VALUES = [ + ("int32_min", [INT32_MIN]), + ("int32_max", [INT32_MAX]), + ("int64_min", [INT64_MIN]), + ("int64_max", [INT64_MAX]), + ("double_near_max", [DOUBLE_NEAR_MAX]), + ("double_min_subnormal", [DOUBLE_MIN_SUBNORMAL]), + ("double_min_neg_subnormal", [DOUBLE_MIN_NEGATIVE_SUBNORMAL]), + ("double_near_min", [DOUBLE_NEAR_MIN]), + ("decimal_max", [DECIMAL128_MAX]), + ("decimal_min", [DECIMAL128_MIN]), + ("decimal_large_exp", [DECIMAL128_LARGE_EXPONENT]), + ("decimal_small_exp", [DECIMAL128_SMALL_EXPONENT]), + ("decimal_min_positive", [DECIMAL128_MIN_POSITIVE]), + ("decimal_max_negative", [DECIMAL128_MAX_NEGATIVE]), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array", BOUNDARY_VALUES, ids=[b[0] for b in BOUNDARY_VALUES]) +def test_set_common_boundary_truthy(collection, op, name, array): + """Test both set operators with non-zero numeric boundary values.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=True, msg=f"{op} {name} should be true") + + +# --------------------------------------------------------------------------- +# Per-type coverage — double +# --------------------------------------------------------------------------- +DOUBLE_VALUES = [ + ("double_zero", [0.0], False), + ("double_neg_zero", [-0.0], False), + ("double_small_pos", [0.0000001], True), + ("double_small_neg", [-0.0000001], True), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array,expected", DOUBLE_VALUES, ids=[d[0] for d in DOUBLE_VALUES]) +def test_set_common_double(collection, op, name, array, expected): + """Test both set operators with double values.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=expected, msg=f"{op} {name}") + + +# --------------------------------------------------------------------------- +# Per-type coverage — int +# --------------------------------------------------------------------------- +INT_VALUES = [ + ("int_zero", [0], False), + ("int_1", [1], True), + ("int_neg1", [-1], True), + ("int_max", [INT32_MAX], True), + ("int_min", [INT32_MIN], True), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array,expected", INT_VALUES, ids=[i[0] for i in INT_VALUES]) +def test_set_common_int(collection, op, name, array, expected): + """Test both set operators with int values.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=expected, msg=f"{op} {name}") + + +# --------------------------------------------------------------------------- +# Per-type coverage — long +# --------------------------------------------------------------------------- +LONG_VALUES = [ + ("long_zero", [Int64(0)], False), + ("long_1", [Int64(1)], True), + ("long_neg1", [Int64(-1)], True), + ("long_max", [INT64_MAX], True), + ("long_min", [INT64_MIN], True), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array,expected", LONG_VALUES, ids=[v[0] for v in LONG_VALUES]) +def test_set_common_long(collection, op, name, array, expected): + """Test both set operators with long values.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=expected, msg=f"{op} {name}") + + +# --------------------------------------------------------------------------- +# Per-type coverage — decimal128 +# --------------------------------------------------------------------------- +DECIMAL_VALUES = [ + ("decimal_zero", [Decimal128("0")], False), + ("decimal_neg_zero", [Decimal128("-0")], False), + ("decimal_small_pos", [Decimal128("0.0000001")], True), + ("decimal_small_neg", [Decimal128("-0.0000001")], True), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize("name,array,expected", DECIMAL_VALUES, ids=[d[0] for d in DECIMAL_VALUES]) +def test_set_common_decimal128(collection, op, name, array, expected): + """Test both set operators with Decimal128 values.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=expected, msg=f"{op} {name}") + + +# --------------------------------------------------------------------------- +# undefined/null in array is falsy +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("op", OPERATORS) +def test_set_common_undefined_falsy(collection, op): + """Test both set operators with array containing null returns false.""" + result = execute_expression_with_insert(collection, {op: ["$arr"]}, {"arr": [None]}) + assert_expression_result(result, expected=False, msg=f"{op} null should be false") + + +# --------------------------------------------------------------------------- +# Duplicates (all same value) +# --------------------------------------------------------------------------- +DUPLICATE_VALUES = [ + ("all_true", [True, True, True], True), + ("all_false", [False, False, False], False), + ("all_ones", [1, 1, 1, 1], True), + ("all_zeros", [0, 0, 0], False), + ("all_nulls", [None, None], False), +] + + +@pytest.mark.parametrize("op", OPERATORS) +@pytest.mark.parametrize( + "name,array,expected", DUPLICATE_VALUES, ids=[d[0] for d in DUPLICATE_VALUES] +) +def test_set_common_duplicates(collection, op, name, array, expected): + """Test both set operators with duplicate elements.""" + result = execute_expression(collection, _build_expr(op, array)) + assert_expression_result(result, expected=expected, msg=f"{op} {name}")