Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""
Comment thread
PatersonProjects marked this conversation as resolved.
Tests for $nor query operator argument handling.

Covers valid array argument variations: single expression, multiple expressions,
many expressions, empty object in array, multiple fields in a single expression,
and clause behavior (ordering invariance, nested double negation, combined filters).
"""

import pytest

from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import (
QueryTestCase,
)
from documentdb_tests.framework.assertions import assertResult
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params

DOCS = [{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}]

VALID_ARRAY_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="single_expression",
filter={"$nor": [{"a": 1}]},
doc=DOCS,
expected=[{"_id": 2, "a": 2, "b": 1}],
msg="$nor with single expression should exclude matching docs",
),
QueryTestCase(
id="two_expressions",
filter={"$nor": [{"a": 1}, {"b": 1}]},
doc=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}, {"_id": 3, "a": 2, "b": 2}],
expected=[{"_id": 3, "a": 2, "b": 2}],
msg="$nor with two expressions should return docs failing both",
),
QueryTestCase(
id="many_expressions",
filter={"$nor": [{"a": 1}, {"b": 1}, {"a": 3}, {"b": 3}, {"a": 4}]},
doc=[{"_id": 1, "a": 1}, {"_id": 2, "a": 2, "b": 2}],
expected=[{"_id": 2, "a": 2, "b": 2}],
msg="$nor with many expressions should return docs failing all",
),
QueryTestCase(
id="all_docs_match_at_least_one",
filter={"$nor": [{"a": 1}, {"a": 2}]},
doc=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 2, "a": 1, "b": 2},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
expected=[],
msg="$nor should return empty when all docs match at least one condition",
),
QueryTestCase(
id="no_docs_match_any",
filter={"$nor": [{"a": 99}, {"b": 99}]},
doc=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 2, "a": 1, "b": 2},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
expected=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 2, "a": 1, "b": 2},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
msg="$nor should return all docs when none match any condition",
),
QueryTestCase(
id="duplicate_expressions",
filter={"$nor": [{"a": 1}, {"a": 1}]},
doc=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 2, "a": 1, "b": 2},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
expected=[{"_id": 3, "a": 2, "b": 1}, {"_id": 4, "a": 2, "b": 2}],
msg="$nor with duplicate expressions should behave same as single",
),
QueryTestCase(
id="empty_object_in_array",
filter={"$nor": [{}]},
doc=DOCS,
expected=[],
msg="$nor with empty object matches all docs so returns empty",
),
]

MULTIPLE_FIELDS_IN_EXPRESSION_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="implicit_and_in_expression",
filter={"$nor": [{"a": 1, "b": 2}]},
doc=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 2, "a": 1, "b": 2},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
expected=[
{"_id": 1, "a": 1, "b": 1},
{"_id": 3, "a": 2, "b": 1},
{"_id": 4, "a": 2, "b": 2},
],
msg="$nor with multiple fields in one expression is implicit AND within",
),
QueryTestCase(
id="overlapping_field_conditions",
filter={"$nor": [{"a": {"$gt": 5}}, {"a": {"$lt": 2}}]},
doc=[{"_id": 1, "a": 1}, {"_id": 2, "a": 3}, {"_id": 3, "a": 7}],
expected=[{"_id": 2, "a": 3}],
msg="$nor with overlapping conditions returns docs in the gap",
),
QueryTestCase(
id="conflicting_operators_same_field",
filter={"$nor": [{"val": {"$gt": 10}}, {"val": {"$lt": 5}}, {"val": {"$eq": 7}}]},
doc=[
{"_id": 1, "val": 3},
{"_id": 2, "val": 7},
{"_id": 3, "val": 8},
{"_id": 4, "val": 12},
],
expected=[{"_id": 3, "val": 8}],
msg="$nor with conflicting operators on same field returns docs failing all",
),
]

CLAUSE_BEHAVIOR_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="clause_ordering_invariance",
filter={"$nor": [{"b": 1}, {"a": 1}]},
doc=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}, {"_id": 3, "a": 2, "b": 2}],
expected=[{"_id": 3, "a": 2, "b": 2}],
msg="$nor with clauses in different order should produce identical results",
),
QueryTestCase(
id="nested_nor_double_negation",
filter={"$nor": [{"$nor": [{"a": 1}, {"b": 1}]}]},
doc=[
{"_id": 1, "a": 1, "b": 2},
{"_id": 2, "a": 2, "b": 1},
{"_id": 3, "a": 2, "b": 2},
],
expected=[{"_id": 1, "a": 1, "b": 2}, {"_id": 2, "a": 2, "b": 1}],
msg="$nor inside $nor (double negation) should be equivalent to $or",
),
Comment thread
PatersonProjects marked this conversation as resolved.
QueryTestCase(
id="combined_with_top_level_filter",
filter={"x": 1, "$nor": [{"a": 1}, {"b": 2}]},
doc=[
{"_id": 1, "x": 1, "a": 1, "b": 1},
{"_id": 2, "x": 1, "a": 2, "b": 1},
{"_id": 3, "x": 2, "a": 2, "b": 1},
],
expected=[{"_id": 2, "x": 1, "a": 2, "b": 1}],
msg="$nor combined with top-level field filter applies implicit AND",
),
]

ALL_TESTS = VALID_ARRAY_TESTS + MULTIPLE_FIELDS_IN_EXPRESSION_TESTS + CLAUSE_BEHAVIOR_TESTS


@pytest.mark.parametrize("test", pytest_params(ALL_TESTS))
def test_nor_argument_handling(collection, test):
"""Test $nor query operator argument validation."""
collection.insert_many(test.doc)
result = execute_command(collection, {"find": collection.name, "filter": test.filter})
assertResult(
result,
expected=test.expected,
error_code=test.error_code,
ignore_doc_order=True,
msg=test.msg,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""
Tests for $nor query operator with array fields.

Covers element matching in arrays, nested array paths, empty arrays,
arrays of objects with dot notation, $elemMatch and $all on arrays,
null elements in arrays, mixed scalar and array values, and multi-element
clause matching.
"""

import pytest

from documentdb_tests.compatibility.tests.core.operator.query.utils.query_test_case import (
QueryTestCase,
)
from documentdb_tests.framework.assertions import assertResult
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params

ARRAY_FIELD_TESTS: list[QueryTestCase] = [
QueryTestCase(
id="element_matches_in_array",
filter={"$nor": [{"val": 1}]},
doc=[{"_id": 1, "val": [1, 2, 3]}, {"_id": 2, "val": [4, 5, 6]}],
expected=[{"_id": 2, "val": [4, 5, 6]}],
msg="$nor should exclude docs where array contains matching element",
),
QueryTestCase(
id="no_element_matches_in_array",
filter={"$nor": [{"val": 1}]},
doc=[{"_id": 1, "val": [4, 5, 6]}, {"_id": 2, "val": [7, 8]}],
expected=[{"_id": 1, "val": [4, 5, 6]}, {"_id": 2, "val": [7, 8]}],
msg="$nor should return docs where array does not contain matching element",
),
QueryTestCase(
id="empty_array_with_size",
filter={"$nor": [{"val": {"$size": 0}}]},
doc=[{"_id": 1, "val": []}, {"_id": 2, "val": [1]}],
expected=[{"_id": 2, "val": [1]}],
msg="$nor with $size:0 should exclude docs with empty array",
),
QueryTestCase(
id="nested_arrays",
filter={"$nor": [{"val": [1, 2]}]},
doc=[{"_id": 1, "val": [[1, 2], [3, 4]]}, {"_id": 2, "val": [[5, 6]]}],
expected=[{"_id": 2, "val": [[5, 6]]}],
msg="$nor should exclude docs where nested array contains matching sub-array",
),
QueryTestCase(
id="array_of_objects_dot_notation",
filter={"$nor": [{"items.x": 1}]},
doc=[
{"_id": 1, "items": [{"x": 1}, {"x": 2}]},
{"_id": 2, "items": [{"x": 3}, {"x": 4}]},
],
expected=[{"_id": 2, "items": [{"x": 3}, {"x": 4}]}],
msg="$nor with dot notation on array of objects should exclude matching docs",
),
QueryTestCase(
id="nested_array_path",
filter={"$nor": [{"arr.field": "value"}]},
doc=[
{"_id": 1, "arr": [{"field": "value"}, {"field": "other"}]},
{"_id": 2, "arr": [{"field": "other"}]},
],
expected=[{"_id": 2, "arr": [{"field": "other"}]}],
msg="$nor with nested array path should exclude docs with matching element",
),
QueryTestCase(
id="elemMatch_on_array_of_objects",
filter={"$nor": [{"items": {"$elemMatch": {"qty": {"$gt": 5}, "price": {"$lt": 10}}}}]},
doc=[
{"_id": 1, "items": [{"qty": 10, "price": 5}]},
{"_id": 2, "items": [{"qty": 3, "price": 5}]},
],
expected=[{"_id": 2, "items": [{"qty": 3, "price": 5}]}],
msg="$nor with $elemMatch on array of objects should exclude matching docs",
),
QueryTestCase(
id="all_on_array",
filter={"$nor": [{"tags": {"$all": ["a", "b"]}}]},
doc=[
{"_id": 1, "tags": ["a", "b", "c"]},
{"_id": 2, "tags": ["a", "c"]},
],
expected=[{"_id": 2, "tags": ["a", "c"]}],
msg="$nor with $all should exclude docs where array contains all specified elements",
),
QueryTestCase(
id="array_containing_null",
filter={"$nor": [{"val": None}]},
doc=[
{"_id": 1, "val": [1, None, 3]},
{"_id": 2, "val": [1, 2, 3]},
{"_id": 3, "val": [None]},
],
expected=[{"_id": 2, "val": [1, 2, 3]}],
msg="$nor with null should exclude docs where array contains null element",
),
QueryTestCase(
id="multiple_elements_match_different_clauses",
filter={"$nor": [{"val": 1}, {"val": 5}]},
doc=[
{"_id": 1, "val": [1, 5, 10]},
{"_id": 2, "val": [2, 3]},
{"_id": 3, "val": [5, 6]},
{"_id": 4, "val": [10, 20]},
],
expected=[{"_id": 2, "val": [2, 3]}, {"_id": 4, "val": [10, 20]}],
msg="$nor should exclude doc when different array elements match different clauses",
),
QueryTestCase(
id="mixed_scalar_and_array_same_field",
filter={"$nor": [{"val": 2}]},
doc=[
{"_id": 1, "val": 2},
{"_id": 2, "val": [1, 2, 3]},
{"_id": 3, "val": 5},
{"_id": 4, "val": [4, 5, 6]},
],
expected=[{"_id": 3, "val": 5}, {"_id": 4, "val": [4, 5, 6]}],
msg="$nor should exclude docs whether matching value is a scalar or array element",
),
QueryTestCase(
id="dot_notation_array_of_arrays",
filter={"$nor": [{"a.0.0": 1}]},
doc=[
{"_id": 1, "a": [[1, 2], [3, 4]]},
{"_id": 2, "a": [[5, 6], [7, 8]]},
],
expected=[{"_id": 2, "a": [[5, 6], [7, 8]]}],
msg="$nor with dot notation into array of arrays should exclude matching docs",
),
QueryTestCase(
id="elemMatch_with_nested_nor",
filter={
"$nor": [
{"items": {"$elemMatch": {"$nor": [{"qty": {"$gt": 5}}, {"price": {"$lt": 3}}]}}}
]
},
doc=[
{"_id": 1, "items": [{"qty": 2, "price": 5}]},
{"_id": 2, "items": [{"qty": 10, "price": 5}]},
{"_id": 3, "items": [{"qty": 2, "price": 1}]},
{"_id": 4, "items": [{"qty": 10, "price": 1}]},
],
expected=[
{"_id": 2, "items": [{"qty": 10, "price": 5}]},
{"_id": 3, "items": [{"qty": 2, "price": 1}]},
{"_id": 4, "items": [{"qty": 10, "price": 1}]},
],
msg="$nor with $elemMatch containing nested $nor should exclude docs where an element "
"satisfies neither condition (qty<=5 AND price>=3)",
),
]

ALL_TESTS = ARRAY_FIELD_TESTS


@pytest.mark.parametrize("test", pytest_params(ALL_TESTS))
def test_nor_array_fields(collection, test):
"""Test $nor query operator with array fields."""
collection.insert_many(test.doc)
result = execute_command(collection, {"find": collection.name, "filter": test.filter})
assertResult(
result,
expected=test.expected,
error_code=test.error_code,
ignore_doc_order=True,
msg=test.msg,
)
Loading
Loading