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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Tests for $ positional update operator core behavior.
Covers: first-match semantics, update command integration, upsert with
existing doc, and empty array edge case.
"""

import pytest

from documentdb_tests.compatibility.tests.core.operator.update.utils import UpdateTestCase
from documentdb_tests.framework.assertions import assertSuccess
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params

CHANGED_DOC_TESTS: list[UpdateTestCase] = [
UpdateTestCase(
"set_first_match",
setup_docs=[{"_id": 1, "arr": [1, 2, 3, 2, 4]}],
query={"_id": 1, "arr": 2},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [1, 99, 3, 2, 4]},
msg="$ should update only the first matching element",
),
UpdateTestCase(
"match_last_element",
setup_docs=[{"_id": 1, "arr": [10, 20, 30]}],
query={"_id": 1, "arr": 30},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [10, 20, 99]},
msg="$ should update last element when it matches",
),
UpdateTestCase(
"upsert_doc_exists_succeeds",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}],
query={"_id": 1, "arr": 2},
update={"$set": {"arr.$": 99}},
upsert=True,
expected={"_id": 1, "arr": [1, 99, 3]},
msg="$ in upsert when document exists and matches should work normally",
),
]


UPDATE_RESULT_TESTS: list[UpdateTestCase] = [
UpdateTestCase(
"update_one",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}, {"_id": 2, "arr": [2, 3, 4]}],
query={"arr": 2},
update={"$set": {"arr.$": 99}},
expected={"n": 1, "nModified": 1, "ok": 1.0},
msg="$ should work with updateOne (updates only one matched doc)",
),
UpdateTestCase(
"array_field_missing",
setup_docs=[{"_id": 1, "x": 1}],
query={"_id": 1, "arr": 5},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ when array field is missing should result in no match",
),
UpdateTestCase(
"empty_array_no_match",
setup_docs=[{"_id": 1, "arr": []}],
Comment thread
PatersonProjects marked this conversation as resolved.
query={"_id": 1, "arr": 5},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ on empty array with no match should result in no update",
),
]


@pytest.mark.parametrize("test", pytest_params(CHANGED_DOC_TESTS))
def test_positional_changed_doc(collection, test: UpdateTestCase):
"""Test $ positional update operator produces expected document."""
if test.setup_docs:
collection.insert_many(test.setup_docs)

update_doc = {"q": test.query, "u": test.update}
if test.upsert:
update_doc["upsert"] = True
execute_command(collection, {"update": collection.name, "updates": [update_doc]})

result = execute_command(
collection, {"find": collection.name, "filter": {"_id": test.expected["_id"]}}
)
assertSuccess(result, [test.expected], msg=test.msg)


@pytest.mark.parametrize("test", pytest_params(UPDATE_RESULT_TESTS))
def test_positional_update_result(collection, test: UpdateTestCase):
"""Test $ positional update command returns expected n/nModified."""
if test.setup_docs:
collection.insert_many(test.setup_docs)

result = execute_command(
collection, {"update": collection.name, "updates": [{"q": test.query, "u": test.update}]}
)
assertSuccess(result, test.expected, msg=test.msg, raw_res=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""Tests for $ positional with data type coverage.
Covers: all BSON types as array elements, numeric equivalence in matching,
BSON type distinction, and per-input-position coverage.
"""

import pytest
from bson import Decimal128, Int64

from documentdb_tests.compatibility.tests.core.operator.update.utils import UpdateTestCase
from documentdb_tests.framework.assertions import assertProperties, assertSuccess
from documentdb_tests.framework.bson_type_validator import (
BsonTypeTestCase,
generate_bson_acceptance_test_cases,
)
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params
from documentdb_tests.framework.property_checks import Eq
from documentdb_tests.framework.test_constants import BsonType

POSITIONAL_BSON_TYPE_SPEC = [
BsonTypeTestCase(
id="positional_match",
msg="$ should match and update array elements of all BSON types",
keyword="arr",
valid_types=list(BsonType),
),
]

BSON_TYPE_CASES = generate_bson_acceptance_test_cases(POSITIONAL_BSON_TYPE_SPEC)


@pytest.mark.parametrize("bson_type,sample_value,spec", BSON_TYPE_CASES)
def test_positional_bson_types(collection, bson_type, sample_value, spec):
"""Test $ positional matches and updates each BSON type in an array."""
collection.insert_one({"_id": 1, "arr": [sample_value, sample_value]})

execute_command(
collection,
{
"update": collection.name,
"updates": [
{"q": {"_id": 1, "arr": sample_value}, "u": {"$set": {"arr.$": "replaced"}}}
],
},
)

result = execute_command(collection, {"find": collection.name, "filter": {"_id": 1}})
assertProperties(result, {"arr.0": Eq("replaced")}, msg=spec.msg)


NUMERIC_EQUIVALENCE_TESTS: list[UpdateTestCase] = [
UpdateTestCase(
"int_matches_long",
setup_docs=[{"_id": 1, "arr": [Int64(1), Int64(2), Int64(3)]}],
query={"_id": 1, "arr": 1},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [99, Int64(2), Int64(3)]},
msg="$ query with int should match long element (numeric equivalence)",
),
UpdateTestCase(
"double_matches_int",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}],
query={"_id": 1, "arr": 1.0},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [99, 2, 3]},
msg="$ query with double(1.0) should match int(1)",
),
UpdateTestCase(
"decimal128_matches_int",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}],
query={"_id": 1, "arr": Decimal128("1")},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [99, 2, 3]},
msg="$ query with Decimal128('1') should match int(1)",
),
UpdateTestCase(
"long_matches_double",
setup_docs=[{"_id": 1, "arr": [1.0, 2.0, 3.0]}],
query={"_id": 1, "arr": Int64(2)},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [1.0, 99, 3.0]},
msg="$ query with long should match double element (numeric equivalence)",
),
UpdateTestCase(
"negative_zero_matches_zero",
setup_docs=[{"_id": 1, "arr": [0, 1, 2]}],
query={"_id": 1, "arr": -0.0},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [99, 1, 2]},
msg="$ query with -0.0 should match 0 (negative zero equivalence)",
),
UpdateTestCase(
"null_matches_null",
setup_docs=[{"_id": 1, "arr": [1, None, 3]}],
query={"_id": 1, "arr": None},
update={"$set": {"arr.$": 99}},
expected={"_id": 1, "arr": [1, 99, 3]},
msg="$ query with null should match null element in array",
),
]


BSON_DISTINCTION_TESTS: list[UpdateTestCase] = [
UpdateTestCase(
"false_not_match_zero",
setup_docs=[{"_id": 1, "arr": [0, 1, 2]}],
query={"_id": 1, "arr": False},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ query with false should NOT match int(0) (distinct BSON types)",
),
UpdateTestCase(
"true_not_match_one",
setup_docs=[{"_id": 1, "arr": [0, 1, 2]}],
query={"_id": 1, "arr": True},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ query with true should NOT match int(1) (distinct BSON types)",
),
UpdateTestCase(
"null_not_match_zero",
setup_docs=[{"_id": 1, "arr": [0, 1, 2]}],
query={"_id": 1, "arr": None},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ query with null should NOT match int(0) (distinct BSON types)",
),
UpdateTestCase(
"string_not_match_int",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}],
query={"_id": 1, "arr": "1"},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ query with string '1' should NOT match int(1) (distinct BSON types)",
),
UpdateTestCase(
"null_not_match_missing",
setup_docs=[{"_id": 1, "arr": [1, 2, 3]}],
query={"_id": 1, "arr": None},
update={"$set": {"arr.$": 99}},
expected={"n": 0, "nModified": 0, "ok": 1.0},
msg="$ query with null should NOT match when array has no null element",
),
]


@pytest.mark.parametrize("test", pytest_params(NUMERIC_EQUIVALENCE_TESTS))
def test_positional_numeric_equivalence(collection, test: UpdateTestCase):
"""Test $ positional with numeric equivalence across BSON number types."""
if test.setup_docs:
collection.insert_many(test.setup_docs)

execute_command(
collection,
{"update": collection.name, "updates": [{"q": test.query, "u": test.update}]},
)

result = execute_command(
collection, {"find": collection.name, "filter": {"_id": test.expected["_id"]}}
)
assertSuccess(result, [test.expected], msg=test.msg)


@pytest.mark.parametrize("test", pytest_params(BSON_DISTINCTION_TESTS))
def test_positional_bson_type_distinction(collection, test: UpdateTestCase):
"""Test $ positional does not match across distinct BSON types."""
if test.setup_docs:
collection.insert_many(test.setup_docs)

result = execute_command(
collection,
{"update": collection.name, "updates": [{"q": test.query, "u": test.update}]},
)
assertSuccess(result, test.expected, msg=test.msg, raw_res=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Tests for $ positional with embedded documents and dot notation.
Covers: updating fields in embedded documents, dot notation paths,
nested fields, and edge cases with numeric path components.
"""

import pytest

from documentdb_tests.compatibility.tests.core.operator.update.utils import UpdateTestCase
from documentdb_tests.framework.assertions import assertSuccess
from documentdb_tests.framework.executor import execute_command
from documentdb_tests.framework.parametrize import pytest_params

EMBEDDED_DOC_TESTS: list[UpdateTestCase] = [
UpdateTestCase(
"set_field_in_matched_doc",
setup_docs=[{"_id": 1, "arr": [{"name": "A", "val": 1}, {"name": "B", "val": 2}]}],
query={"_id": 1, "arr.name": "B"},
update={"$set": {"arr.$.val": 99}},
expected={"_id": 1, "arr": [{"name": "A", "val": 1}, {"name": "B", "val": 99}]},
msg="$ with dot notation should update field in matched embedded document",
),
UpdateTestCase(
"elemMatch_set_field",
setup_docs=[{"_id": 1, "arr": [{"x": 1, "y": 10}, {"x": 2, "y": 20}]}],
query={"_id": 1, "arr": {"$elemMatch": {"x": 2}}},
update={"$set": {"arr.$.y": 99}},
expected={"_id": 1, "arr": [{"x": 1, "y": 10}, {"x": 2, "y": 99}]},
msg="$ with $elemMatch and $set should update matched embedded doc field",
),
UpdateTestCase(
"deeply_nested_field",
setup_docs=[{"_id": 1, "arr": [{"nested": {"field": 1}}, {"nested": {"field": 2}}]}],
query={"_id": 1, "arr.nested.field": 2},
update={"$set": {"arr.$.nested.field": 99}},
expected={"_id": 1, "arr": [{"nested": {"field": 1}}, {"nested": {"field": 99}}]},
msg="$ updating deeply nested field should work",
),
UpdateTestCase(
"nested_array_field",
setup_docs=[{"_id": 1, "outer": {"inner": [1, 2, 3]}}],
query={"_id": 1, "outer.inner": 2},
update={"$set": {"outer.inner.$": 99}},
expected={"_id": 1, "outer": {"inner": [1, 99, 3]}},
msg="$ on nested array 'outer.inner.$' should update matched element",
),
UpdateTestCase(
"query_traverses_nested_array",
setup_docs=[
{"_id": 1, "arr": [{"nested": [1, 2]}, {"nested": [3, 4]}]},
],
query={"arr.nested": 3},
update={"$set": {"arr.$.nested": [99]}},
expected={"_id": 1, "arr": [{"nested": [1, 2]}, {"nested": [99]}]},
msg="$ should resolve outer array position when query traverses inner array",
),
UpdateTestCase(
"numeric_path_component",
setup_docs=[
{"_id": 1, "arr": [{"vals": [10, 20]}, {"vals": [30, 40]}]},
],
query={"_id": 1, "arr.vals": 30},
update={"$set": {"arr.$.vals.0": 99}},
expected={"_id": 1, "arr": [{"vals": [10, 20]}, {"vals": [99, 40]}]},
msg="$ with numeric path component should update specific index in matched element",
),
]


@pytest.mark.parametrize("test", pytest_params(EMBEDDED_DOC_TESTS))
def test_positional_embedded_docs(collection, test: UpdateTestCase):
"""Test $ positional with embedded documents and dot notation."""
if test.setup_docs:
collection.insert_many(test.setup_docs)

execute_command(
collection, {"update": collection.name, "updates": [{"q": test.query, "u": test.update}]}
)

result = execute_command(
collection, {"find": collection.name, "filter": {"_id": test.expected["_id"]}}
)
assertSuccess(result, [test.expected], msg=test.msg)
Loading
Loading