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/conftest.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/conftest.py new file mode 100644 index 000000000..c87774256 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/conftest.py @@ -0,0 +1,31 @@ +"""Shared fixtures for setParameter tests. + +Provides an autouse fixture that snapshots mutable server parameters before +each test and unconditionally restores them after, preventing leaked state +even when a test fails. +""" + +import pytest + +from documentdb_tests.framework.executor import execute_admin_command + + +@pytest.fixture(autouse=True) +def restore_server_params(collection): + """Snapshot mutable server parameters before the test, restore after. + + This runs unconditionally (autouse) so that any test modifying server state + cannot leak changes into subsequent tests — whether it passes, fails, or errors. + """ + # Snapshot current values + snapshot = {} + for param in ["logLevel", "quiet", "automationServiceDescriptor", "logComponentVerbosity"]: + result = execute_admin_command(collection, {"getParameter": 1, param: 1}) + if isinstance(result, dict) and param in result: + snapshot[param] = result[param] + + yield + + # Restore original values unconditionally + for param, value in snapshot.items(): + execute_admin_command(collection, {"setParameter": 1, param: value}) 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..387c5ef7b --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_argument_validation.py @@ -0,0 +1,56 @@ +"""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 [Argument Validation]: setParameter accepts valid argument variations. +ARGUMENT_VALIDATION_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", + ), + CommandTestCase( + "fractional_double_coerces", + command=lambda ctx: {"setParameter": 1, "logLevel": 1.5}, + expected={"ok": 1.0}, + msg="setParameter should truncate fractional double for integer param", + ), + CommandTestCase( + "integer_valid_range", + command=lambda ctx: {"setParameter": 1, "logLevel": 5}, + expected={"ok": 1.0}, + msg="setParameter should accept logLevel at upper bound", + ), + CommandTestCase( + "string_param_valid", + command=lambda ctx: {"setParameter": 1, "automationServiceDescriptor": "test"}, + expected={"ok": 1.0}, + msg="setParameter should accept valid string value", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ARGUMENT_VALIDATION_TESTS)) +def test_setParameter_argument_validation(database_client, collection, test): + """Test setParameter argument validation 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) 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..bc083705d --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_bson_type_validation.py @@ -0,0 +1,83 @@ +"""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.""" + result = execute_admin_command(collection, {"setParameter": 1, "quiet": value}) + assertSuccessPartial( + result, {"ok": 1.0}, msg=f"setParameter boolean param should accept {desc}" + ) + + +# 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}" + ) 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..c1afadb76 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_core_behavior.py @@ -0,0 +1,141 @@ +"""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", + ), + CommandTestCase( + "single_param_returns_ok", + command=lambda ctx: {"setParameter": 1, "logLevel": 1}, + expected={"ok": 1.0}, + msg="setParameter should return ok:1", + ), +] + + +@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) + + +# Property [Response Structure]: setParameter returns ok:1 and previous value in 'was'. +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'" + ) + + +def test_setParameter_boolean_was_type(collection): + """Test setParameter 'was' field for a boolean parameter is boolean type.""" + 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" + ) + + +# 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" + ) + + +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") + + +# 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.""" + 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") + + +# 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" + ) 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..224cd990d --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_errors.py @@ -0,0 +1,268 @@ +"""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 ( + 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" + ) 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..edc3b7629 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_setParameter_hierarchical_params.py @@ -0,0 +1,148 @@ +"""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. +""" + +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 [Single-Command Hierarchical]: simple set/read operations. +HIERARCHICAL_SINGLE_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "multi_member_set", + command=lambda ctx: { + "setParameter": 1, + "logComponentVerbosity": {"command": {"verbosity": 2}, "network": {"verbosity": 3}}, + }, + expected={"ok": 1.0}, + msg="setParameter multi-member set should succeed", + ), + CommandTestCase( + "bare_numeric_form", + command=lambda ctx: {"setParameter": 1, "logComponentVerbosity": {"command": 4}}, + expected={"ok": 1.0}, + msg="setParameter should accept bare numeric form", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(HIERARCHICAL_SINGLE_TESTS)) +def test_setParameter_hierarchical_single(database_client, collection, test): + """Test setParameter hierarchical single-command 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) + + +# 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, {"setParameter": 1, "logLevel": 0}) + result = execute_admin_command(collection, {"getParameter": 1, "logComponentVerbosity": 1}) + assertSuccessPartial( + result, + {"logComponentVerbosity": {"verbosity": 0}}, + msg="setParameter top-level verbosity should match logLevel", + ) + + +# 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", + ) + + +# 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 Readback]: bare numeric form takes effect when read back. +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", + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_smoke_setParameter.py b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_smoke_setParameter.py index 5b60edc76..b91245425 100644 --- a/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_smoke_setParameter.py +++ b/documentdb_tests/compatibility/tests/system/administration/commands/setParameter/test_smoke_setParameter.py @@ -14,12 +14,7 @@ def test_smoke_setParameter(collection): """Test basic setParameter behavior.""" - get_result = execute_admin_command(collection, {"getParameter": 1, "logLevel": 1}) - original_value = get_result.get("logLevel", 0) if not isinstance(get_result, Exception) else 0 - result = execute_admin_command(collection, {"setParameter": 1, "logLevel": 0}) expected = {"ok": 1.0} assertSuccessPartial(result, expected, msg="Should support setParameter command") - - execute_admin_command(collection, {"setParameter": 1, "logLevel": original_value})