diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_argument_handling.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_argument_handling.py new file mode 100644 index 000000000..9fa765003 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_argument_handling.py @@ -0,0 +1,172 @@ +"""Tests for serverStatus command argument handling. + +Validates that serverStatus accepts any BSON type as its argument value. +The command field value is ignored by serverStatus. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +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 + + +# Property [BSON Type Acceptance]: serverStatus accepts any BSON type as the command argument value. +ARGUMENT_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="int_1", + command={"serverStatus": 1}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept int 1 as argument value", + ), + DiagnosticTestCase( + id="int_0", + command={"serverStatus": 0}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept int 0 as argument value", + ), + DiagnosticTestCase( + id="int_neg1", + command={"serverStatus": -1}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept negative int as argument value", + ), + DiagnosticTestCase( + id="bool_true", + command={"serverStatus": True}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept boolean true as argument value", + ), + DiagnosticTestCase( + id="bool_false", + command={"serverStatus": False}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept boolean false as argument value", + ), + DiagnosticTestCase( + id="string", + command={"serverStatus": "hello"}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept string as argument value", + ), + DiagnosticTestCase( + id="empty_string", + command={"serverStatus": ""}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept empty string as argument value", + ), + DiagnosticTestCase( + id="null", + command={"serverStatus": None}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept null as argument value", + ), + DiagnosticTestCase( + id="empty_object", + command={"serverStatus": {}}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept empty object as argument value", + ), + DiagnosticTestCase( + id="empty_array", + command={"serverStatus": []}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept empty array as argument value", + ), + DiagnosticTestCase( + id="double", + command={"serverStatus": 1.5}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept double as argument value", + ), + DiagnosticTestCase( + id="int64", + command={"serverStatus": Int64(1)}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept int64 as argument value", + ), + DiagnosticTestCase( + id="decimal128", + command={"serverStatus": Decimal128("1")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept Decimal128 as argument value", + ), + DiagnosticTestCase( + id="decimal128_nan", + command={"serverStatus": Decimal128("NaN")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept Decimal128 NaN as argument value", + ), + DiagnosticTestCase( + id="infinity", + command={"serverStatus": float("inf")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept infinity as argument value", + ), + DiagnosticTestCase( + id="date", + command={"serverStatus": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept datetime as argument value", + ), + DiagnosticTestCase( + id="binData", + command={"serverStatus": Binary(b"")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept Binary as argument value", + ), + DiagnosticTestCase( + id="objectId", + command={"serverStatus": ObjectId()}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept ObjectId as argument value", + ), + DiagnosticTestCase( + id="regex", + command={"serverStatus": Regex("test")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept Regex as argument value", + ), + DiagnosticTestCase( + id="timestamp", + command={"serverStatus": Timestamp(0, 0)}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept Timestamp as argument value", + ), + DiagnosticTestCase( + id="minKey", + command={"serverStatus": MinKey()}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept MinKey as argument value", + ), + DiagnosticTestCase( + id="maxKey", + command={"serverStatus": MaxKey()}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept MaxKey as argument value", + ), + DiagnosticTestCase( + id="code", + command={"serverStatus": Code("function(){}")}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept JavaScript Code as argument value", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ARGUMENT_TYPE_TESTS)) +def test_serverStatus_argument_types(collection, test): + """Test that serverStatus accepts various BSON types as argument value.""" + result = execute_admin_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_core_behavior.py new file mode 100644 index 000000000..659d79235 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_core_behavior.py @@ -0,0 +1,257 @@ +"""Tests for serverStatus command core behavior. + +Validates semantic correctness of response field values, non-negative +counters, and cross-field consistency. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, Gt, Gte + +pytestmark = pytest.mark.admin + + +# Property [Positive Values]: serverStatus fields have expected positive or non-negative bounds. +POSITIVE_VALUE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="uptime_gte_0", + checks={"uptime": Gte(0)}, + msg="serverStatus should return uptime >= 0", + ), + DiagnosticTestCase( + id="uptimeMillis_gte_0", + checks={"uptimeMillis": Gte(0)}, + msg="serverStatus should return uptimeMillis >= 0", + ), + DiagnosticTestCase( + id="uptimeEstimate_gte_0", + checks={"uptimeEstimate": Gte(0)}, + msg="serverStatus should return uptimeEstimate >= 0", + ), + DiagnosticTestCase( + id="pid_gt_0", + checks={"pid": Gt(0)}, + msg="serverStatus should return pid > 0", + ), + DiagnosticTestCase( + id="connections_current_gte_1", + checks={"connections.current": Gte(1)}, + msg="serverStatus should report at least one current connection", + ), + DiagnosticTestCase( + id="connections_available_gte_0", + checks={"connections.available": Gte(0)}, + msg="serverStatus should return connections.available >= 0", + ), + DiagnosticTestCase( + id="connections_totalCreated_gte_1", + checks={"connections.totalCreated": Gte(1)}, + msg="serverStatus should report at least one created connection", + ), + DiagnosticTestCase( + id="mem_resident_gt_0", + checks={"mem.resident": Gt(0)}, + msg="serverStatus should report positive resident memory usage", + ), + DiagnosticTestCase( + id="mem_virtual_gt_0", + checks={"mem.virtual": Gt(0)}, + msg="serverStatus should report positive virtual memory usage", + ), + DiagnosticTestCase( + id="globalLock_totalTime_gte_0", + checks={"globalLock.totalTime": Gte(0)}, + msg="serverStatus should return globalLock.totalTime >= 0", + ), + DiagnosticTestCase( + id="process_is_mongod", + checks={"process": Eq("mongod")}, + msg="serverStatus should return process as mongod on a mongod instance", + ), +] + +# Property [Non-Negative Counters]: serverStatus counter fields are non-negative integers. +COUNTER_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="asserts_regular_gte_0", + checks={"asserts.regular": Gte(0)}, + msg="serverStatus should return asserts.regular >= 0", + ), + DiagnosticTestCase( + id="asserts_warning_gte_0", + checks={"asserts.warning": Gte(0)}, + msg="serverStatus should return asserts.warning >= 0", + ), + DiagnosticTestCase( + id="asserts_msg_gte_0", + checks={"asserts.msg": Gte(0)}, + msg="serverStatus should return asserts.msg >= 0", + ), + DiagnosticTestCase( + id="asserts_user_gte_0", + checks={"asserts.user": Gte(0)}, + msg="serverStatus should return asserts.user >= 0", + ), + DiagnosticTestCase( + id="asserts_rollovers_gte_0", + checks={"asserts.rollovers": Gte(0)}, + msg="serverStatus should return asserts.rollovers >= 0", + ), + DiagnosticTestCase( + id="opcounters_insert_gte_0", + checks={"opcounters.insert": Gte(0)}, + msg="serverStatus should return opcounters.insert >= 0", + ), + DiagnosticTestCase( + id="opcounters_query_gte_0", + checks={"opcounters.query": Gte(0)}, + msg="serverStatus should return opcounters.query >= 0", + ), + DiagnosticTestCase( + id="opcounters_update_gte_0", + checks={"opcounters.update": Gte(0)}, + msg="serverStatus should return opcounters.update >= 0", + ), + DiagnosticTestCase( + id="opcounters_delete_gte_0", + checks={"opcounters.delete": Gte(0)}, + msg="serverStatus should return opcounters.delete >= 0", + ), + DiagnosticTestCase( + id="opcounters_getmore_gte_0", + checks={"opcounters.getmore": Gte(0)}, + msg="serverStatus should return opcounters.getmore >= 0", + ), + DiagnosticTestCase( + id="opcounters_command_gte_0", + checks={"opcounters.command": Gte(0)}, + msg="serverStatus should return opcounters.command >= 0", + ), + DiagnosticTestCase( + id="network_bytesIn_gte_0", + checks={"network.bytesIn": Gte(0)}, + msg="serverStatus should return network.bytesIn >= 0", + ), + DiagnosticTestCase( + id="network_bytesOut_gte_0", + checks={"network.bytesOut": Gte(0)}, + msg="serverStatus should return network.bytesOut >= 0", + ), + DiagnosticTestCase( + id="network_numRequests_gte_0", + checks={"network.numRequests": Gte(0)}, + msg="serverStatus should return network.numRequests >= 0", + ), + DiagnosticTestCase( + id="catalogStats_collections_gte_0", + checks={"catalogStats.collections": Gte(0)}, + msg="serverStatus should return catalogStats.collections >= 0", + ), + DiagnosticTestCase( + id="catalogStats_capped_gte_0", + checks={"catalogStats.capped": Gte(0)}, + msg="serverStatus should return catalogStats.capped >= 0", + ), + DiagnosticTestCase( + id="catalogStats_views_gte_0", + checks={"catalogStats.views": Gte(0)}, + msg="serverStatus should return catalogStats.views >= 0", + ), +] + +VALUE_BOUND_TESTS = POSITIVE_VALUE_TESTS + COUNTER_TESTS + + +@pytest.mark.parametrize("test", pytest_params(VALUE_BOUND_TESTS)) +def test_serverStatus_value_bounds(collection, test): + """Verifies serverStatus fields have expected value bounds.""" + result = execute_admin_command(collection, {"serverStatus": 1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [Cross-Field Consistency]: serverStatus fields are consistent across calls. + + +def test_serverStatus_totalCreated_gte_current(collection): + """Verify serverStatus connections.totalCreated >= connections.current.""" + result = execute_admin_command(collection, {"serverStatus": 1}) + current = result["connections"]["current"] + assertProperties( + result, + {"connections.totalCreated": Gte(current)}, + raw_res=True, + msg="serverStatus should report totalCreated >= current connections", + ) + + +def test_serverStatus_uptimeMillis_consistent_with_uptime(collection): + """Verify serverStatus uptime is consistent with uptimeMillis.""" + result = execute_admin_command(collection, {"serverStatus": 1}) + uptime_from_millis = result["uptimeMillis"] // 1000 + assertProperties( + result, + {"uptime": Gte(uptime_from_millis - 1)}, + raw_res=True, + msg="serverStatus should return uptime >= uptimeMillis / 1000 (within 1s tolerance)", + ) + + +def test_serverStatus_pid_consistent_across_calls(collection): + """Verify serverStatus pid is stable across calls.""" + result1 = execute_admin_command(collection, {"serverStatus": 1}) + result2 = execute_admin_command(collection, {"serverStatus": 1}) + assertProperties( + result2, + {"pid": Eq(result1["pid"])}, + raw_res=True, + msg="serverStatus should return the same pid across calls", + ) + + +def test_serverStatus_host_consistent_across_calls(collection): + """Verify serverStatus host is stable across calls.""" + result1 = execute_admin_command(collection, {"serverStatus": 1}) + result2 = execute_admin_command(collection, {"serverStatus": 1}) + assertProperties( + result2, + {"host": Eq(result1["host"])}, + raw_res=True, + msg="serverStatus should return the same host across calls", + ) + + +def test_serverStatus_version_consistent_across_calls(collection): + """Verify serverStatus version is stable across calls.""" + result1 = execute_admin_command(collection, {"serverStatus": 1}) + result2 = execute_admin_command(collection, {"serverStatus": 1}) + assertProperties( + result2, + {"version": Eq(result1["version"])}, + raw_res=True, + msg="serverStatus should return the same version across calls", + ) + + +def test_serverStatus_cross_database_same_core_fields(collection): + """Verify serverStatus core identity fields match across databases.""" + admin_result = execute_admin_command(collection, {"serverStatus": 1}) + db_result = execute_command(collection, {"serverStatus": 1}) + assertProperties( + db_result, + { + "host": Eq(admin_result["host"]), + "version": Eq(admin_result["version"]), + "process": Eq(admin_result["process"]), + "pid": Eq(admin_result["pid"]), + }, + raw_res=True, + msg="serverStatus should return the same core fields regardless of database", + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_error_conditions.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_error_conditions.py new file mode 100644 index 000000000..4344447d4 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_error_conditions.py @@ -0,0 +1,40 @@ +"""Tests for serverStatus command error conditions. + +Validates that invalid usages of serverStatus produce appropriate errors. +serverStatus is highly permissive: it accepts any BSON type as the command +argument value, ignores unrecognized fields, and coerces toggle values to +truthy/falsy. The only error condition is a case-mismatched command name, +which is the generic unknown-command rejection, not serverStatus-specific. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import COMMAND_NOT_FOUND_ERROR +from documentdb_tests.framework.executor import execute_admin_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = pytest.mark.admin + + +# Property [Case Sensitivity]: serverStatus command name is case-sensitive. +ERROR_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="case_ServerStatus", + command={"ServerStatus": 1}, + error_code=COMMAND_NOT_FOUND_ERROR, + msg="serverStatus should reject case-mismatched command name", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_serverStatus_error_conditions(collection, test): + """Verify serverStatus rejects invalid usages with appropriate error codes.""" + result = execute_admin_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_field_toggle.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_field_toggle.py new file mode 100644 index 000000000..86e87e469 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_field_toggle.py @@ -0,0 +1,220 @@ +"""Tests for serverStatus command field toggle behavior. + +Validates section inclusion/exclusion via field toggles, multiple +toggle combinations, and toggle value coercion. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +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, Exists, IsType, NotExists + +pytestmark = pytest.mark.admin + + +# Property [Section Exclusion]: serverStatus omits a section when its toggle is set to 0. +EXCLUDE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="exclude_metrics", + command={"serverStatus": 1, "metrics": 0}, + checks={"metrics": NotExists()}, + msg="serverStatus should exclude metrics when set to 0", + ), + DiagnosticTestCase( + id="exclude_locks", + command={"serverStatus": 1, "locks": 0}, + checks={"locks": NotExists()}, + msg="serverStatus should exclude locks when set to 0", + ), + DiagnosticTestCase( + id="exclude_connections", + command={"serverStatus": 1, "connections": 0}, + checks={"connections": NotExists()}, + msg="serverStatus should exclude connections when set to 0", + ), + DiagnosticTestCase( + id="exclude_opcounters", + command={"serverStatus": 1, "opcounters": 0}, + checks={"opcounters": NotExists()}, + msg="serverStatus should exclude opcounters when set to 0", + ), + DiagnosticTestCase( + id="exclude_asserts", + command={"serverStatus": 1, "asserts": 0}, + checks={"asserts": NotExists()}, + msg="serverStatus should exclude asserts when set to 0", + ), + DiagnosticTestCase( + id="exclude_network", + command={"serverStatus": 1, "network": 0}, + checks={"network": NotExists()}, + msg="serverStatus should exclude network when set to 0", + ), + DiagnosticTestCase( + id="exclude_globalLock", + command={"serverStatus": 1, "globalLock": 0}, + checks={"globalLock": NotExists()}, + msg="serverStatus should exclude globalLock when set to 0", + ), + DiagnosticTestCase( + id="exclude_extra_info", + command={"serverStatus": 1, "extra_info": 0}, + checks={"extra_info": NotExists()}, + msg="serverStatus should exclude extra_info when set to 0", + ), + DiagnosticTestCase( + id="exclude_wiredTiger", + command={"serverStatus": 1, "wiredTiger": 0}, + checks={"wiredTiger": NotExists()}, + msg="serverStatus should exclude wiredTiger when set to 0", + ), + DiagnosticTestCase( + id="exclude_transactions", + command={"serverStatus": 1, "transactions": 0}, + checks={"transactions": NotExists()}, + msg="serverStatus should exclude transactions when set to 0", + ), +] + +# Property [Section Inclusion]: serverStatus includes non-default sections when toggled to 1. +INCLUDE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="include_mirroredReads", + command={"serverStatus": 1, "mirroredReads": 1}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include mirroredReads when set to 1", + ), +] + +# Property [Multiple Toggles]: serverStatus respects multiple section toggles simultaneously. +MULTIPLE_TOGGLE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="exclude_multiple_sections", + command={"serverStatus": 1, "metrics": 0, "locks": 0, "asserts": 0}, + checks={ + "metrics": NotExists(), + "locks": NotExists(), + "asserts": NotExists(), + "connections": IsType("object"), + "opcounters": IsType("object"), + }, + msg="serverStatus should exclude multiple sections when each is set to 0", + ), + DiagnosticTestCase( + id="exclude_some_include_some", + command={"serverStatus": 1, "metrics": 0, "mirroredReads": 1}, + checks={ + "metrics": NotExists(), + "mirroredReads": Exists(), + }, + msg="serverStatus should respect mixed include and exclude toggles", + ), +] + +# Property [Toggle Value Coercion]: serverStatus coerces toggle values to truthy or falsy. +TOGGLE_COERCION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="toggle_false_excludes", + command={"serverStatus": 1, "metrics": False}, + checks={"metrics": NotExists()}, + msg="serverStatus should exclude section when toggle is boolean false", + ), + DiagnosticTestCase( + id="toggle_true_includes", + command={"serverStatus": 1, "mirroredReads": True}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include section when toggle is boolean true", + ), + DiagnosticTestCase( + id="toggle_negative_includes", + command={"serverStatus": 1, "mirroredReads": -1}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include section when toggle is negative int (truthy)", + ), + DiagnosticTestCase( + id="toggle_string_includes", + command={"serverStatus": 1, "mirroredReads": "hello"}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include section when toggle is string (truthy)", + ), + DiagnosticTestCase( + id="toggle_null_excludes", + command={"serverStatus": 1, "metrics": None}, + checks={"metrics": NotExists()}, + msg="serverStatus should exclude section when toggle is null (falsy)", + ), + DiagnosticTestCase( + id="toggle_object_includes", + command={"serverStatus": 1, "mirroredReads": {}}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include section when toggle is empty object (truthy)", + ), + DiagnosticTestCase( + id="toggle_array_includes", + command={"serverStatus": 1, "mirroredReads": []}, + checks={"mirroredReads": Exists()}, + msg="serverStatus should include section when toggle is empty array (truthy)", + ), +] + +# Property [Core Fields Preserved]: serverStatus core fields are unaffected by section toggles. +CORE_FIELDS_PRESERVED_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="core_fields_present_with_exclusions", + command={"serverStatus": 1, "metrics": 0, "locks": 0}, + checks={ + "ok": Exists(), + "host": Exists(), + "version": Exists(), + "process": Exists(), + "pid": Exists(), + "uptime": Exists(), + "localTime": Exists(), + }, + msg="serverStatus should include core fields even when optional sections are excluded", + ), +] + +# Property [Default Exclusions]: serverStatus omits certain sections by default. +DEFAULT_EXCLUSION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="mirroredReads_excluded_by_default", + command={"serverStatus": 1}, + checks={"mirroredReads": NotExists()}, + msg="serverStatus should exclude mirroredReads by default", + ), +] + +# Property [Unrecognized Fields]: serverStatus accepts unknown fields as section toggles. +UNRECOGNIZED_FIELD_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="unrecognized_field_accepted", + command={"serverStatus": 1, "completelyFakeFieldName": 1}, + checks={"ok": Eq(1.0)}, + msg="serverStatus should accept unrecognized fields as section toggles", + ), +] + +TOGGLE_TESTS = ( + EXCLUDE_TESTS + + INCLUDE_TESTS + + MULTIPLE_TOGGLE_TESTS + + TOGGLE_COERCION_TESTS + + CORE_FIELDS_PRESERVED_TESTS + + DEFAULT_EXCLUSION_TESTS + + UNRECOGNIZED_FIELD_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(TOGGLE_TESTS)) +def test_serverStatus_field_toggle(collection, test): + """Verifies serverStatus section toggle behavior.""" + result = execute_admin_command(collection, test.command) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_response_structure.py new file mode 100644 index 000000000..bb74f0cd3 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/serverStatus/test_serverStatus_response_structure.py @@ -0,0 +1,316 @@ +"""Tests for serverStatus command response structure. + +Validates presence and types of core response fields, default sections, +and sub-document fields returned by serverStatus. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +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, Exists, IsType, NonEmptyStr + +pytestmark = pytest.mark.admin + + +# Property [Top-Level Fields]: serverStatus response contains core fields with expected types. +TOP_LEVEL_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="ok_is_1", + checks={"ok": Eq(1.0)}, + msg="serverStatus should return ok: 1.0", + ), + DiagnosticTestCase( + id="host_is_string", + checks={"host": IsType("string")}, + msg="serverStatus should return host as a string", + ), + DiagnosticTestCase( + id="version_is_string", + checks={"version": IsType("string")}, + msg="serverStatus should return version as a string", + ), + DiagnosticTestCase( + id="process_is_string", + checks={"process": IsType("string")}, + msg="serverStatus should return process as a string", + ), + DiagnosticTestCase( + id="pid_is_long", + checks={"pid": IsType("long")}, + msg="serverStatus should return pid as a long", + ), + DiagnosticTestCase( + id="uptime_is_double", + checks={"uptime": IsType("double")}, + msg="serverStatus should return uptime as a double", + ), + DiagnosticTestCase( + id="uptimeMillis_is_long", + checks={"uptimeMillis": IsType("long")}, + msg="serverStatus should return uptimeMillis as a long", + ), + DiagnosticTestCase( + id="uptimeEstimate_is_long", + checks={"uptimeEstimate": IsType("long")}, + msg="serverStatus should return uptimeEstimate as a long", + ), + DiagnosticTestCase( + id="localTime_is_date", + checks={"localTime": IsType("date")}, + msg="serverStatus should return localTime as a date", + ), +] + +# Property [Default Sections]: serverStatus includes standard sections as objects by default. +DEFAULT_SECTION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="asserts_is_object", + checks={"asserts": IsType("object")}, + msg="serverStatus should include asserts as an object", + ), + DiagnosticTestCase( + id="connections_is_object", + checks={"connections": IsType("object")}, + msg="serverStatus should include connections as an object", + ), + DiagnosticTestCase( + id="extra_info_is_object", + checks={"extra_info": IsType("object")}, + msg="serverStatus should include extra_info as an object", + ), + DiagnosticTestCase( + id="globalLock_is_object", + checks={"globalLock": IsType("object")}, + msg="serverStatus should include globalLock as an object", + ), + DiagnosticTestCase( + id="locks_is_object", + checks={"locks": IsType("object")}, + msg="serverStatus should include locks as an object", + ), + DiagnosticTestCase( + id="logicalSessionRecordCache_is_object", + checks={"logicalSessionRecordCache": IsType("object")}, + msg="serverStatus should include logicalSessionRecordCache as an object", + ), + DiagnosticTestCase( + id="mem_is_object", + checks={"mem": IsType("object")}, + msg="serverStatus should include mem as an object", + ), + DiagnosticTestCase( + id="metrics_is_object", + checks={"metrics": IsType("object")}, + msg="serverStatus should include metrics as an object", + ), + DiagnosticTestCase( + id="network_is_object", + checks={"network": IsType("object")}, + msg="serverStatus should include network as an object", + ), + DiagnosticTestCase( + id="opcounters_is_object", + checks={"opcounters": IsType("object")}, + msg="serverStatus should include opcounters as an object", + ), + DiagnosticTestCase( + id="opcountersRepl_is_object", + checks={"opcountersRepl": IsType("object")}, + msg="serverStatus should include opcountersRepl as an object", + ), + DiagnosticTestCase( + id="storageEngine_is_object", + checks={"storageEngine": IsType("object")}, + msg="serverStatus should include storageEngine as an object", + ), + DiagnosticTestCase( + id="transactions_is_object", + checks={"transactions": IsType("object")}, + msg="serverStatus should include transactions as an object", + ), + DiagnosticTestCase( + id="wiredTiger_is_object", + checks={"wiredTiger": IsType("object")}, + msg="serverStatus should include wiredTiger as an object", + ), + DiagnosticTestCase( + id="catalogStats_is_object", + checks={"catalogStats": IsType("object")}, + msg="serverStatus should include catalogStats as an object", + ), +] + +# Property [Sub-Document Fields]: serverStatus sections contain expected nested fields. +SUB_DOC_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + id="asserts_regular_exists", + checks={"asserts.regular": Exists()}, + msg="serverStatus should include asserts.regular", + ), + DiagnosticTestCase( + id="asserts_warning_exists", + checks={"asserts.warning": Exists()}, + msg="serverStatus should include asserts.warning", + ), + DiagnosticTestCase( + id="asserts_msg_exists", + checks={"asserts.msg": Exists()}, + msg="serverStatus should include asserts.msg", + ), + DiagnosticTestCase( + id="asserts_user_exists", + checks={"asserts.user": Exists()}, + msg="serverStatus should include asserts.user", + ), + DiagnosticTestCase( + id="asserts_rollovers_exists", + checks={"asserts.rollovers": Exists()}, + msg="serverStatus should include asserts.rollovers", + ), + DiagnosticTestCase( + id="connections_current_exists", + checks={"connections.current": Exists()}, + msg="serverStatus should include connections.current", + ), + DiagnosticTestCase( + id="connections_available_exists", + checks={"connections.available": Exists()}, + msg="serverStatus should include connections.available", + ), + DiagnosticTestCase( + id="connections_totalCreated_exists", + checks={"connections.totalCreated": Exists()}, + msg="serverStatus should include connections.totalCreated", + ), + DiagnosticTestCase( + id="connections_active_exists", + checks={"connections.active": Exists()}, + msg="serverStatus should include connections.active", + ), + DiagnosticTestCase( + id="opcounters_insert_exists", + checks={"opcounters.insert": Exists()}, + msg="serverStatus should include opcounters.insert", + ), + DiagnosticTestCase( + id="opcounters_query_exists", + checks={"opcounters.query": Exists()}, + msg="serverStatus should include opcounters.query", + ), + DiagnosticTestCase( + id="opcounters_update_exists", + checks={"opcounters.update": Exists()}, + msg="serverStatus should include opcounters.update", + ), + DiagnosticTestCase( + id="opcounters_delete_exists", + checks={"opcounters.delete": Exists()}, + msg="serverStatus should include opcounters.delete", + ), + DiagnosticTestCase( + id="opcounters_getmore_exists", + checks={"opcounters.getmore": Exists()}, + msg="serverStatus should include opcounters.getmore", + ), + DiagnosticTestCase( + id="opcounters_command_exists", + checks={"opcounters.command": Exists()}, + msg="serverStatus should include opcounters.command", + ), + DiagnosticTestCase( + id="network_bytesIn_exists", + checks={"network.bytesIn": Exists()}, + msg="serverStatus should include network.bytesIn", + ), + DiagnosticTestCase( + id="network_bytesOut_exists", + checks={"network.bytesOut": Exists()}, + msg="serverStatus should include network.bytesOut", + ), + DiagnosticTestCase( + id="network_numRequests_exists", + checks={"network.numRequests": Exists()}, + msg="serverStatus should include network.numRequests", + ), + DiagnosticTestCase( + id="globalLock_totalTime_exists", + checks={"globalLock.totalTime": Exists()}, + msg="serverStatus should include globalLock.totalTime", + ), + DiagnosticTestCase( + id="globalLock_currentQueue_is_object", + checks={"globalLock.currentQueue": IsType("object")}, + msg="serverStatus should return globalLock.currentQueue as an object", + ), + DiagnosticTestCase( + id="globalLock_activeClients_is_object", + checks={"globalLock.activeClients": IsType("object")}, + msg="serverStatus should return globalLock.activeClients as an object", + ), + DiagnosticTestCase( + id="storageEngine_name_is_string", + checks={"storageEngine.name": IsType("string")}, + msg="serverStatus should return storageEngine.name as a string", + ), + DiagnosticTestCase( + id="storageEngine_name_is_nonempty", + checks={"storageEngine.name": NonEmptyStr()}, + msg="serverStatus should return a non-empty storageEngine.name", + ), + DiagnosticTestCase( + id="mem_resident_exists", + checks={"mem.resident": Exists()}, + msg="serverStatus should include mem.resident", + ), + DiagnosticTestCase( + id="mem_virtual_exists", + checks={"mem.virtual": Exists()}, + msg="serverStatus should include mem.virtual", + ), + DiagnosticTestCase( + id="catalogStats_collections_exists", + checks={"catalogStats.collections": Exists()}, + msg="serverStatus should include catalogStats.collections", + ), + DiagnosticTestCase( + id="catalogStats_capped_exists", + checks={"catalogStats.capped": Exists()}, + msg="serverStatus should include catalogStats.capped", + ), + DiagnosticTestCase( + id="catalogStats_views_exists", + checks={"catalogStats.views": Exists()}, + msg="serverStatus should include catalogStats.views", + ), + DiagnosticTestCase( + id="catalogStats_timeseries_exists", + checks={"catalogStats.timeseries": Exists()}, + msg="serverStatus should include catalogStats.timeseries", + ), + DiagnosticTestCase( + id="catalogStats_internalCollections_exists", + checks={"catalogStats.internalCollections": Exists()}, + msg="serverStatus should include catalogStats.internalCollections", + ), + DiagnosticTestCase( + id="catalogStats_internalViews_exists", + checks={"catalogStats.internalViews": Exists()}, + msg="serverStatus should include catalogStats.internalViews", + ), +] + +RESPONSE_STRUCTURE_TESTS = TOP_LEVEL_TESTS + DEFAULT_SECTION_TESTS + SUB_DOC_TESTS + + +@pytest.mark.parametrize("test", pytest_params(RESPONSE_STRUCTURE_TESTS)) +def test_serverStatus_response_structure(collection, test): + """Verifies serverStatus response fields exist with expected types.""" + result = execute_admin_command(collection, {"serverStatus": 1}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True)