diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_bson_type_validation.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_bson_type_validation.py new file mode 100644 index 000000000..32a2f97f7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_bson_type_validation.py @@ -0,0 +1,82 @@ +"""readConcern BSON type validation: field must be a document, level must be a string.""" + +import pytest + +from documentdb_tests.framework.assertions import assertFailureCode, assertSuccess +from documentdb_tests.framework.bson_type_validator import ( + BsonType, + BsonTypeTestCase, + generate_bson_acceptance_test_cases, + generate_bson_rejection_test_cases, +) +from documentdb_tests.framework.error_codes import TYPE_MISMATCH_ERROR +from documentdb_tests.framework.executor import execute_command + +READ_CONCERN_PARAMS = [ + BsonTypeTestCase( + id="read_concern_field", + msg="readConcern field should reject non-document BSON types", + valid_types=[BsonType.OBJECT], + skip_rejection_types=[BsonType.NULL], + default_error_code=TYPE_MISMATCH_ERROR, + expected=[{"_id": 1, "x": 1}], + valid_inputs={BsonType.OBJECT: {"level": "local"}}, + ), + BsonTypeTestCase( + id="level_field", + msg="readConcern.level should reject non-string BSON types", + valid_types=[BsonType.STRING], + skip_rejection_types=[BsonType.NULL], + default_error_code=TYPE_MISMATCH_ERROR, + expected=[{"_id": 1, "x": 1}], + valid_inputs={BsonType.STRING: "local"}, + ), +] + + +def _build_read_concern(spec, sample_value): + """Build the readConcern value based on which aspect is being tested.""" + if spec.id == "read_concern_field": + return sample_value + # level_field: wrap the sample as the level sub-field. + return {"level": sample_value} + + +@pytest.mark.parametrize( + "bson_type,sample_value,spec", generate_bson_rejection_test_cases(READ_CONCERN_PARAMS) +) +def test_read_concern_bson_type_rejected(collection, bson_type, sample_value, spec): + """Test readConcern rejects invalid BSON types for the field and level sub-field.""" + collection.insert_one({"_id": 1, "x": 1}) + result = execute_command( + collection, + { + "find": collection.name, + "filter": {}, + "readConcern": _build_read_concern(spec, sample_value), + }, + ) + assertFailureCode( + result, + spec.expected_code(bson_type), + msg=f"readConcern should reject {bson_type.value} for {spec.id}", + ) + + +@pytest.mark.parametrize( + "bson_type,sample_value,spec", generate_bson_acceptance_test_cases(READ_CONCERN_PARAMS) +) +def test_read_concern_bson_type_accepted(collection, bson_type, sample_value, spec): + """Test readConcern accepts valid BSON types for the field and level sub-field.""" + collection.insert_one({"_id": 1, "x": 1}) + result = execute_command( + collection, + { + "find": collection.name, + "filter": {}, + "readConcern": _build_read_concern(spec, sample_value), + }, + ) + assertSuccess( + result, spec.expected, msg=f"readConcern should accept {bson_type.value} for {spec.id}" + ) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_command_interaction.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_command_interaction.py new file mode 100644 index 000000000..abeb42381 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_command_interaction.py @@ -0,0 +1,134 @@ +"""readConcern with find: command options, empty/non-existent collections, views, and getMore.""" + +from typing import Any, Dict, cast + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import CommandTestCase +from documentdb_tests.framework.assertions import assertProperties, assertResult +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.target_collection import ViewCollection + +INTERACTION_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_with_options_and_read_concern", + docs=[{"_id": 1, "x": 3, "y": 9}, {"_id": 2, "x": 1, "y": 9}, {"_id": 3, "x": 2, "y": 9}], + command={ + "filter": {}, + "sort": {"x": 1}, + "projection": {"x": 1, "_id": 1}, + "limit": 2, + "readConcern": {"level": "local"}, + }, + expected=[{"_id": 2, "x": 1}, {"_id": 3, "x": 2}], + msg="find with readConcern should not interfere with sort/projection/limit options.", + ), + CommandTestCase( + "find_on_empty_collection", + docs=[], + command={"filter": {}, "readConcern": {"level": "local"}}, + expected=[], + msg="find with readConcern on empty collection should return empty.", + ), + CommandTestCase( + "find_on_nonexistent_collection", + docs=None, + command={"filter": {}, "readConcern": {"level": "local"}}, + expected=[], + msg="find with readConcern on non-existent collection should return empty.", + ), + CommandTestCase( + "find_on_view", + target_collection=ViewCollection(options={"pipeline": [{"$match": {"x": {"$gte": 10}}}]}), + docs=[{"_id": 1, "x": 10}, {"_id": 2, "x": 20}, {"_id": 3, "x": 5}], + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "local"}}, + expected=[{"_id": 1, "x": 10}, {"_id": 2, "x": 20}], + msg="find with readConcern on view should return filtered view results.", + ), + CommandTestCase( + "find_first_batch_with_batch_size", + docs=[{"_id": i, "x": i} for i in range(10)], + command={ + "filter": {}, + "batchSize": 3, + "sort": {"_id": 1}, + "readConcern": {"level": "local"}, + }, + expected=[{"_id": 0, "x": 0}, {"_id": 1, "x": 1}, {"_id": 2, "x": 2}], + msg="first batch from find with readConcern should contain 3 documents.", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(INTERACTION_TESTS)) +def test_read_concern_command_interaction(collection, test: CommandTestCase): + """Test readConcern works with other command options, collection states, and views.""" + collection = test.prepare(collection.database, collection) + find_body = cast(Dict[str, Any], test.command) + result = execute_command(collection, {"find": collection.name, **find_body}) + assertResult(result, expected=test.expected, msg=test.msg) + + +def test_getmore_after_find_with_read_concern_next_batch(collection): + """Test getMore after find with readConcern returns next batch correctly.""" + collection.insert_many([{"_id": i, "x": i} for i in range(10)]) + + initial_result = execute_command( + collection, + { + "find": collection.name, + "filter": {}, + "batchSize": 3, + "sort": {"_id": 1}, + "readConcern": {"level": "local"}, + }, + ) + cursor_id = initial_result["cursor"]["id"] + + getmore_result = execute_command( + collection, + {"getMore": cursor_id, "collection": collection.name, "batchSize": 3}, + ) + expected_next = [{"_id": 3, "x": 3}, {"_id": 4, "x": 4}, {"_id": 5, "x": 5}] + assertProperties( + getmore_result, + {"cursor.nextBatch": Eq(expected_next), "ok": Eq(1.0)}, + raw_res=True, + msg="getMore after readConcern find should return next batch correctly.", + ) + + +def test_getmore_ignores_read_concern_parameter(collection): + """Test getMore ignores a readConcern parameter and still returns the next batch.""" + collection.insert_many([{"_id": i, "x": i} for i in range(10)]) + + initial_result = execute_command( + collection, + { + "find": collection.name, + "filter": {}, + "batchSize": 3, + "sort": {"_id": 1}, + "readConcern": {"level": "local"}, + }, + ) + cursor_id = initial_result["cursor"]["id"] + + getmore_result = execute_command( + collection, + { + "getMore": cursor_id, + "collection": collection.name, + "batchSize": 3, + "readConcern": {"level": "local"}, + }, + ) + expected_next = [{"_id": 3, "x": 3}, {"_id": 4, "x": 4}, {"_id": 5, "x": 5}] + assertProperties( + getmore_result, + {"cursor.nextBatch": Eq(expected_next), "ok": Eq(1.0)}, + raw_res=True, + msg="getMore should ignore a readConcern parameter and return the next batch.", + ) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_errors.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_errors.py new file mode 100644 index 000000000..1191e7399 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_errors.py @@ -0,0 +1,134 @@ +"""readConcern rejection cases with find: bad levels, unknown fields, topology, afterClusterTime.""" + +from typing import Any, Dict, cast + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import CommandTestCase +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + NOT_A_REPLICA_SET_ERROR, + TYPE_MISMATCH_ERROR, + UNRECOGNIZED_COMMAND_FIELD_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +_REQUIRES_STANDALONE = (pytest.mark.requires(cluster_read_concern=False),) +_REQUIRES_REPLICA_SET = (pytest.mark.requires(cluster_read_concern=True),) + +INVALID_LEVEL_STRING_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_rejects_empty_string_level", + command={"filter": {}, "readConcern": {"level": ""}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject empty readConcern level string.", + ), + CommandTestCase( + "find_rejects_unknown_level_string", + command={"filter": {}, "readConcern": {"level": "invalid"}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject unrecognized readConcern level string 'invalid'.", + ), + CommandTestCase( + "find_rejects_uppercase_level", + command={"filter": {}, "readConcern": {"level": "LOCAL"}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject uppercase readConcern level 'LOCAL'.", + ), + CommandTestCase( + "find_rejects_mixed_case_level", + command={"filter": {}, "readConcern": {"level": "Majority"}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject mixed-case readConcern level 'Majority'.", + ), + CommandTestCase( + "find_rejects_nonexistent_level", + command={"filter": {}, "readConcern": {"level": "strong"}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject nonexistent readConcern level 'strong'.", + ), + CommandTestCase( + "find_rejects_null_byte_in_level", + command={"filter": {}, "readConcern": {"level": "local\x00extra"}}, + error_code=BAD_VALUE_ERROR, + msg="find should reject null byte in readConcern level string.", + ), +] + +UNKNOWN_FIELD_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_rejects_unknown_field_no_level", + command={"filter": {}, "readConcern": {"unknownField": 1}}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="find should reject readConcern with unknown field and no level.", + ), + CommandTestCase( + "find_rejects_extra_field_with_valid_level", + command={"filter": {}, "readConcern": {"level": "local", "unknownField": 1}}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="find should reject readConcern with extra unknown field.", + ), +] + + +AFTER_CLUSTER_TIME_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_rejects_afterClusterTime_string", + command={"filter": {}, "readConcern": {"level": "local", "afterClusterTime": "invalid"}}, + error_code=TYPE_MISMATCH_ERROR, + msg="find should reject non-Timestamp afterClusterTime (string).", + marks=_REQUIRES_REPLICA_SET, + ), + CommandTestCase( + "find_rejects_afterClusterTime_integer", + command={"filter": {}, "readConcern": {"level": "local", "afterClusterTime": 12345}}, + error_code=TYPE_MISMATCH_ERROR, + msg="find should reject non-Timestamp afterClusterTime (integer).", + marks=_REQUIRES_REPLICA_SET, + ), + CommandTestCase( + "find_rejects_afterClusterTime_null", + command={"filter": {}, "readConcern": {"level": "local", "afterClusterTime": None}}, + error_code=TYPE_MISMATCH_ERROR, + msg="find should reject non-Timestamp afterClusterTime (null).", + marks=_REQUIRES_REPLICA_SET, + ), +] + +# 'snapshot' and 'linearizable' both require a replicated topology and are rejected on standalone. +REPLICA_SET_ONLY_LEVEL_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_rejects_snapshot_on_standalone", + docs=[{"_id": 1}], + command={"filter": {}, "readConcern": {"level": "snapshot"}}, + error_code=NOT_A_REPLICA_SET_ERROR, + msg="readConcern 'snapshot' should be rejected on a standalone (not a replica set).", + marks=_REQUIRES_STANDALONE, + ), + CommandTestCase( + "find_rejects_linearizable_on_standalone", + docs=[{"_id": 1}], + command={"filter": {}, "readConcern": {"level": "linearizable"}}, + error_code=NOT_A_REPLICA_SET_ERROR, + msg="readConcern 'linearizable' should be rejected on a standalone (not a replica set).", + marks=_REQUIRES_STANDALONE, + ), +] + +ERROR_TESTS: list[CommandTestCase] = ( + INVALID_LEVEL_STRING_TESTS + + UNKNOWN_FIELD_TESTS + + REPLICA_SET_ONLY_LEVEL_TESTS + + AFTER_CLUSTER_TIME_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_read_concern_rejected(collection, test: CommandTestCase): + """Test readConcern rejection cases return the expected error code.""" + collection = test.prepare(collection.database, collection) + find_body = cast(Dict[str, Any], test.command) + result = execute_command(collection, {"find": collection.name, **find_body}) + assertResult(result, error_code=test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_level_acceptance.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_level_acceptance.py new file mode 100644 index 000000000..731c46d16 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_level_acceptance.py @@ -0,0 +1,94 @@ +"""readConcern acceptance with find: valid levels, default shapes, and valid afterClusterTime.""" + +from typing import Any, Dict, cast + +import pytest +from bson import Timestamp + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import CommandTestCase +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +_TWO_DOCS = [{"_id": 1, "x": 1}, {"_id": 2, "x": 2}] + +ACCEPTANCE_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_accepts_local", + docs=_TWO_DOCS, + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "local"}}, + expected=_TWO_DOCS, + msg="find should accept readConcern level 'local'.", + ), + CommandTestCase( + "find_accepts_available", + docs=_TWO_DOCS, + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "available"}}, + expected=_TWO_DOCS, + msg="find should accept readConcern level 'available'.", + ), + CommandTestCase( + "find_accepts_majority", + docs=_TWO_DOCS, + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "majority"}}, + expected=_TWO_DOCS, + msg="find should accept readConcern level 'majority'.", + ), + CommandTestCase( + "find_accepts_linearizable", + docs=[{"_id": 1, "x": 1}], + command={"filter": {"_id": 1}, "readConcern": {"level": "linearizable"}}, + expected=[{"_id": 1, "x": 1}], + msg="find should accept readConcern level 'linearizable'.", + marks=(pytest.mark.requires(cluster_read_concern=True),), + ), + CommandTestCase( + "find_accepts_snapshot", + docs=[{"_id": 1, "x": 1}], + command={"filter": {"_id": 1}, "readConcern": {"level": "snapshot"}}, + expected=[{"_id": 1, "x": 1}], + msg="find should accept readConcern level 'snapshot' on a replica set.", + marks=(pytest.mark.requires(cluster_read_concern=True),), + ), + CommandTestCase( + "find_accepts_empty_document", + docs=[{"_id": 1, "x": 1}], + command={"filter": {}, "readConcern": {}}, + expected=[{"_id": 1, "x": 1}], + msg="find should accept empty readConcern document.", + ), + CommandTestCase( + "find_accepts_null_read_concern", + docs=[{"_id": 1, "x": 1}], + command={"filter": {}, "readConcern": None}, + expected=[{"_id": 1, "x": 1}], + msg="find should treat null readConcern as omitted.", + ), + CommandTestCase( + "find_accepts_null_level", + docs=[{"_id": 1, "x": 1}], + command={"filter": {}, "readConcern": {"level": None}}, + expected=[{"_id": 1, "x": 1}], + msg="find should treat readConcern {level: null} as implicit default.", + ), + CommandTestCase( + "find_accepts_valid_after_cluster_time", + docs=[{"_id": 1, "x": 1}], + command={ + "filter": {"_id": 1}, + "readConcern": {"level": "local", "afterClusterTime": Timestamp(1, 1)}, + }, + expected=[{"_id": 1, "x": 1}], + msg="find should accept a valid Timestamp afterClusterTime on a replica set.", + marks=(pytest.mark.requires(cluster_read_concern=True),), + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ACCEPTANCE_TESTS)) +def test_read_concern_level_acceptance(collection, test: CommandTestCase): + """Test readConcern level (and default-equivalent shapes) is accepted by find.""" + collection = test.prepare(collection.database, collection) + find_body = cast(Dict[str, Any], test.command) + result = execute_command(collection, {"find": collection.name, **find_body}) + assertResult(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_local.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_local.py new file mode 100644 index 000000000..1dbc3e7d3 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_local.py @@ -0,0 +1,77 @@ +"""readConcern level 'local' availability and behavior with find.""" + +from typing import Any, Dict, cast + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import CommandTestCase +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + + +def _update_status(coll): + """Update _id 1's status to 'done' via a write command.""" + execute_command( + coll, + {"update": coll.name, "updates": [{"q": {"_id": 1}, "u": {"$set": {"status": "done"}}}]}, + ) + + +def _delete_first(coll): + """Delete _id 1 via a write command.""" + execute_command(coll, {"delete": coll.name, "deletes": [{"q": {"_id": 1}, "limit": 1}]}) + + +# Each ``command`` is the find body (everything except the collection name); the +# test runner prepends ``"find": ``. +LOCAL_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "find_omitted_read_concern_is_default", + docs=[{"_id": 1, "x": 1}, {"_id": 2, "x": 2}], + command={"filter": {}, "sort": {"_id": 1}}, + expected=[{"_id": 1, "x": 1}, {"_id": 2, "x": 2}], + msg="find without readConcern should return all documents (implicit default).", + ), + CommandTestCase( + "find_local_without_session", + docs=[{"_id": 1, "v": "a"}, {"_id": 2, "v": "b"}], + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "local"}}, + expected=[{"_id": 1, "v": "a"}, {"_id": 2, "v": "b"}], + msg="find with readConcern 'local' must be available without a session or transaction.", + ), + CommandTestCase( + "find_reads_fresh_data", + docs=[{"_id": 1, "score": 100}], + command={"filter": {"score": 100}, "readConcern": {"level": "local"}}, + expected=[{"_id": 1, "score": 100}], + msg="find with readConcern 'local' should return inserted documents.", + ), + CommandTestCase( + "find_sees_updated_document", + docs=[{"_id": 1, "status": "pending"}], + setup=_update_status, + command={"filter": {"_id": 1}, "readConcern": {"level": "local"}}, + expected=[{"_id": 1, "status": "done"}], + msg="find with readConcern 'local' must reflect an update applied to the local instance.", + ), + CommandTestCase( + "find_reflects_delete", + docs=[{"_id": 1}, {"_id": 2}, {"_id": 3}], + setup=_delete_first, + command={"filter": {}, "sort": {"_id": 1}, "readConcern": {"level": "local"}}, + expected=[{"_id": 2}, {"_id": 3}], + msg="find with readConcern 'local' must reflect a deletion applied to the local instance.", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(LOCAL_TESTS)) +def test_read_concern_local(collection, test: CommandTestCase): + """Test readConcern level 'local' availability and behavior.""" + collection = test.prepare(collection.database, collection) + if test.setup: + test.setup(collection) + find_body = cast(Dict[str, Any], test.command) + result = execute_command(collection, {"find": collection.name, **find_body}) + assertResult(result, expected=test.expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_write_commands.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_write_commands.py new file mode 100644 index 000000000..8a4ffa589 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_read_concern_write_commands.py @@ -0,0 +1,70 @@ +"""readConcern on write commands (insert, update, delete, findAndModify) outside transactions.""" + +from typing import Any, Dict, cast + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +WRITE_COMMAND_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "insert_with_read_concern", + command=lambda ctx: { + "insert": ctx.collection, + "documents": [{"_id": 1, "x": 1}], + "readConcern": {"level": "local"}, + }, + expected={"n": 1, "ok": 1.0}, + msg="insert should accept readConcern outside transaction.", + ), + CommandTestCase( + "update_with_read_concern", + docs=[{"_id": 1, "x": 1}], + command=lambda ctx: { + "update": ctx.collection, + "updates": [{"q": {"_id": 1}, "u": {"$set": {"x": 2}}}], + "readConcern": {"level": "local"}, + }, + expected={"n": 1, "nModified": 1, "ok": 1.0}, + msg="update should accept readConcern outside transaction.", + ), + CommandTestCase( + "delete_with_read_concern", + docs=[{"_id": 1}], + command=lambda ctx: { + "delete": ctx.collection, + "deletes": [{"q": {"_id": 1}, "limit": 1}], + "readConcern": {"level": "local"}, + }, + expected={"n": 1, "ok": 1.0}, + msg="delete should accept readConcern outside transaction.", + ), + CommandTestCase( + "find_and_modify_with_read_concern", + docs=[{"_id": 1, "x": 1}], + command=lambda ctx: { + "findAndModify": ctx.collection, + "query": {"_id": 1}, + "update": {"$set": {"x": 2}}, + "readConcern": {"level": "local"}, + }, + expected={"value": {"_id": 1, "x": 1}, "ok": 1.0}, + msg="findAndModify should accept readConcern outside transaction.", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(WRITE_COMMAND_TESTS)) +def test_write_command_with_read_concern(collection, test: CommandTestCase): + """Test write commands accept readConcern outside transaction.""" + collection = test.prepare(collection.database, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + expected = cast(Dict[str, Any], test.build_expected(ctx)) + assertSuccessPartial(result, expected, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_smoke_read_concern.py b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_smoke_read_concern.py index 140e0a288..dc0f2eae2 100644 --- a/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_smoke_read_concern.py +++ b/documentdb_tests/compatibility/tests/core/query_and_write/read_concern/test_smoke_read_concern.py @@ -1,8 +1,4 @@ -""" -Smoke test for readConcern. - -Tests basic readConcern functionality. -""" +"""Smoke test for basic readConcern functionality.""" import pytest