diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/__init__.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_argument_validation.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_argument_validation.py new file mode 100644 index 000000000..ee5af9a5b --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_argument_validation.py @@ -0,0 +1,70 @@ +"""Tests for setParameter argument validation (success cases). + +Validates control field Int64 max, parameter value range, and string param acceptance. +Type coercion matrices are in test_setParameter_bson_type_validation.py. +""" + +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_admin_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import INT64_MAX + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel] + + +# Property [Name Acceptance]: setParameter accepts edge-case control field values. +NAME_ACCEPTANCE_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "control_field_int64_max", + command=lambda ctx: {"setParameter": INT64_MAX, "logLevel": 0}, + expected={"ok": 1.0}, + msg="setParameter should accept Int64 max as control field value", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NAME_ACCEPTANCE_TESTS)) +def test_setParameter_name_accepted(database_client, collection, test): + """Test setParameter accepts valid parameter names.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_admin_command(collection, test.build_command(ctx)) + assertSuccessPartial(result, test.build_expected(ctx), msg=test.msg) + + +# Standalone tests below require save/restore of server state after each set. + + +# Property [Integer Range]: integer-typed parameters accept values within valid bounds. +def test_setParameter_integer_param_with_fractional_double_coerces(collection): + """Test setParameter truncates fractional double for integer-typed param.""" + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 1.5}) + assertSuccessPartial( + result, {"ok": 1.0}, msg="setParameter should truncate fractional double for integer param" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + + +def test_setParameter_integer_param_valid_range(collection): + """Test setParameter accepts integer at valid upper bound.""" + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 5}) + assertSuccessPartial( + result, {"ok": 1.0}, msg="setParameter should accept logLevel at upper bound" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + + +# Property [String Param Acceptance]: string-typed parameters accept valid strings. +def test_setParameter_string_param_valid_succeeds(collection): + """Test setParameter accepts valid short string for string-typed param.""" + result = execute_admin_command( + collection, {"setParameter": 1, "automationServiceDescriptor": "test"} + ) + assertSuccessPartial(result, {"ok": 1.0}, msg="setParameter should accept valid string value") + execute_admin_command(collection, {"setParameter": 1, "automationServiceDescriptor": ""}) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_bson_type_validation.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_bson_type_validation.py new file mode 100644 index 000000000..2a66afe30 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_bson_type_validation.py @@ -0,0 +1,86 @@ +"""Tests for setParameter BSON type validation (success cases). + +Validates control field acceptance of all BSON types, and type coercion +behavior for boolean and integer parameter values. +""" + +import pytest +from bson import Decimal128, Int64 + +from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.framework.bson_type_validator import ( + BsonType, + BsonTypeTestCase, + generate_bson_acceptance_test_cases, +) +from documentdb_tests.framework.executor import execute_admin_command + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel] + + +# Property [Control Field Acceptance]: setParameter control field accepts all BSON types. +CONTROL_FIELD_PARAM = [ + BsonTypeTestCase( + id="setParameter_control", + msg="setParameter control field should accept all BSON types", + keyword="setParameter", + valid_types=list(BsonType), + requires={"logLevel": 0}, + ), +] + +CONTROL_FIELD_ACCEPTANCE = generate_bson_acceptance_test_cases(CONTROL_FIELD_PARAM) + + +@pytest.mark.parametrize("bson_type,sample_value,spec", CONTROL_FIELD_ACCEPTANCE) +def test_setParameter_control_field_bson_type_accepted(collection, bson_type, sample_value, spec): + """Test setParameter control field accepts all BSON types.""" + result = execute_admin_command(collection, {"setParameter": sample_value, "logLevel": 0}) + assertSuccessPartial(result, {"ok": 1.0}, msg=f"{spec.msg} (bson_type={bson_type.value})") + + +# Property [Boolean Coercion]: boolean-typed params accept many BSON types via coercion. +@pytest.mark.parametrize( + "value,desc", + [ + pytest.param(True, "bool True", id="true"), + pytest.param(False, "bool False", id="false"), + pytest.param(1, "int 1", id="int1"), + pytest.param(0, "int 0", id="int0"), + pytest.param(1.0, "double 1.0", id="double1"), + pytest.param(0.0, "double 0.0", id="double0"), + pytest.param(Int64(1), "Int64(1)", id="long1"), + pytest.param(Int64(0), "Int64(0)", id="long0"), + pytest.param("true", "string 'true'", id="string"), + pytest.param([True], "array [True]", id="array"), + pytest.param({"a": True}, "document", id="document"), + ], +) +def test_setParameter_boolean_coercion_accepted(collection, value, desc): + """Test setParameter boolean parameter coercion.""" + original = execute_admin_command(collection, {"getParameter": 1, "quiet": 1}) + result = execute_admin_command(collection, {"setParameter": 1, "quiet": value}) + assertSuccessPartial( + result, {"ok": 1.0}, msg=f"setParameter boolean param should accept {desc}" + ) + execute_admin_command(collection, {"setParameter": 1, "quiet": original["quiet"]}) + + +# Property [Integer Coercion Accepted]: integer-typed params accept whole-number numerics. +@pytest.mark.parametrize( + "value,desc", + [ + pytest.param(1, "int32", id="int32"), + pytest.param(Int64(1), "Int64", id="long"), + pytest.param(1.0, "whole double", id="whole_double"), + pytest.param(Decimal128("1"), "Decimal128 whole", id="decimal128_whole"), + pytest.param(True, "bool True", id="bool"), + ], +) +def test_setParameter_integer_coercion_accepted(collection, value, desc): + """Test setParameter integer parameter coercion.""" + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": value}) + assertSuccessPartial( + result, {"ok": 1.0}, msg=f"setParameter integer param should accept {desc}" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_core_behavior.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_core_behavior.py new file mode 100644 index 000000000..6394d9cf9 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_core_behavior.py @@ -0,0 +1,155 @@ +"""Tests for setParameter command core behavior. + +Validates single/multiple parameter modification, admin database requirement, +runtime vs startup-only parameters, command shape, response structure, and +getParameter interaction. +""" + +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_admin_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel] + + +# Property [Command Acceptance]: setParameter accepts valid commands on admin db. +COMMAND_ACCEPTANCE_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "admin_db_succeeds", + command=lambda ctx: {"setParameter": 1, "logLevel": 0}, + expected={"ok": 1.0}, + msg="setParameter should succeed on admin db", + ), + CommandTestCase( + "comment_field_accepted", + command=lambda ctx: {"setParameter": 1, "logLevel": 0, "comment": "test"}, + expected={"ok": 1.0}, + msg="setParameter should accept comment field alongside params", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(COMMAND_ACCEPTANCE_TESTS)) +def test_setParameter_accepted(database_client, collection, test): + """Test setParameter command acceptance cases.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_admin_command(collection, test.build_command(ctx)) + assertSuccessPartial(result, test.build_expected(ctx), msg=test.msg) + + +# Standalone tests below require save/restore of server state after each set. + + +# Property [Response Structure]: setParameter returns ok:1 and previous value in 'was'. +def test_setParameter_single_param_returns_ok(collection): + """Test setParameter with one runtime-settable parameter returns ok:1.""" + original = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + original_val = original["logLevel"] + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 1}) + assertSuccessPartial(result, {"ok": 1.0}, msg="setParameter should return ok:1") + execute_admin_command(collection, {"setParameter": 1, "logLevel": original_val}) + + +def test_setParameter_returns_was_field(collection): + """Test setParameter response includes the previous value in 'was' field.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 2}) + assertSuccessPartial( + result, {"ok": 1.0, "was": 0}, msg="setParameter should report previous value in 'was'" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + + +def test_setParameter_boolean_was_type(collection): + """Test setParameter 'was' field for a boolean parameter is boolean type.""" + original = execute_admin_command(collection, {"getParameter": 1, "quiet": 1}) + execute_admin_command(collection, {"setParameter": 1, "quiet": False}) + result = execute_admin_command(collection, {"setParameter": 1, "quiet": True}) + assertSuccessPartial( + result, {"ok": 1.0, "was": False}, msg="setParameter 'was' should be boolean for quiet" + ) + execute_admin_command(collection, {"setParameter": 1, "quiet": original["quiet"]}) + + +# Property [Value Persistence]: setParameter changes are reflected in getParameter. +def test_setParameter_getParameter_reflects_new_value(collection): + """Test setParameter changes are reflected via getParameter.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 3}) + result = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + assertSuccessPartial( + result, {"logLevel": 3}, msg="setParameter should reflect new value via getParameter" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + + +def test_setParameter_getParameter_unchanged_after_failure(collection): + """Test setParameter getParameter is unchanged after a failed set.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": "invalid"}) + result = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + assertSuccessPartial( + result, {"logLevel": 0}, msg="setParameter should leave value unchanged after failure" + ) + + +# Property [Idempotency]: setting a parameter to its current value succeeds. +def test_setParameter_idempotent_set(collection): + """Test setParameter setting a parameter to its current value returns ok:1.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + assertSuccessPartial( + result, {"ok": 1.0, "was": 0}, msg="setParameter should succeed idempotently" + ) + + +# Property [Multiple Parameters]: setParameter sets multiple params in one command. +def test_setParameter_two_params_returns_ok(collection): + """Test setParameter with two params in one command sets both.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0, "quiet": False}) + result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 1, "quiet": True}) + assertSuccessPartial(result, {"ok": 1.0}, msg="setParameter should set both parameters") + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0, "quiet": False}) + + +# Property [Round Trip]: value can be restored via 'was' field. +def test_setParameter_round_trip_restore(collection): + """Test setParameter round-trip: set new, restore via 'was' field.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + set_result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 3}) + original_val = set_result["was"] + execute_admin_command(collection, {"setParameter": 1, "logLevel": original_val}) + result = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + assertSuccessPartial(result, {"logLevel": 0}, msg="setParameter should restore to original") + + +# Property [Boolean Toggle]: boolean-typed parameters toggle correctly. +def test_setParameter_boolean_toggle(collection): + """Test setParameter boolean-typed parameter toggles correctly.""" + original = execute_admin_command(collection, {"getParameter": 1, "quiet": 1}) + execute_admin_command(collection, {"setParameter": 1, "quiet": False}) + result = execute_admin_command(collection, {"getParameter": 1, "quiet": 1}) + assertSuccessPartial(result, {"quiet": False}, msg="setParameter boolean should be False") + execute_admin_command(collection, {"setParameter": 1, "quiet": original["quiet"]}) + + +# Property [Ordering Independence]: parameter order in command does not affect result. +def test_setParameter_ordering_independence(collection): + """Test setParameter parameter order in command does not affect result.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0, "quiet": False}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 2, "quiet": True}) + state1 = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0, "quiet": False}) + execute_admin_command(collection, {"setParameter": 1, "quiet": True, "logLevel": 2}) + state2 = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + assertSuccessPartial( + state2, {"logLevel": state1["logLevel"]}, msg="setParameter order should not matter" + ) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0, "quiet": False}) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_errors.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_errors.py new file mode 100644 index 000000000..8af1f02bc --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_errors.py @@ -0,0 +1,278 @@ +"""Tests for setParameter error cases. + +ALL error assertions for setParameter are consolidated in this file. +""" + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import ( + assertFailureCode, + assertResult, + assertSuccessPartial, +) +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + INVALID_OPTIONS_ERROR, + OVERFLOW_ERROR, + TYPE_MISMATCH_ERROR, + UNAUTHORIZED_ERROR, +) +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.test_constants import INT32_MAX, INT32_MIN + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel] + + +# Property [Error Codes]: setParameter returns correct error codes for invalid inputs. +ERROR_CODE_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "nonexistent_param", + command=lambda ctx: {"setParameter": 1, "nonExistentXYZ": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject non-existent param", + ), + CommandTestCase( + "startup_only_param", + command=lambda ctx: {"setParameter": 1, "port": 27018}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject startup-only param", + ), + CommandTestCase( + "out_of_range_below_min", + command=lambda ctx: {"setParameter": 1, "logLevel": -1}, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject value below minimum", + ), + CommandTestCase( + "above_int32_max", + command=lambda ctx: {"setParameter": 1, "logLevel": INT32_MAX + 1}, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject value exceeding int32 max", + ), + CommandTestCase( + "empty_param_name", + command=lambda ctx: {"setParameter": 1, "": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject empty param name", + ), + CommandTestCase( + "no_param_pair", + command=lambda ctx: {"setParameter": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject missing param pair", + ), + CommandTestCase( + "multi_param_with_invalid", + command=lambda ctx: {"setParameter": 1, "logLevel": 0, "nonExistentXYZ": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject multi-param command when one is invalid", + ), +] + +# Property [Name Rejection]: setParameter rejects invalid parameter names. +NAME_REJECTION_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "case_sensitive", + command=lambda ctx: {"setParameter": 1, "LogLevel": 0}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject wrong-case param name", + ), + CommandTestCase( + "long_name", + command=lambda ctx: {"setParameter": 1, "a" * 1000: 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject very long param name", + ), + CommandTestCase( + "dotted_name", + command=lambda ctx: {"setParameter": 1, "log.level": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject dotted param name", + ), + CommandTestCase( + "dollar_name", + command=lambda ctx: {"setParameter": 1, "$logLevel": 1}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject dollar-prefixed param name", + ), + CommandTestCase( + "whitespace_name", + command=lambda ctx: {"setParameter": 1, " logLevel ": 0}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject whitespace in param name", + ), + CommandTestCase( + "numeric_string_name", + command=lambda ctx: {"setParameter": 1, "12345": 0}, + error_code=INVALID_OPTIONS_ERROR, + msg="setParameter should reject numeric string param name", + ), +] + +# Property [Value Type Rejection]: setParameter rejects invalid value types. +VALUE_TYPE_REJECTION_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "string_param_with_numeric", + command=lambda ctx: {"setParameter": 1, "automationServiceDescriptor": 12345}, + error_code=TYPE_MISMATCH_ERROR, + msg="setParameter should reject numeric value for string param", + ), + CommandTestCase( + "string_param_overlength", + command=lambda ctx: {"setParameter": 1, "automationServiceDescriptor": "x" * 65}, + error_code=OVERFLOW_ERROR, + msg="setParameter should reject over-length string value", + ), +] + +# Property [Hierarchical Type Rejection]: logComponentVerbosity rejects invalid types. +HIERARCHICAL_ERROR_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "hierarchical_string_value", + command=lambda ctx: {"setParameter": 1, "logComponentVerbosity": "abc"}, + error_code=TYPE_MISMATCH_ERROR, + msg="setParameter should reject string for logComponentVerbosity", + ), + CommandTestCase( + "hierarchical_nested_string_verbosity", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": "abc"}}, + }, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject string for nested verbosity", + ), + CommandTestCase( + "hierarchical_nested_overflow", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": INT32_MAX + 1}}, + }, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject overflow for nested verbosity", + ), + CommandTestCase( + "hierarchical_nested_underflow", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": INT32_MIN - 1}}, + }, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject underflow for nested verbosity", + ), + CommandTestCase( + "hierarchical_unknown_component", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"unknownComponent": {"verbosity": 1}}, + }, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject unknown component name", + ), + CommandTestCase( + "hierarchical_nested_nan_verbosity", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": float("nan")}}, + }, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject NaN for nested verbosity", + ), +] + +# Property [Integer Coercion Rejected]: integer-typed params reject non-numeric types. +INTEGER_COERCION_REJECTION_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "integer_rejects_string", + command=lambda ctx: {"setParameter": 1, "logLevel": "1"}, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject string for integer param", + ), + CommandTestCase( + "integer_rejects_array", + command=lambda ctx: {"setParameter": 1, "logLevel": [1]}, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject array for integer param", + ), + CommandTestCase( + "integer_rejects_document", + command=lambda ctx: {"setParameter": 1, "logLevel": {"a": 1}}, + error_code=BAD_VALUE_ERROR, + msg="setParameter should reject document for integer param", + ), +] + +ALL_ERROR_TESTS = ( + ERROR_CODE_TESTS + + NAME_REJECTION_TESTS + + VALUE_TYPE_REJECTION_TESTS + + HIERARCHICAL_ERROR_TESTS + + INTEGER_COERCION_REJECTION_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(ALL_ERROR_TESTS)) +def test_setParameter_errors(database_client, collection, test): + """Test setParameter error cases.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_admin_command(collection, test.build_command(ctx)) + assertResult( + result, + expected=test.build_expected(ctx), + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) + + +# Property [Non-Admin Rejection]: setParameter fails on non-admin database. +NON_ADMIN_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "non_admin_db", + command=lambda ctx: {"setParameter": 1, "logLevel": 0}, + error_code=UNAUTHORIZED_ERROR, + msg="setParameter should reject non-admin database", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NON_ADMIN_TESTS)) +def test_setParameter_non_admin_db(database_client, collection, test): + """Test setParameter fails on non-admin database.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + expected=test.build_expected(ctx), + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) + + +# Property [Atomicity]: multi-parameter set is atomic on failure. +# Standalone because it requires multi-step state verification. +def test_setParameter_multi_param_atomic_on_failure(collection): + """Test multi-parameter command with invalid second param does not apply first.""" + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 3, "nonExistentXYZ": 1}) + result = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) + assertSuccessPartial( + result, {"logLevel": 0}, msg="First param should not be applied when second fails" + ) + + +# Property [Name Collision]: parameter name matching control field name fails. +def test_setParameter_name_collides_with_control_field(collection): + """Test setParameter rejects param name that collides with control field.""" + result = execute_admin_command(collection, {"setParameter": 1, "setParameter": 2}) # noqa: F601 + assertFailureCode( + result, INVALID_OPTIONS_ERROR, msg="setParameter should reject name collision" + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_hierarchical_params.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_hierarchical_params.py new file mode 100644 index 000000000..418fe67bd --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_hierarchical_params.py @@ -0,0 +1,168 @@ +"""Tests for setParameter hierarchical/object parameter handling (success cases). + +Validates logComponentVerbosity nested parameter behavior including +read defaults, multi-member set, atomic rejection, and bare numeric form. + +Tests are standalone functions because each modifies server state and must +save/restore original values. +""" + +import pytest + +from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.framework.executor import execute_admin_command + +pytestmark = [pytest.mark.admin, pytest.mark.no_parallel] + + +# Property [Readability]: logComponentVerbosity is readable with defined defaults. +def test_setParameter_hierarchical_param_readable(collection): + """Test setParameter reading logComponentVerbosity returns a defined value.""" + result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result, {"ok": 1.0}, msg="setParameter should be able to read hierarchical param" + ) + + +def test_setParameter_hierarchical_nested_field_defined(collection): + """Test setParameter deeply nested verbosity field is defined.""" + result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result, + {"logComponentVerbosity": {"network": {"asio": {"verbosity": -1}}}}, + msg="setParameter nested component should have defined verbosity", + ) + + +# Property [Top-Level Verbosity]: top-level verbosity matches scalar logLevel. +def test_setParameter_hierarchical_top_level_verbosity(collection): + """Test setParameter top-level verbosity matches scalar logLevel default.""" + execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) + result2 = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result2, + {"logComponentVerbosity": {"verbosity": 0}}, + msg="setParameter top-level verbosity should match logLevel", + ) + + +# Property [Multi-Member Set]: setting several nested members in one command succeeds. +def test_setParameter_hierarchical_multi_member_set(collection): + """Test setParameter setting several nested members in one command succeeds.""" + execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": -1}, "network": {"verbosity": -1}}, + }, + ) + result = execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": 2}, "network": {"verbosity": 3}}, + }, + ) + assertSuccessPartial(result, {"ok": 1.0}, msg="setParameter multi-member set should succeed") + execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": -1}, "network": {"verbosity": -1}}, + }, + ) + + +# Property [Multi-Member Readback]: reading back reflects each member set. +def test_setParameter_hierarchical_multi_member_readback(collection): + """Test setParameter reading back reflects each member set in a multi-member command.""" + execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": 2}, "network": {"verbosity": 3}}, + }, + ) + read_result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + read_result, + {"logComponentVerbosity": {"command": {"verbosity": 2}, "network": {"verbosity": 3}}}, + msg="setParameter should reflect both members after set", + ) + execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": -1}, "network": {"verbosity": -1}}, + }, + ) + + +# Property [Atomic Rejection]: no members change when a multi-member set is rejected. +def test_setParameter_hierarchical_atomic_rejection(collection): + """Test setParameter no members are changed when a multi-member set is rejected.""" + execute_admin_command( + collection, + {"setParameter": 1, "logComponentVerbosity": {"command": {"verbosity": -1}}}, + ) + execute_admin_command( + collection, + { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": 2}, "unknownXYZ": {"verbosity": 1}}, + }, + ) + result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result, + {"logComponentVerbosity": {"command": {"verbosity": -1}}}, + msg="setParameter should not change any member on atomic rejection", + ) + + +# Property [Clear With Negative]: setting -1 resets to inherited default. +def test_setParameter_hierarchical_clear_with_negative(collection): + """Test setParameter clearing nested members with -1 resets to inherited default.""" + execute_admin_command( + collection, + {"setParameter": 1, "logComponentVerbosity": {"command": {"verbosity": 3}}}, + ) + execute_admin_command( + collection, + {"setParameter": 1, "logComponentVerbosity": {"command": {"verbosity": -1}}}, + ) + result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result, + {"logComponentVerbosity": {"command": {"verbosity": -1}}}, + msg="setParameter should reset member to -1 (inherited) after clear", + ) + + +# Property [Bare Numeric Form]: setting component with bare numeric level succeeds. +def test_setParameter_hierarchical_bare_numeric_form(collection): + """Test setParameter setting nested component with bare numeric level succeeds.""" + result = execute_admin_command( + collection, {"setParameter": 1, "logComponentVerbosity": {"command": 4}} + ) + assertSuccessPartial(result, {"ok": 1.0}, msg="setParameter should accept bare numeric form") + execute_admin_command( + collection, + {"setParameter": 1, "logComponentVerbosity": {"command": {"verbosity": -1}}}, + ) + + +def test_setParameter_hierarchical_bare_numeric_readback(collection): + """Test setParameter bare numeric component set takes effect when read back.""" + execute_admin_command(collection, {"setParameter": 1, "logComponentVerbosity": {"command": 4}}) + read_result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + read_result, + {"logComponentVerbosity": {"command": {"verbosity": 4}}}, + msg="setParameter bare numeric should set verbosity", + ) + execute_admin_command( + collection, + {"setParameter": 1, "logComponentVerbosity": {"command": {"verbosity": -1}}}, + )