From f42d3af9a91a3b865b3cefc0ea5f804791f65875 Mon Sep 17 00:00:00 2001 From: "Victor [C] Tsang" Date: Fri, 26 Jun 2026 21:02:16 +0000 Subject: [PATCH] Add admin command tests for setDefaultRWConcern Signed-off-by: Victor [C] Tsang --- .../compatibility/tests/system/__init__.py | 0 .../tests/system/administration/__init__.py | 0 .../administration/commands/__init__.py | 0 .../commands/setDefaultRWConcern/__init__.py | 0 ...etDefaultRWConcern_bson_type_validation.py | 87 +++++++ .../test_setDefaultRWConcern_core_behavior.py | 154 ++++++++++++ .../test_setDefaultRWConcern_errors.py | 231 ++++++++++++++++++ ..._setDefaultRWConcern_response_structure.py | 75 ++++++ .../test_setDefaultRWConcern_valid_inputs.py | 143 +++++++++++ .../system/administration/utils/__init__.py | 0 .../administration/utils/admin_test_case.py | 19 ++ 11 files changed, 709 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_bson_type_validation.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_core_behavior.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_errors.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_response_structure.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_valid_inputs.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/utils/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/utils/admin_test_case.py diff --git a/documentdb_tests/compatibility/tests/system/__init__.py b/documentdb_tests/compatibility/tests/system/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/__init__.py b/documentdb_tests/compatibility/tests/system/administration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/__init__.py b/documentdb_tests/compatibility/tests/system/administration/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/__init__.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_bson_type_validation.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_bson_type_validation.py new file mode 100644 index 000000000..2c00c3fe5 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_bson_type_validation.py @@ -0,0 +1,87 @@ +"""BSON type validation tests for setDefaultRWConcern.""" + +import pytest + +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +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_admin_command +from documentdb_tests.framework.property_checks import Eq + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel, pytest.mark.requires(cluster_admin=True)] + + +# NULL is skipped from rejection: a null concern is treated as omitted, not rejected. +# The null-as-omitted behavior is covered in test_setDefaultRWConcern_valid_inputs.py. +FIELD_SPECS: list[BsonTypeTestCase] = [ + BsonTypeTestCase( + id="setDefaultRWConcern_value", + msg="setDefaultRWConcern command value is type-agnostic (any BSON type accepted)", + keyword="setDefaultRWConcern", + valid_types=list(BsonType), + requires={"defaultReadConcern": {"level": "local"}}, + ), + BsonTypeTestCase( + id="defaultReadConcern", + msg="defaultReadConcern should accept object types only", + keyword="defaultReadConcern", + valid_types=[BsonType.OBJECT], + skip_rejection_types=[BsonType.NULL], + valid_inputs={BsonType.OBJECT: {"level": "local"}}, + default_error_code=TYPE_MISMATCH_ERROR, + ), + BsonTypeTestCase( + id="defaultWriteConcern", + msg="defaultWriteConcern should accept object types only", + keyword="defaultWriteConcern", + valid_types=[BsonType.OBJECT], + skip_rejection_types=[BsonType.NULL], + valid_inputs={BsonType.OBJECT: {"w": 1}}, + default_error_code=TYPE_MISMATCH_ERROR, + ), + BsonTypeTestCase( + id="writeConcern", + msg="writeConcern should accept object types only", + keyword="writeConcern", + valid_types=[BsonType.OBJECT], + skip_rejection_types=[BsonType.NULL], + valid_inputs={BsonType.OBJECT: {"w": 1}}, + requires={"defaultReadConcern": {"level": "local"}}, + default_error_code=TYPE_MISMATCH_ERROR, + ), +] + +ACCEPTANCE_CASES = generate_bson_acceptance_test_cases(FIELD_SPECS) +REJECTION_CASES = generate_bson_rejection_test_cases(FIELD_SPECS) + + +def _build_command(spec: BsonTypeTestCase, sample_value): + """Build a setDefaultRWConcern command with ``sample_value`` in ``spec.keyword``.""" + keyword = spec.keyword + assert keyword is not None, "BsonTypeTestCase must define a keyword" + if keyword == "setDefaultRWConcern": + command = {"setDefaultRWConcern": sample_value} + else: + command = {"setDefaultRWConcern": 1, keyword: sample_value} + if spec.requires: + command.update(spec.requires) + return command + + +@pytest.mark.parametrize("bson_type,sample_value,spec", ACCEPTANCE_CASES) +def test_setDefaultRWConcern_bson_type_accepted(collection, bson_type, sample_value, spec): + """Test each field accepts the BSON types declared valid for it.""" + result = execute_admin_command(collection, _build_command(spec, sample_value)) + assertProperties(result, {"ok": Eq(1.0)}, msg=spec.msg, raw_res=True) + + +@pytest.mark.parametrize("bson_type,sample_value,spec", REJECTION_CASES) +def test_setDefaultRWConcern_bson_type_rejected(collection, bson_type, sample_value, spec): + """Test each field rejects BSON types outside its declared valid set.""" + result = execute_admin_command(collection, _build_command(spec, sample_value)) + assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_core_behavior.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_core_behavior.py new file mode 100644 index 000000000..56f5e470a --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_core_behavior.py @@ -0,0 +1,154 @@ +"""Core behavior tests for setDefaultRWConcern.""" + +import pytest + +from documentdb_tests.compatibility.tests.system.administration.utils.admin_test_case import ( + AdminTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, Gt + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel, pytest.mark.requires(cluster_admin=True)] + + +CORE_TESTS: list[AdminTestCase] = [ + AdminTestCase( + "set_both_defaults", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "majority"}, + "defaultWriteConcern": {"w": 1}, + }, + expected={"ok": Eq(1.0)}, + msg="Should succeed with both defaults", + ), + AdminTestCase( + "round_trip_write_concern", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={"getDefaultRWConcern": 1}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}}, + msg="getDefaultRWConcern should reflect the configured write concern", + ), + AdminTestCase( + "round_trip_read_concern", + setup_commands=({"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}},), + command={"getDefaultRWConcern": 1}, + expected={"ok": Eq(1.0), "defaultReadConcern": {"level": Eq("majority")}}, + msg="getDefaultRWConcern should reflect the configured read concern", + ), + AdminTestCase( + "idempotent_write_concern", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}}, + msg="Second application should succeed with same value", + ), + AdminTestCase( + "change_read_concern_level", + setup_commands=({"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local"}},), + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}}, + expected={"ok": Eq(1.0), "defaultReadConcern": {"level": Eq("majority")}}, + msg="Latest read concern level should win", + ), + AdminTestCase( + "change_write_concern_value", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": "majority"}}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq("majority")}}, + msg="Should succeed changing write concern value", + ), + AdminTestCase( + "observable_via_get", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={"getDefaultRWConcern": 1}, + expected={"defaultWriteConcernSource": Eq("global")}, + msg="Source should be 'global' after explicit set", + ), + AdminTestCase( + "unset_read_concern_reverts_source_to_implicit", + setup_commands=( + {"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}}, + {"setDefaultRWConcern": 1, "defaultReadConcern": {}}, + ), + command={"getDefaultRWConcern": 1}, + expected={"ok": Eq(1.0), "defaultReadConcernSource": Eq("implicit")}, + msg="unsetting the read concern with {} reverts its source from 'global' to 'implicit'", + ), + AdminTestCase( + "set_read_concern_preserves_existing_write_concern", + setup_commands=( + {"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}}, + {"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}}, + ), + command={"getDefaultRWConcern": 1}, + expected={ + "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}, + "defaultReadConcern": {"level": Eq("majority")}, + }, + msg="setting only the read concern leaves a previously-set write concern intact", + ), + AdminTestCase( + "idempotent_read_concern", + setup_commands=({"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}},), + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}}, + expected={"ok": Eq(1.0), "defaultReadConcern": {"level": Eq("majority")}}, + msg="re-applying the same read concern should succeed with the same value", + ), + AdminTestCase( + "round_trip_write_concern_majority", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": "majority"}},), + command={"getDefaultRWConcern": 1}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq("majority"), "wtimeout": Eq(0)}}, + msg="getDefaultRWConcern should reflect a w:'majority' write concern", + ), + AdminTestCase( + "round_trip_read_concern_available", + setup_commands=({"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "available"}},), + command={"getDefaultRWConcern": 1}, + expected={"ok": Eq(1.0), "defaultReadConcern": {"level": Eq("available")}}, + msg="getDefaultRWConcern should reflect an 'available' read concern", + ), + AdminTestCase( + "round_trip_both_defaults", + setup_commands=( + { + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "majority"}, + "defaultWriteConcern": {"w": 1}, + }, + ), + command={"getDefaultRWConcern": 1}, + expected={ + "defaultReadConcern": {"level": Eq("majority")}, + "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}, + }, + msg="getDefaultRWConcern reflects both defaults set in a single call", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(CORE_TESTS)) +def test_setDefaultRWConcern_core(collection, test): + """Run a setDefaultRWConcern core behavior case.""" + for setup in test.setup_commands: + execute_admin_command(collection, setup) + result = execute_admin_command(collection, test.command) + assertResult(result, expected=test.expected, msg=test.msg, raw_res=True) + + +def test_setDefaultRWConcern_update_op_time_advances(collection): + """Test that updateOpTime advances after a value-changing set.""" + execute_admin_command(collection, {"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}}) + before = execute_admin_command(collection, {"getDefaultRWConcern": 1}) + execute_admin_command( + collection, {"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": "majority"}} + ) + after = execute_admin_command(collection, {"getDefaultRWConcern": 1}) + assertResult( + after, + expected={"updateOpTime": Gt(before["updateOpTime"])}, + msg="updateOpTime should advance after a value-changing set", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_errors.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_errors.py new file mode 100644 index 000000000..eb2ef1a7d --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_errors.py @@ -0,0 +1,231 @@ +"""Error-case tests for setDefaultRWConcern.""" + +import pytest + +from documentdb_tests.compatibility.tests.system.administration.utils.admin_test_case import ( + AdminTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + FAILED_TO_PARSE_ERROR, + ILLEGAL_OPERATION_ERROR, + TYPE_MISMATCH_ERROR, + UNAUTHORIZED_ERROR, + UNKNOWN_REPL_WRITE_CONCERN_ERROR, + UNRECOGNIZED_COMMAND_FIELD_ERROR, +) +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel, pytest.mark.requires(cluster_admin=True)] + + +ERROR_TESTS: list[AdminTestCase] = [ + AdminTestCase( + "neither_default_specified", + command={"setDefaultRWConcern": 1}, + error_code=BAD_VALUE_ERROR, + msg="Should reject when neither default specified", + ), + AdminTestCase( + "unrecognized_field", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "unknownField": 1, + }, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="Should reject unrecognized fields", + ), + AdminTestCase( + "comment_does_not_mask_error", + command={"setDefaultRWConcern": 1, "comment": "a comment"}, + error_code=BAD_VALUE_ERROR, + msg="Comment should not mask at-least-one error", + ), + AdminTestCase( + "both_null_fails", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": None, + "defaultWriteConcern": None, + }, + error_code=BAD_VALUE_ERROR, + msg="Both null should fail like both absent", + ), + AdminTestCase( + "command_write_concern_unknown_tag_rejected", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "writeConcern": {"w": "customTag"}, + }, + error_code=UNKNOWN_REPL_WRITE_CONCERN_ERROR, + msg="unknown tag in the command-level writeConcern should be rejected", + ), + AdminTestCase( + "read_level_linearizable_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "linearizable"}}, + error_code=BAD_VALUE_ERROR, + msg="linearizable not supported as default", + ), + AdminTestCase( + "read_level_snapshot_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "snapshot"}}, + error_code=BAD_VALUE_ERROR, + msg="snapshot not supported as default", + ), + AdminTestCase( + "unsupported_read_level_arbitrary", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "foobar"}}, + error_code=BAD_VALUE_ERROR, + msg="Should reject arbitrary string level", + ), + AdminTestCase( + "write_concern_unknown_tag", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": "customTag"}}, + error_code=UNKNOWN_REPL_WRITE_CONCERN_ERROR, + msg="Unknown tag should be rejected", + ), + AdminTestCase( + "write_concern_w0_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 0}}, + error_code=BAD_VALUE_ERROR, + msg="w:0 unsupported as default", + ), + AdminTestCase( + "read_level_non_string_type", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": 123}}, + error_code=TYPE_MISMATCH_ERROR, + msg="level must be string", + ), + AdminTestCase( + "read_concern_extra_key_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local", "extra": 1}}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="Extra key should be rejected", + ), + AdminTestCase( + "non_level_key_in_read_concern", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"readPreference": "primary"}}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="Should reject non-level key in defaultReadConcern", + ), + AdminTestCase( + "read_level_empty_string_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": ""}}, + error_code=BAD_VALUE_ERROR, + msg="Empty string level should be rejected", + ), + AdminTestCase( + "read_level_wrong_case_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "LOCAL"}}, + error_code=BAD_VALUE_ERROR, + msg="Levels are case-sensitive", + ), + AdminTestCase( + "write_concern_negative_w_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": -1}}, + error_code=FAILED_TO_PARSE_ERROR, + msg="Negative w should be rejected", + ), + AdminTestCase( + "write_concern_w_bool_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": True}}, + error_code=FAILED_TO_PARSE_ERROR, + msg="boolean w should be rejected", + ), + AdminTestCase( + "write_concern_w_null_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": None}}, + error_code=UNKNOWN_REPL_WRITE_CONCERN_ERROR, + msg="null w is coerced to an empty mode name and rejected", + ), + AdminTestCase( + "write_concern_wtimeout_only_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"wtimeout": 100}}, + error_code=BAD_VALUE_ERROR, + msg="wtimeout-only should be rejected", + ), + AdminTestCase( + "write_concern_journal_non_bool_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "j": "true"}}, + error_code=TYPE_MISMATCH_ERROR, + msg="j must be boolean", + ), + AdminTestCase( + "read_concern_nested_level_rejected", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": {"nested": "local"}}}, + error_code=TYPE_MISMATCH_ERROR, + msg="Nested level object should be rejected", + ), + AdminTestCase( + "write_concern_oversized_w_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 9999}}, + error_code=FAILED_TO_PARSE_ERROR, + msg="Oversized w should be rejected", + ), + AdminTestCase( + "write_concern_extra_field_rejected", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "unknownField": 1}}, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="Extra WC field should be rejected", + ), + AdminTestCase( + "atomic_failure_invalid_read_with_valid_write", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "snapshot"}, + "defaultWriteConcern": {"w": 1}, + }, + error_code=BAD_VALUE_ERROR, + msg="Should fail atomically when read concern is invalid", + ), + AdminTestCase( + "atomic_failure_invalid_write", + setup_commands=({"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local"}},), + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "majority"}, + "defaultWriteConcern": {"w": 0}, + }, + error_code=BAD_VALUE_ERROR, + msg="Should fail atomically when write concern is invalid", + ), + AdminTestCase( + "write_concern_cannot_unset_once_set", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {}}, + error_code=ILLEGAL_OPERATION_ERROR, + msg="Cannot unset write concern once set", + ), + AdminTestCase( + "set_read_and_unset_write_rejected", + setup_commands=({"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}},), + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "defaultWriteConcern": {}, + }, + error_code=ILLEGAL_OPERATION_ERROR, + msg="Cannot unset write concern even with valid read", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_setDefaultRWConcern_errors(collection, test): + """Run a setDefaultRWConcern error case.""" + for setup in test.setup_commands: + execute_admin_command(collection, setup) + result = execute_admin_command(collection, test.command) + assertResult(result, error_code=test.error_code, msg=test.msg) + + +def test_setDefaultRWConcern_non_admin_database_rejected(collection): + """Test setDefaultRWConcern is rejected when run against a non-admin database.""" + result = execute_command( + collection, {"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local"}} + ) + assertResult(result, error_code=UNAUTHORIZED_ERROR, msg="Should fail on non-admin database") diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_response_structure.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_response_structure.py new file mode 100644 index 000000000..146dbc8e2 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_response_structure.py @@ -0,0 +1,75 @@ +"""Response structure tests for setDefaultRWConcern.""" + +import pytest + +from documentdb_tests.compatibility.tests.system.administration.utils.admin_test_case import ( + AdminTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, IsType + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel, pytest.mark.requires(cluster_admin=True)] + +READ_CONCERN_CMD = {"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local"}} +WRITE_CONCERN_CMD = {"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}} + + +RESPONSE_TESTS: list[AdminTestCase] = [ + AdminTestCase( + "ok_field", + command=READ_CONCERN_CMD, + expected={"ok": Eq(1.0)}, + msg="success response contains ok field with value 1", + ), + AdminTestCase( + "echoes_write_concern", + command=WRITE_CONCERN_CMD, + expected={"defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}}, + msg="success response echoes the configured defaultWriteConcern", + ), + AdminTestCase( + "echoes_read_concern", + command=READ_CONCERN_CMD, + expected={"defaultReadConcern": {"level": Eq("local")}}, + msg="success response echoes the configured defaultReadConcern", + ), + AdminTestCase( + "update_op_time_is_timestamp", + command=READ_CONCERN_CMD, + expected={"updateOpTime": IsType("timestamp")}, + msg="success response contains updateOpTime field of timestamp type", + ), + AdminTestCase( + "update_wall_clock_time_is_date", + command=READ_CONCERN_CMD, + expected={"updateWallClockTime": IsType("date")}, + msg="success response contains updateWallClockTime field of date type", + ), + AdminTestCase( + "local_update_wall_clock_time_is_date", + command=READ_CONCERN_CMD, + expected={"localUpdateWallClockTime": IsType("date")}, + msg="success response contains localUpdateWallClockTime field of date type", + ), + AdminTestCase( + "write_concern_source_is_string", + command=READ_CONCERN_CMD, + expected={"defaultWriteConcernSource": IsType("string")}, + msg="success response contains defaultWriteConcernSource field of string type", + ), + AdminTestCase( + "read_concern_source_is_string", + command=READ_CONCERN_CMD, + expected={"defaultReadConcernSource": IsType("string")}, + msg="success response contains defaultReadConcernSource field of string type", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(RESPONSE_TESTS)) +def test_setDefaultRWConcern_response_structure(collection, test): + """Run a setDefaultRWConcern response-structure case.""" + result = execute_admin_command(collection, test.command) + assertResult(result, expected=test.expected, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_valid_inputs.py b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_valid_inputs.py new file mode 100644 index 000000000..aa7041fa8 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setDefaultRWConcern/test_setDefaultRWConcern_valid_inputs.py @@ -0,0 +1,143 @@ +"""Accepted-input tests for setDefaultRWConcern.""" + +import pytest + +from documentdb_tests.compatibility.tests.system.administration.utils.admin_test_case import ( + AdminTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel, pytest.mark.requires(cluster_admin=True)] + + +VALID_INPUT_TESTS: list[AdminTestCase] = [ + AdminTestCase( + "both_defaults_plus_command_write_concern", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "defaultWriteConcern": {"w": 1}, + "writeConcern": {"w": 1}, + }, + expected={"ok": Eq(1.0)}, + msg="both defaults plus a command-level writeConcern succeeds", + ), + AdminTestCase( + "empty_read_concern_satisfies_requirement", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {}}, + expected={"ok": Eq(1.0)}, + msg="empty document {} for defaultReadConcern satisfies the at-least-one requirement", + ), + AdminTestCase( + "accepts_command_write_concern", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "writeConcern": {"w": 1}, + }, + expected={"ok": Eq(1.0)}, + msg="command accepts its own writeConcern field for acknowledgement", + ), + AdminTestCase( + "command_write_concern_w0", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": {"level": "local"}, + "writeConcern": {"w": 0}, + }, + expected={"ok": Eq(1.0)}, + msg="command-level writeConcern w:0 should work independently of defaultWriteConcern", + ), + AdminTestCase( + "read_level_local", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "local"}}, + expected={"ok": Eq(1.0)}, + msg="level 'local' is accepted", + ), + AdminTestCase( + "read_level_available", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "available"}}, + expected={"ok": Eq(1.0)}, + msg="level 'available' is accepted", + ), + AdminTestCase( + "read_level_majority", + command={"setDefaultRWConcern": 1, "defaultReadConcern": {"level": "majority"}}, + expected={"ok": Eq(1.0)}, + msg="level 'majority' is accepted", + ), + AdminTestCase( + "write_concern_w_majority", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": "majority"}}, + expected={"ok": Eq(1.0)}, + msg="w:'majority' is accepted", + ), + AdminTestCase( + "write_concern_journal_flag", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "j": True}}, + expected={"ok": Eq(1.0)}, + msg="journal/j boolean flag is accepted", + ), + AdminTestCase( + "write_concern_fractional_w", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1.5}}, + expected={"ok": Eq(1.0)}, + msg="fractional double w is accepted", + ), + AdminTestCase( + "write_concern_wtimeout_string", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "wtimeout": "100"}}, + expected={"ok": Eq(1.0)}, + msg="wtimeout as string is accepted (coerced)", + ), + AdminTestCase( + "write_concern_negative_wtimeout", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "wtimeout": -1}}, + expected={"ok": Eq(1.0)}, + msg="negative wtimeout is accepted (coerced)", + ), + AdminTestCase( + "write_concern_wtimeout_stored", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1, "wtimeout": 5000}}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(5000)}}, + msg="wtimeout should be stored", + ), + AdminTestCase( + "write_concern_wtimeout_defaults_to_zero", + command={"setDefaultRWConcern": 1, "defaultWriteConcern": {"w": 1}}, + expected={"ok": Eq(1.0), "defaultWriteConcern": {"w": Eq(1), "wtimeout": Eq(0)}}, + msg="wtimeout should default to 0", + ), + AdminTestCase( + "read_concern_null_as_omitted", + command={ + "setDefaultRWConcern": 1, + "defaultReadConcern": None, + "defaultWriteConcern": {"w": 1}, + }, + expected={"ok": Eq(1.0)}, + msg="defaultReadConcern null is treated as omitted", + ), + AdminTestCase( + "write_concern_null_as_omitted", + command={ + "setDefaultRWConcern": 1, + "defaultWriteConcern": None, + "defaultReadConcern": {"level": "local"}, + }, + expected={"ok": Eq(1.0)}, + msg="defaultWriteConcern null is treated as omitted", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(VALID_INPUT_TESTS)) +def test_setDefaultRWConcern_valid_inputs(collection, test): + """Run a setDefaultRWConcern accepted-input case.""" + for setup in test.setup_commands: + execute_admin_command(collection, setup) + result = execute_admin_command(collection, test.command) + assertResult(result, expected=test.expected, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/administration/utils/__init__.py b/documentdb_tests/compatibility/tests/system/administration/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/utils/admin_test_case.py b/documentdb_tests/compatibility/tests/system/administration/utils/admin_test_case.py new file mode 100644 index 000000000..a923a0346 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/utils/admin_test_case.py @@ -0,0 +1,19 @@ +"""Shared test case for administration command tests.""" + +from dataclasses import dataclass +from typing import Any, Optional + +from documentdb_tests.framework.test_case import BaseTestCase + + +@dataclass(frozen=True) +class AdminTestCase(BaseTestCase): + """Test case for an administration command. + + Attributes: + command: The command document to execute. + setup_commands: Commands to run before the test command. + """ + + command: Optional[dict[str, Any]] = None + setup_commands: tuple[dict[str, Any], ...] = ()