From ea38f11065ce30387d24bcdb91a0523296594554 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 16:30:39 -0700 Subject: [PATCH 01/12] generate tests Signed-off-by: Alina (Xi) Li --- .../tests/system/replication/__init__.py | 0 .../system/replication/commands/__init__.py | 0 .../replication/commands/hello/__init__.py | 0 .../hello/test_hello_command_value.py | 68 ++++++ .../commands/hello/test_hello_comment.py | 121 ++++++++++ .../commands/hello/test_hello_consistency.py | 189 +++++++++++++++ .../hello/test_hello_error_validation.py | 54 +++++ .../commands/hello/test_hello_replica_set.py | 225 ++++++++++++++++++ .../hello/test_hello_response_structure.py | 213 +++++++++++++++++ .../hello/test_hello_sasl_supported_mechs.py | 192 +++++++++++++++ .../commands/hello/test_smoke_hello.py | 21 ++ .../system/replication/utils/__init__.py | 0 .../utils/replication_test_case.py | 25 ++ 13 files changed, 1108 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/replication/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/utils/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py diff --git a/documentdb_tests/compatibility/tests/system/replication/__init__.py b/documentdb_tests/compatibility/tests/system/replication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/__init__.py b/documentdb_tests/compatibility/tests/system/replication/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/__init__.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py new file mode 100644 index 000000000..7bbd7ba03 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py @@ -0,0 +1,68 @@ +"""Tests for hello command value type acceptance. + +Validates that the hello command accepts all standard BSON types as +the command value (the ``1`` in ``{hello: 1}``). +""" + +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.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Command Value Type Acceptance]: the hello command accepts +# all standard BSON types as the command value. +HELLO_COMMAND_VALUE_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + f"command_value_{tid}", + command=lambda ctx, v=val: {"hello": v}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg=f"hello should accept {tid} as command value", + ) + for tid, val in [ + ("int32", 1), + ("bool_true", True), + ("double", 1.0), + ("string", "test"), + ("null", None), + ("object_empty", {}), + ("array_empty", []), + ("int64", Int64(1)), + ("decimal128", Decimal128("1")), + ("datetime", datetime(2024, 1, 1, tzinfo=timezone.utc)), + ("binary", Binary(b"\x00", 0)), + ("objectid", ObjectId()), + ("regex", Regex(".*")), + ("timestamp", Timestamp(0, 0)), + ("minkey", MinKey()), + ("maxkey", MaxKey()), + ("code", Code("function(){}")), + ] +] + + +@pytest.mark.parametrize("test", pytest_params(HELLO_COMMAND_VALUE_TESTS)) +def test_hello_command_value(collection, test): + """Test hello command value type acceptance.""" + 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, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py new file mode 100644 index 000000000..4725b28de --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py @@ -0,0 +1,121 @@ +"""Tests for hello command comment parameter, combined parameters, +and unrecognized fields. + +Validates that the comment parameter accepts all BSON types, that +combined parameters work together, and that unrecognized fields are +handled correctly. +""" + +from __future__ import annotations + +import pytest +from bson import Decimal128, Int64, ObjectId + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Comment Type Acceptance]: the comment parameter accepts all +# BSON types without error. +HELLO_COMMENT_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + f"comment_{tid}", + command=lambda ctx, v=val: {"hello": 1, "comment": v}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg=f"hello should accept {tid} as comment value", + ) + for tid, val in [ + ("string", "a log comment"), + ("int32", 42), + ("double", 3.14), + ("bool_true", True), + ("null", None), + ("object", {"key": "val"}), + ("array", [1, "two", 3]), + ("int64", Int64(999)), + ("decimal128", Decimal128("1.5")), + ("objectid", ObjectId()), + ] +] + +# Property [Combined Parameters]: hello accepts saslSupportedMechs and +# comment together or individually. +HELLO_COMBINED_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "combined_both_params", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.testuser", + "comment": "both params", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept both saslSupportedMechs and comment", + ), + ReplicationTestCase( + "combined_comment_only", + command=lambda ctx: {"hello": 1, "comment": "only comment"}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed with only comment parameter", + ), + ReplicationTestCase( + "combined_sasl_only", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.testuser", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed with only saslSupportedMechs parameter", + ), +] + +# Property [Unrecognized Field Handling]: hello silently ignores +# unrecognized fields. +HELLO_UNRECOGNIZED_FIELD_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "unrecognized_single_field", + command=lambda ctx: {"hello": 1, "unknownField": "value"}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should ignore unrecognized field", + ), + ReplicationTestCase( + "unrecognized_multiple_fields", + command=lambda ctx: { + "hello": 1, + "unknownField1": 1, + "unknownField2": 2, + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should ignore multiple unrecognized fields", + ), +] + +HELLO_COMMENT_ALL_TESTS: list[ReplicationTestCase] = ( + HELLO_COMMENT_TESTS + HELLO_COMBINED_TESTS + HELLO_UNRECOGNIZED_FIELD_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(HELLO_COMMENT_ALL_TESTS)) +def test_hello_comment(collection, test): + """Test hello comment parameter, combined parameters, and unrecognized fields.""" + 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, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py new file mode 100644 index 000000000..101f6ff24 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -0,0 +1,189 @@ +"""Tests for hello command consistency, idempotency, legacy compatibility, +execution context, standalone behavior, and read-only behavior. + +Validates that hello returns consistent results, works across databases, +is compatible with legacy isMaster, and does not modify state. +""" + +from __future__ import annotations + +from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial +from documentdb_tests.framework.executor import execute_admin_command, execute_command +from documentdb_tests.framework.property_checks import Eq, Gte + + +# Property [Consistency]: static fields are identical across consecutive +# hello calls. +def test_hello_consistency_static_fields(collection): + """Test hello returns identical static fields across consecutive calls.""" + r1 = execute_command(collection, {"hello": 1}) + r2 = execute_command(collection, {"hello": 1}) + assertProperties( + r2, + { + "maxBsonObjectSize": Eq(r1["maxBsonObjectSize"]), + "maxMessageSizeBytes": Eq(r1["maxMessageSizeBytes"]), + "maxWriteBatchSize": Eq(r1["maxWriteBatchSize"]), + "minWireVersion": Eq(r1["minWireVersion"]), + "maxWireVersion": Eq(r1["maxWireVersion"]), + }, + msg="Static fields should be identical across consecutive calls", + raw_res=True, + ) + + +def test_hello_on_admin_database(collection): + """Test hello succeeds on admin database.""" + result = execute_admin_command(collection, {"hello": 1}) + assertSuccessPartial(result, {"ok": 1.0}, msg="hello should succeed on admin database") + + +def test_hello_on_non_admin_database(collection): + """Test hello succeeds on non-admin database.""" + result = execute_command(collection, {"hello": 1}) + assertSuccessPartial(result, {"ok": 1.0}, msg="hello should succeed on non-admin database") + + +def test_hello_admin_and_user_db_static_fields_match(collection): + """Test hello static fields are identical on admin and user databases.""" + r_admin = execute_admin_command(collection, {"hello": 1}) + r_user = execute_command(collection, {"hello": 1}) + assertProperties( + r_user, + { + "maxBsonObjectSize": Eq(r_admin["maxBsonObjectSize"]), + "maxMessageSizeBytes": Eq(r_admin["maxMessageSizeBytes"]), + "maxWriteBatchSize": Eq(r_admin["maxWriteBatchSize"]), + "minWireVersion": Eq(r_admin["minWireVersion"]), + "maxWireVersion": Eq(r_admin["maxWireVersion"]), + }, + msg="Static fields should match between admin and user db", + raw_res=True, + ) + + +def test_hello_localTime_monotonic(collection): + """Test hello localTime is monotonically non-decreasing.""" + r1 = execute_command(collection, {"hello": 1}) + r2 = execute_command(collection, {"hello": 1}) + assertProperties( + r2, + {"localTime": Gte(r1["localTime"])}, + msg="localTime should be non-decreasing across calls", + raw_res=True, + ) + + +def test_hello_connectionId_stable(collection): + """Test hello connectionId remains the same on same connection.""" + r1 = execute_command(collection, {"hello": 1}) + r2 = execute_command(collection, {"hello": 1}) + assertProperties( + r2, + {"connectionId": Eq(r1["connectionId"])}, + msg="connectionId should remain the same across calls on same connection", + raw_res=True, + ) + + +# Property [Legacy isMaster Compatibility]: isMaster and ismaster still +# work and return compatible output. +def test_hello_legacy_isMaster_succeeds(collection): + """Test isMaster command succeeds.""" + result = execute_command(collection, {"isMaster": 1}) + assertSuccessPartial(result, {"ok": 1.0}, msg="isMaster should succeed with ok: 1.0") + + +def test_hello_legacy_ismaster_lowercase_succeeds(collection): + """Test ismaster (lowercase) command succeeds.""" + result = execute_command(collection, {"ismaster": 1}) + assertSuccessPartial( + result, {"ok": 1.0}, msg="ismaster (lowercase) should succeed with ok: 1.0" + ) + + +def test_hello_legacy_isMaster_fields_match(collection): + """Test hello and isMaster return same values for common fields.""" + r_hello = execute_command(collection, {"hello": 1}) + r_ismaster = execute_command(collection, {"isMaster": 1}) + assertProperties( + r_ismaster, + { + "maxBsonObjectSize": Eq(r_hello["maxBsonObjectSize"]), + "maxMessageSizeBytes": Eq(r_hello["maxMessageSizeBytes"]), + "maxWriteBatchSize": Eq(r_hello["maxWriteBatchSize"]), + "minWireVersion": Eq(r_hello["minWireVersion"]), + "maxWireVersion": Eq(r_hello["maxWireVersion"]), + "readOnly": Eq(r_hello["readOnly"]), + "connectionId": Eq(r_hello["connectionId"]), + "logicalSessionTimeoutMinutes": Eq(r_hello["logicalSessionTimeoutMinutes"]), + }, + msg="Common fields should match between hello and isMaster", + raw_res=True, + ) + + +# Property [Execution Context]: hello works on any database, including +# non-existent ones. +def test_hello_on_user_created_database(collection): + """Test hello succeeds on a user-created database.""" + result = execute_command(collection, {"hello": 1}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="hello should succeed on a user-created database", + ) + + +def test_hello_on_nonexistent_database(collection): + """Test hello succeeds on a non-existent database.""" + result = execute_command(collection, {"hello": 1}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="hello should succeed on any database", + ) + + +# Property [Standalone Instance]: on standalone, isWritablePrimary is true, +# readOnly is false, and replica set fields are absent. +def test_hello_standalone_isWritablePrimary(collection): + """Test hello isWritablePrimary is true on standalone/primary.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"isWritablePrimary": Eq(True)}, + msg="isWritablePrimary should be true on standalone/primary", + raw_res=True, + ) + + +def test_hello_standalone_readOnly(collection): + """Test hello readOnly is false on standalone/primary.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"readOnly": Eq(False)}, + msg="readOnly should be false on standalone/primary", + raw_res=True, + ) + + +# Property [Read-Only Behavior]: hello does not modify server state. +def test_hello_read_only_behavior(collection): + """Test hello static fields unchanged after inserting a document.""" + r_before = execute_command(collection, {"hello": 1}) + collection.insert_one({"_id": 1, "data": "test"}) + r_after = execute_command(collection, {"hello": 1}) + assertProperties( + r_after, + { + "maxBsonObjectSize": Eq(r_before["maxBsonObjectSize"]), + "maxMessageSizeBytes": Eq(r_before["maxMessageSizeBytes"]), + "maxWriteBatchSize": Eq(r_before["maxWriteBatchSize"]), + "minWireVersion": Eq(r_before["minWireVersion"]), + "maxWireVersion": Eq(r_before["maxWireVersion"]), + }, + msg="Static fields should be unchanged after insert", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py new file mode 100644 index 000000000..1bda0d6d8 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py @@ -0,0 +1,54 @@ +"""Tests for hello command error code validation. + +Validates that case-mismatched command names are rejected with +COMMAND_NOT_FOUND_ERROR. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +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_command +from documentdb_tests.framework.parametrize import pytest_params + +# Property [Case-Sensitive Command Name]: the hello command is +# case-sensitive. Case mismatches produce COMMAND_NOT_FOUND_ERROR. +HELLO_CASE_ERROR_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "case_capital_H", + command=lambda ctx: {"Hello": 1}, + use_admin=False, + error_code=COMMAND_NOT_FOUND_ERROR, + msg="hello should reject 'Hello' (capital H)", + ), + ReplicationTestCase( + "case_all_caps", + command=lambda ctx: {"HELLO": 1}, + use_admin=False, + error_code=COMMAND_NOT_FOUND_ERROR, + msg="hello should reject 'HELLO' (all caps)", + ), + ReplicationTestCase( + "case_mixed", + command=lambda ctx: {"heLLo": 1}, + use_admin=False, + error_code=COMMAND_NOT_FOUND_ERROR, + msg="hello should reject 'heLLo' (mixed case)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(HELLO_CASE_ERROR_TESTS)) +def test_hello_error_validation(collection, test): + """Test hello command case-sensitive name validation.""" + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py new file mode 100644 index 000000000..3e69016b7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -0,0 +1,225 @@ +"""Tests for hello command replica set response fields. + +Validates required replica set fields, conditional fields, and +behavioral checks when connected to a replica set member. +All tests in this file require a replica set connection. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.property_checks import ( + ContainsElement, + Eq, + Gte, + IsType, + NonEmptyStr, +) + +pytestmark = [pytest.mark.replica_set] + + +# Property [Required Replica Set Fields]: hello response includes +# hosts, setName, setVersion, secondary, primary, me, and lastWrite +# when connected to a replica set member. +def test_hello_rs_hosts(collection): + """Test hello response contains hosts as array.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"hosts": IsType("array")}, + msg="hosts should exist and be array on replica set member", + raw_res=True, + ) + + +def test_hello_rs_setName(collection): + """Test hello response contains setName as non-empty string.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"setName": NonEmptyStr()}, + msg="setName should exist and be non-empty string", + raw_res=True, + ) + + +def test_hello_rs_setVersion(collection): + """Test hello response contains setVersion as positive int.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"setVersion": Gte(1)}, + msg="setVersion should be a positive integer", + raw_res=True, + ) + + +def test_hello_rs_secondary(collection): + """Test hello response contains secondary as boolean.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"secondary": IsType("bool")}, + msg="secondary should exist and be boolean", + raw_res=True, + ) + + +def test_hello_rs_primary(collection): + """Test hello response contains primary as non-empty string.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"primary": NonEmptyStr()}, + msg="primary should exist and be non-empty string", + raw_res=True, + ) + + +def test_hello_rs_me(collection): + """Test hello response contains me as non-empty string.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"me": NonEmptyStr()}, + msg="me should exist and be non-empty string", + raw_res=True, + ) + + +def test_hello_rs_lastWrite(collection): + """Test hello response contains lastWrite as object.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": IsType("object")}, + msg="lastWrite should exist and be object", + raw_res=True, + ) + + +def test_hello_rs_lastWrite_opTime(collection): + """Test hello response contains lastWrite.opTime as object.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"opTime": IsType("object")}}, + msg="lastWrite.opTime should exist and be object", + raw_res=True, + ) + + +def test_hello_rs_lastWrite_lastWriteDate(collection): + """Test hello response contains lastWrite.lastWriteDate as date.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"lastWriteDate": IsType("date")}}, + msg="lastWrite.lastWriteDate should exist and be date", + raw_res=True, + ) + + +def test_hello_rs_lastWrite_majorityOpTime(collection): + """Test hello response contains lastWrite.majorityOpTime as object.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"majorityOpTime": IsType("object")}}, + msg="lastWrite.majorityOpTime should exist and be object", + raw_res=True, + ) + + +def test_hello_rs_lastWrite_majorityWriteDate(collection): + """Test hello response contains lastWrite.majorityWriteDate as date.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"majorityWriteDate": IsType("date")}}, + msg="lastWrite.majorityWriteDate should exist and be date", + raw_res=True, + ) + + +# Property [Replica Set Behavioral Checks]: verify behavioral +# invariants on primary nodes. +def test_hello_rs_primary_isWritablePrimary_and_not_secondary(collection): + """Test on primary, isWritablePrimary is true and secondary is false.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"isWritablePrimary": Eq(True), "secondary": Eq(False)}, + msg="Primary should have isWritablePrimary=true and secondary=false", + raw_res=True, + ) + + +def test_hello_rs_primary_equals_me(collection): + """Test on primary, primary field equals me field.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"primary": Eq(result.get("me", "MISSING"))}, + msg="On primary, 'primary' should equal 'me'", + raw_res=True, + ) + + +def test_hello_rs_hosts_contains_primary(collection): + """Test hello hosts array contains the primary.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"hosts": ContainsElement(result.get("primary", "MISSING"))}, + msg="hosts array should contain the primary", + raw_res=True, + ) + + +def test_hello_rs_me_in_hosts(collection): + """Test hello me appears in hosts array.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"hosts": ContainsElement(result.get("me", "MISSING"))}, + msg="me should appear in hosts array", + raw_res=True, + ) + + +def test_hello_rs_lastWriteDate_is_date(collection): + """Test hello lastWrite.lastWriteDate is a date.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"lastWriteDate": IsType("date")}}, + msg="lastWrite.lastWriteDate should be a date", + raw_res=True, + ) + + +def test_hello_rs_majorityWriteDate_is_date(collection): + """Test hello lastWrite.majorityWriteDate is a date.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"lastWrite": {"majorityWriteDate": IsType("date")}}, + msg="lastWrite.majorityWriteDate should be a date", + raw_res=True, + ) + + +def test_hello_rs_electionId_on_primary(collection): + """Test hello electionId exists and is ObjectId on primary.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"electionId": IsType("objectId")}, + msg="electionId should exist and be ObjectId on primary", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py new file mode 100644 index 000000000..11817ad48 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -0,0 +1,213 @@ +"""Tests for hello command response structure. + +Validates common response fields (all instances), topologyVersion +fields, and wire version fields using property checks. +""" + +from __future__ import annotations + +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.property_checks import Eq, Gte, IsType + + +def test_hello_response_isWritablePrimary(collection): + """Test hello response contains isWritablePrimary as boolean.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"isWritablePrimary": IsType("bool")}, + msg="isWritablePrimary should exist and be boolean", + raw_res=True, + ) + + +def test_hello_response_maxBsonObjectSize(collection): + """Test hello response contains maxBsonObjectSize equal to 16777216.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxBsonObjectSize": Eq(16777216)}, + msg="maxBsonObjectSize should equal 16777216", + raw_res=True, + ) + + +def test_hello_response_maxMessageSizeBytes(collection): + """Test hello response contains maxMessageSizeBytes equal to 48000000.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxMessageSizeBytes": Eq(48000000)}, + msg="maxMessageSizeBytes should equal 48000000", + raw_res=True, + ) + + +def test_hello_response_maxWriteBatchSize(collection): + """Test hello response contains maxWriteBatchSize equal to 100000.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxWriteBatchSize": Eq(100000)}, + msg="maxWriteBatchSize should equal 100000", + raw_res=True, + ) + + +def test_hello_response_localTime(collection): + """Test hello response contains localTime as date.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"localTime": IsType("date")}, + msg="localTime should exist and be date", + raw_res=True, + ) + + +def test_hello_response_logicalSessionTimeoutMinutes(collection): + """Test hello response contains logicalSessionTimeoutMinutes as positive int.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"logicalSessionTimeoutMinutes": Gte(1)}, + msg="logicalSessionTimeoutMinutes should be a positive integer", + raw_res=True, + ) + + +def test_hello_response_connectionId(collection): + """Test hello response contains connectionId as positive int.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"connectionId": Gte(1)}, + msg="connectionId should be a positive integer", + raw_res=True, + ) + + +def test_hello_response_minWireVersion(collection): + """Test hello response contains minWireVersion as non-negative int.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"minWireVersion": Gte(0)}, + msg="minWireVersion should be a non-negative integer", + raw_res=True, + ) + + +def test_hello_response_maxWireVersion(collection): + """Test hello response contains maxWireVersion as int.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxWireVersion": IsType("int")}, + msg="maxWireVersion should exist and be int", + raw_res=True, + ) + + +def test_hello_response_wire_version_ordering(collection): + """Test hello response maxWireVersion >= minWireVersion.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxWireVersion": Gte(result.get("minWireVersion", 0))}, + msg="maxWireVersion should be >= minWireVersion", + raw_res=True, + ) + + +def test_hello_response_readOnly(collection): + """Test hello response contains readOnly as boolean.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"readOnly": IsType("bool")}, + msg="readOnly should exist and be boolean", + raw_res=True, + ) + + +def test_hello_response_ok(collection): + """Test hello response contains ok equal to 1.0.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"ok": Eq(1.0)}, + msg="ok should equal 1.0", + raw_res=True, + ) + + +# Property [topologyVersion]: hello response contains topologyVersion +# with processId (ObjectId) and counter (non-negative int64). +def test_hello_response_topologyVersion_exists(collection): + """Test hello response contains topologyVersion as object.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"topologyVersion": IsType("object")}, + msg="topologyVersion should exist and be object", + raw_res=True, + ) + + +def test_hello_response_topologyVersion_processId(collection): + """Test hello topologyVersion.processId exists and is ObjectId.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"topologyVersion": {"processId": IsType("objectId")}}, + msg="topologyVersion.processId should exist and be ObjectId", + raw_res=True, + ) + + +def test_hello_response_topologyVersion_counter(collection): + """Test hello topologyVersion.counter exists and is non-negative.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"topologyVersion": {"counter": Gte(0)}}, + msg="topologyVersion.counter should be non-negative", + raw_res=True, + ) + + +def test_hello_response_topologyVersion_processId_stable(collection): + """Test hello topologyVersion.processId is stable across calls.""" + r1 = execute_command(collection, {"hello": 1}) + r2 = execute_command(collection, {"hello": 1}) + assertProperties( + r2, + {"topologyVersion": {"processId": Eq(r1["topologyVersion"]["processId"])}}, + msg="topologyVersion.processId should remain the same across consecutive calls", + raw_res=True, + ) + + +# Property [Wire Version]: wire version fields indicate protocol compatibility. +def test_hello_wire_version_min_non_negative(collection): + """Test hello minWireVersion is non-negative.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"minWireVersion": Gte(0)}, + msg="minWireVersion should be >= 0", + raw_res=True, + ) + + +def test_hello_wire_version_max_reasonable(collection): + """Test hello maxWireVersion is a reasonable value for modern MongoDB.""" + result = execute_command(collection, {"hello": 1}) + assertProperties( + result, + {"maxWireVersion": Gte(21)}, + msg="maxWireVersion should be >= 21 for MongoDB 7.0+", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py new file mode 100644 index 000000000..e8ebdafed --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py @@ -0,0 +1,192 @@ +"""Tests for hello command saslSupportedMechs parameter. + +Validates valid usage, invalid format, type rejection, and edge cases +for the saslSupportedMechs parameter. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import BAD_VALUE_ERROR, TYPE_MISMATCH_ERROR +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [saslSupportedMechs Valid Usage]: hello accepts valid +# "db.user" format strings for saslSupportedMechs. +SASL_VALID_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_nonexistent_user", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.nonExistentUser", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed for non-existent user in saslSupportedMechs", + ), + ReplicationTestCase( + "sasl_other_db_prefix", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "otherdb.someuser", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept non-admin database prefix in saslSupportedMechs", + ), +] + +# Property [saslSupportedMechs Invalid Format]: hello rejects strings +# that do not follow the "db.user" format. +SASL_INVALID_FORMAT_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_no_dot_separator", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "noDotSeparator", + }, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject saslSupportedMechs without db.user format", + ), + ReplicationTestCase( + "sasl_empty_db_component", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": ".noDatabase", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept saslSupportedMechs with empty database component", + ), + ReplicationTestCase( + "sasl_empty_username", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept saslSupportedMechs with empty username", + ), + ReplicationTestCase( + "sasl_empty_string", + command=lambda ctx: {"hello": 1, "saslSupportedMechs": ""}, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject empty string saslSupportedMechs", + ), + ReplicationTestCase( + "sasl_dot_only", + command=lambda ctx: {"hello": 1, "saslSupportedMechs": "."}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept dot-only saslSupportedMechs", + ), +] + +# Property [saslSupportedMechs Type Rejection]: hello rejects non-string +# types for saslSupportedMechs. +SASL_TYPE_REJECTION_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + f"sasl_type_{tid}", + command=lambda ctx, v=val: {"hello": 1, "saslSupportedMechs": v}, + use_admin=False, + error_code=err, + msg=f"hello should reject {tid} as saslSupportedMechs", + ) + for tid, val, err in [ + ("int", 123, TYPE_MISMATCH_ERROR), + ("bool", True, TYPE_MISMATCH_ERROR), + ("array", ["admin.user"], TYPE_MISMATCH_ERROR), + ("object", {"db": "admin"}, BAD_VALUE_ERROR), + ] +] + +# Property [saslSupportedMechs Null]: hello rejects null +# saslSupportedMechs with type mismatch error. +SASL_NULL_TEST: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_type_null", + command=lambda ctx: {"hello": 1, "saslSupportedMechs": None}, + use_admin=False, + error_code=TYPE_MISMATCH_ERROR, + msg="hello should reject null saslSupportedMechs", + ), +] + +# Property [saslSupportedMechs Edge Cases]: hello handles edge cases +# in the "db.user" format string. +SASL_EDGE_CASE_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_dots_in_username", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.user.with.dots", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept saslSupportedMechs with dots in username", + ), + ReplicationTestCase( + "sasl_special_chars_in_db", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "a]dmin.user", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept saslSupportedMechs with special chars in db name", + ), + ReplicationTestCase( + "sasl_long_username", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "admin.a_very_long_username_that_exceeds_typical_lengths", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept saslSupportedMechs with long username", + ), + ReplicationTestCase( + "sasl_external_db", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "$external.user", + }, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should accept $external database prefix in saslSupportedMechs", + ), +] + +HELLO_SASL_ALL_TESTS: list[ReplicationTestCase] = ( + SASL_VALID_TESTS + + SASL_INVALID_FORMAT_TESTS + + SASL_TYPE_REJECTION_TESTS + + SASL_NULL_TEST + + SASL_EDGE_CASE_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(HELLO_SASL_ALL_TESTS)) +def test_hello_sasl_supported_mechs(collection, test): + """Test hello saslSupportedMechs parameter handling.""" + 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, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py new file mode 100644 index 000000000..f3815623b --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py @@ -0,0 +1,21 @@ +""" +Smoke test for hello command. + +Tests basic hello command functionality by verifying the command returns +a successful response with ok: 1.0. +""" + +import pytest + +from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.framework.executor import execute_command + +pytestmark = [pytest.mark.smoke] + + +def test_smoke_hello(collection): + """Test basic hello command behavior.""" + result = execute_command(collection, {"hello": 1}) + + expected = {"ok": 1.0} + assertSuccessPartial(result, expected, msg="Should support hello command") diff --git a/documentdb_tests/compatibility/tests/system/replication/utils/__init__.py b/documentdb_tests/compatibility/tests/system/replication/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py b/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py new file mode 100644 index 000000000..5b93302e2 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py @@ -0,0 +1,25 @@ +"""Shared test case for replication command tests.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandTestCase, +) + + +@dataclass(frozen=True) +class ReplicationTestCase(CommandTestCase): + """Test case for replication command tests. + + Extends CommandTestCase with a ``use_admin`` flag that controls + whether the command is executed against the admin database. + + Attributes: + use_admin: If True (the default), execute against the admin + database via ``execute_admin_command``. If False, execute + against the test database via ``execute_command``. + """ + + use_admin: bool = True From 8f7beaaac3cdd304e829a54cd0d082a9bae697f9 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 16:38:42 -0700 Subject: [PATCH 02/12] convert to use ReplicationTestCase remains tests that cannot be converted Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_consistency.py | 202 +++++++----- .../commands/hello/test_hello_replica_set.py | 250 +++++++------- .../hello/test_hello_response_structure.py | 307 ++++++++---------- 3 files changed, 367 insertions(+), 392 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py index 101f6ff24..1d436b6f5 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -7,20 +7,120 @@ from __future__ import annotations -from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult 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, Gte +# Property [Execution Context]: hello works on any database. +HELLO_CONTEXT_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "admin_database", + command={"hello": 1}, + use_admin=True, + expected={"ok": Eq(1.0)}, + msg="hello should succeed on admin database", + ), + ReplicationTestCase( + "non_admin_database", + command={"hello": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed on non-admin database", + ), + ReplicationTestCase( + "user_created_database", + command={"hello": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed on a user-created database", + ), + ReplicationTestCase( + "nonexistent_database", + command={"hello": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="hello should succeed on any database", + ), +] + +# Property [Standalone Instance]: on standalone, isWritablePrimary is true, +# readOnly is false. +HELLO_STANDALONE_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "standalone_isWritablePrimary", + command={"hello": 1}, + use_admin=False, + expected={"isWritablePrimary": Eq(True)}, + msg="isWritablePrimary should be true on standalone/primary", + ), + ReplicationTestCase( + "standalone_readOnly", + command={"hello": 1}, + use_admin=False, + expected={"readOnly": Eq(False)}, + msg="readOnly should be false on standalone/primary", + ), +] + +# Property [Legacy isMaster Compatibility]: isMaster and ismaster still +# work and return compatible output. +HELLO_LEGACY_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "legacy_isMaster_succeeds", + command={"isMaster": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="isMaster should succeed with ok: 1.0", + ), + ReplicationTestCase( + "legacy_ismaster_lowercase_succeeds", + command={"ismaster": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, + msg="ismaster (lowercase) should succeed with ok: 1.0", + ), +] + +HELLO_CONSISTENCY_ALL_TESTS: list[ReplicationTestCase] = ( + HELLO_CONTEXT_TESTS + HELLO_STANDALONE_TESTS + HELLO_LEGACY_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(HELLO_CONSISTENCY_ALL_TESTS)) +def test_hello_consistency(collection, test): + """Test hello command consistency, context, and legacy compatibility.""" + ctx = CommandContext.from_collection(collection) + if test.use_admin: + result = execute_admin_command(collection, test.build_command(ctx)) + else: + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + expected=test.build_expected(ctx), + msg=test.msg, + raw_res=True, + ) + # Property [Consistency]: static fields are identical across consecutive -# hello calls. +# hello calls. These tests compare two command results and cannot use +# the parametrized test case pattern. def test_hello_consistency_static_fields(collection): """Test hello returns identical static fields across consecutive calls.""" r1 = execute_command(collection, {"hello": 1}) r2 = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r2, - { + expected={ "maxBsonObjectSize": Eq(r1["maxBsonObjectSize"]), "maxMessageSizeBytes": Eq(r1["maxMessageSizeBytes"]), "maxWriteBatchSize": Eq(r1["maxWriteBatchSize"]), @@ -32,25 +132,13 @@ def test_hello_consistency_static_fields(collection): ) -def test_hello_on_admin_database(collection): - """Test hello succeeds on admin database.""" - result = execute_admin_command(collection, {"hello": 1}) - assertSuccessPartial(result, {"ok": 1.0}, msg="hello should succeed on admin database") - - -def test_hello_on_non_admin_database(collection): - """Test hello succeeds on non-admin database.""" - result = execute_command(collection, {"hello": 1}) - assertSuccessPartial(result, {"ok": 1.0}, msg="hello should succeed on non-admin database") - - def test_hello_admin_and_user_db_static_fields_match(collection): """Test hello static fields are identical on admin and user databases.""" r_admin = execute_admin_command(collection, {"hello": 1}) r_user = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r_user, - { + expected={ "maxBsonObjectSize": Eq(r_admin["maxBsonObjectSize"]), "maxMessageSizeBytes": Eq(r_admin["maxMessageSizeBytes"]), "maxWriteBatchSize": Eq(r_admin["maxWriteBatchSize"]), @@ -66,9 +154,9 @@ def test_hello_localTime_monotonic(collection): """Test hello localTime is monotonically non-decreasing.""" r1 = execute_command(collection, {"hello": 1}) r2 = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r2, - {"localTime": Gte(r1["localTime"])}, + expected={"localTime": Gte(r1["localTime"])}, msg="localTime should be non-decreasing across calls", raw_res=True, ) @@ -78,37 +166,21 @@ def test_hello_connectionId_stable(collection): """Test hello connectionId remains the same on same connection.""" r1 = execute_command(collection, {"hello": 1}) r2 = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r2, - {"connectionId": Eq(r1["connectionId"])}, + expected={"connectionId": Eq(r1["connectionId"])}, msg="connectionId should remain the same across calls on same connection", raw_res=True, ) -# Property [Legacy isMaster Compatibility]: isMaster and ismaster still -# work and return compatible output. -def test_hello_legacy_isMaster_succeeds(collection): - """Test isMaster command succeeds.""" - result = execute_command(collection, {"isMaster": 1}) - assertSuccessPartial(result, {"ok": 1.0}, msg="isMaster should succeed with ok: 1.0") - - -def test_hello_legacy_ismaster_lowercase_succeeds(collection): - """Test ismaster (lowercase) command succeeds.""" - result = execute_command(collection, {"ismaster": 1}) - assertSuccessPartial( - result, {"ok": 1.0}, msg="ismaster (lowercase) should succeed with ok: 1.0" - ) - - def test_hello_legacy_isMaster_fields_match(collection): """Test hello and isMaster return same values for common fields.""" r_hello = execute_command(collection, {"hello": 1}) r_ismaster = execute_command(collection, {"isMaster": 1}) - assertProperties( + assertResult( r_ismaster, - { + expected={ "maxBsonObjectSize": Eq(r_hello["maxBsonObjectSize"]), "maxMessageSizeBytes": Eq(r_hello["maxMessageSizeBytes"]), "maxWriteBatchSize": Eq(r_hello["maxWriteBatchSize"]), @@ -123,61 +195,15 @@ def test_hello_legacy_isMaster_fields_match(collection): ) -# Property [Execution Context]: hello works on any database, including -# non-existent ones. -def test_hello_on_user_created_database(collection): - """Test hello succeeds on a user-created database.""" - result = execute_command(collection, {"hello": 1}) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="hello should succeed on a user-created database", - ) - - -def test_hello_on_nonexistent_database(collection): - """Test hello succeeds on a non-existent database.""" - result = execute_command(collection, {"hello": 1}) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="hello should succeed on any database", - ) - - -# Property [Standalone Instance]: on standalone, isWritablePrimary is true, -# readOnly is false, and replica set fields are absent. -def test_hello_standalone_isWritablePrimary(collection): - """Test hello isWritablePrimary is true on standalone/primary.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"isWritablePrimary": Eq(True)}, - msg="isWritablePrimary should be true on standalone/primary", - raw_res=True, - ) - - -def test_hello_standalone_readOnly(collection): - """Test hello readOnly is false on standalone/primary.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"readOnly": Eq(False)}, - msg="readOnly should be false on standalone/primary", - raw_res=True, - ) - - # Property [Read-Only Behavior]: hello does not modify server state. def test_hello_read_only_behavior(collection): """Test hello static fields unchanged after inserting a document.""" r_before = execute_command(collection, {"hello": 1}) collection.insert_one({"_id": 1, "data": "test"}) r_after = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r_after, - { + expected={ "maxBsonObjectSize": Eq(r_before["maxBsonObjectSize"]), "maxMessageSizeBytes": Eq(r_before["maxMessageSizeBytes"]), "maxWriteBatchSize": Eq(r_before["maxWriteBatchSize"]), diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index 3e69016b7..51be9680e 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -9,8 +9,15 @@ import pytest -from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import ( ContainsElement, Eq, @@ -25,146 +32,124 @@ # Property [Required Replica Set Fields]: hello response includes # hosts, setName, setVersion, secondary, primary, me, and lastWrite # when connected to a replica set member. -def test_hello_rs_hosts(collection): - """Test hello response contains hosts as array.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"hosts": IsType("array")}, +RS_REQUIRED_FIELD_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "rs_hosts", + command={"hello": 1}, + use_admin=False, + expected={"hosts": IsType("array")}, msg="hosts should exist and be array on replica set member", - raw_res=True, - ) - - -def test_hello_rs_setName(collection): - """Test hello response contains setName as non-empty string.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"setName": NonEmptyStr()}, + ), + ReplicationTestCase( + "rs_setName", + command={"hello": 1}, + use_admin=False, + expected={"setName": NonEmptyStr()}, msg="setName should exist and be non-empty string", - raw_res=True, - ) - - -def test_hello_rs_setVersion(collection): - """Test hello response contains setVersion as positive int.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"setVersion": Gte(1)}, + ), + ReplicationTestCase( + "rs_setVersion", + command={"hello": 1}, + use_admin=False, + expected={"setVersion": Gte(1)}, msg="setVersion should be a positive integer", - raw_res=True, - ) - - -def test_hello_rs_secondary(collection): - """Test hello response contains secondary as boolean.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"secondary": IsType("bool")}, + ), + ReplicationTestCase( + "rs_secondary", + command={"hello": 1}, + use_admin=False, + expected={"secondary": IsType("bool")}, msg="secondary should exist and be boolean", - raw_res=True, - ) - - -def test_hello_rs_primary(collection): - """Test hello response contains primary as non-empty string.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"primary": NonEmptyStr()}, + ), + ReplicationTestCase( + "rs_primary", + command={"hello": 1}, + use_admin=False, + expected={"primary": NonEmptyStr()}, msg="primary should exist and be non-empty string", - raw_res=True, - ) - - -def test_hello_rs_me(collection): - """Test hello response contains me as non-empty string.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"me": NonEmptyStr()}, + ), + ReplicationTestCase( + "rs_me", + command={"hello": 1}, + use_admin=False, + expected={"me": NonEmptyStr()}, msg="me should exist and be non-empty string", - raw_res=True, - ) - - -def test_hello_rs_lastWrite(collection): - """Test hello response contains lastWrite as object.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"lastWrite": IsType("object")}, + ), + ReplicationTestCase( + "rs_lastWrite", + command={"hello": 1}, + use_admin=False, + expected={"lastWrite": IsType("object")}, msg="lastWrite should exist and be object", - raw_res=True, - ) - - -def test_hello_rs_lastWrite_opTime(collection): - """Test hello response contains lastWrite.opTime as object.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"lastWrite": {"opTime": IsType("object")}}, + ), + ReplicationTestCase( + "rs_lastWrite_opTime", + command={"hello": 1}, + use_admin=False, + expected={"lastWrite": {"opTime": IsType("object")}}, msg="lastWrite.opTime should exist and be object", - raw_res=True, - ) - - -def test_hello_rs_lastWrite_lastWriteDate(collection): - """Test hello response contains lastWrite.lastWriteDate as date.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"lastWrite": {"lastWriteDate": IsType("date")}}, + ), + ReplicationTestCase( + "rs_lastWrite_lastWriteDate", + command={"hello": 1}, + use_admin=False, + expected={"lastWrite": {"lastWriteDate": IsType("date")}}, msg="lastWrite.lastWriteDate should exist and be date", - raw_res=True, - ) - - -def test_hello_rs_lastWrite_majorityOpTime(collection): - """Test hello response contains lastWrite.majorityOpTime as object.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"lastWrite": {"majorityOpTime": IsType("object")}}, + ), + ReplicationTestCase( + "rs_lastWrite_majorityOpTime", + command={"hello": 1}, + use_admin=False, + expected={"lastWrite": {"majorityOpTime": IsType("object")}}, msg="lastWrite.majorityOpTime should exist and be object", - raw_res=True, - ) - - -def test_hello_rs_lastWrite_majorityWriteDate(collection): - """Test hello response contains lastWrite.majorityWriteDate as date.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"lastWrite": {"majorityWriteDate": IsType("date")}}, + ), + ReplicationTestCase( + "rs_lastWrite_majorityWriteDate", + command={"hello": 1}, + use_admin=False, + expected={"lastWrite": {"majorityWriteDate": IsType("date")}}, msg="lastWrite.majorityWriteDate should exist and be date", - raw_res=True, - ) + ), + ReplicationTestCase( + "rs_primary_isWritablePrimary_and_not_secondary", + command={"hello": 1}, + use_admin=False, + expected={"isWritablePrimary": Eq(True), "secondary": Eq(False)}, + msg="Primary should have isWritablePrimary=true and secondary=false", + ), + ReplicationTestCase( + "rs_electionId_on_primary", + command={"hello": 1}, + use_admin=False, + expected={"electionId": IsType("objectId")}, + msg="electionId should exist and be ObjectId on primary", + ), +] +HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = RS_REQUIRED_FIELD_TESTS -# Property [Replica Set Behavioral Checks]: verify behavioral -# invariants on primary nodes. -def test_hello_rs_primary_isWritablePrimary_and_not_secondary(collection): - """Test on primary, isWritablePrimary is true and secondary is false.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( + +@pytest.mark.parametrize("test", pytest_params(HELLO_RS_ALL_TESTS)) +def test_hello_replica_set(collection, test): + """Test hello replica set response fields.""" + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( result, - {"isWritablePrimary": Eq(True), "secondary": Eq(False)}, - msg="Primary should have isWritablePrimary=true and secondary=false", + expected=test.build_expected(ctx), + msg=test.msg, raw_res=True, ) +# Property [Replica Set Behavioral Checks]: these tests reference +# dynamic values from the result itself and require standalone functions. def test_hello_rs_primary_equals_me(collection): """Test on primary, primary field equals me field.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"primary": Eq(result.get("me", "MISSING"))}, + expected={"primary": Eq(result.get("me", "MISSING"))}, msg="On primary, 'primary' should equal 'me'", raw_res=True, ) @@ -173,9 +158,9 @@ def test_hello_rs_primary_equals_me(collection): def test_hello_rs_hosts_contains_primary(collection): """Test hello hosts array contains the primary.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"hosts": ContainsElement(result.get("primary", "MISSING"))}, + expected={"hosts": ContainsElement(result.get("primary", "MISSING"))}, msg="hosts array should contain the primary", raw_res=True, ) @@ -184,9 +169,9 @@ def test_hello_rs_hosts_contains_primary(collection): def test_hello_rs_me_in_hosts(collection): """Test hello me appears in hosts array.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"hosts": ContainsElement(result.get("me", "MISSING"))}, + expected={"hosts": ContainsElement(result.get("me", "MISSING"))}, msg="me should appear in hosts array", raw_res=True, ) @@ -195,9 +180,9 @@ def test_hello_rs_me_in_hosts(collection): def test_hello_rs_lastWriteDate_is_date(collection): """Test hello lastWrite.lastWriteDate is a date.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"lastWrite": {"lastWriteDate": IsType("date")}}, + expected={"lastWrite": {"lastWriteDate": IsType("date")}}, msg="lastWrite.lastWriteDate should be a date", raw_res=True, ) @@ -206,20 +191,9 @@ def test_hello_rs_lastWriteDate_is_date(collection): def test_hello_rs_majorityWriteDate_is_date(collection): """Test hello lastWrite.majorityWriteDate is a date.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"lastWrite": {"majorityWriteDate": IsType("date")}}, + expected={"lastWrite": {"majorityWriteDate": IsType("date")}}, msg="lastWrite.majorityWriteDate should be a date", raw_res=True, ) - - -def test_hello_rs_electionId_on_primary(collection): - """Test hello electionId exists and is ObjectId on primary.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"electionId": IsType("objectId")}, - msg="electionId should exist and be ObjectId on primary", - raw_res=True, - ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 11817ad48..01151fc02 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -6,174 +6,172 @@ from __future__ import annotations -from documentdb_tests.framework.assertions import assertProperties +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Gte, IsType - -def test_hello_response_isWritablePrimary(collection): - """Test hello response contains isWritablePrimary as boolean.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"isWritablePrimary": IsType("bool")}, +# Property [Common Response Fields]: hello response includes all required +# fields with correct types and values. +RESPONSE_COMMON_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "response_isWritablePrimary", + command={"hello": 1}, + use_admin=False, + expected={"isWritablePrimary": IsType("bool")}, msg="isWritablePrimary should exist and be boolean", - raw_res=True, - ) - - -def test_hello_response_maxBsonObjectSize(collection): - """Test hello response contains maxBsonObjectSize equal to 16777216.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxBsonObjectSize": Eq(16777216)}, + ), + ReplicationTestCase( + "response_maxBsonObjectSize", + command={"hello": 1}, + use_admin=False, + expected={"maxBsonObjectSize": Eq(16777216)}, msg="maxBsonObjectSize should equal 16777216", - raw_res=True, - ) - - -def test_hello_response_maxMessageSizeBytes(collection): - """Test hello response contains maxMessageSizeBytes equal to 48000000.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxMessageSizeBytes": Eq(48000000)}, + ), + ReplicationTestCase( + "response_maxMessageSizeBytes", + command={"hello": 1}, + use_admin=False, + expected={"maxMessageSizeBytes": Eq(48000000)}, msg="maxMessageSizeBytes should equal 48000000", - raw_res=True, - ) - - -def test_hello_response_maxWriteBatchSize(collection): - """Test hello response contains maxWriteBatchSize equal to 100000.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxWriteBatchSize": Eq(100000)}, + ), + ReplicationTestCase( + "response_maxWriteBatchSize", + command={"hello": 1}, + use_admin=False, + expected={"maxWriteBatchSize": Eq(100000)}, msg="maxWriteBatchSize should equal 100000", - raw_res=True, - ) - - -def test_hello_response_localTime(collection): - """Test hello response contains localTime as date.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"localTime": IsType("date")}, + ), + ReplicationTestCase( + "response_localTime", + command={"hello": 1}, + use_admin=False, + expected={"localTime": IsType("date")}, msg="localTime should exist and be date", - raw_res=True, - ) - - -def test_hello_response_logicalSessionTimeoutMinutes(collection): - """Test hello response contains logicalSessionTimeoutMinutes as positive int.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"logicalSessionTimeoutMinutes": Gte(1)}, + ), + ReplicationTestCase( + "response_logicalSessionTimeoutMinutes", + command={"hello": 1}, + use_admin=False, + expected={"logicalSessionTimeoutMinutes": Gte(1)}, msg="logicalSessionTimeoutMinutes should be a positive integer", - raw_res=True, - ) - - -def test_hello_response_connectionId(collection): - """Test hello response contains connectionId as positive int.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"connectionId": Gte(1)}, + ), + ReplicationTestCase( + "response_connectionId", + command={"hello": 1}, + use_admin=False, + expected={"connectionId": Gte(1)}, msg="connectionId should be a positive integer", - raw_res=True, - ) - - -def test_hello_response_minWireVersion(collection): - """Test hello response contains minWireVersion as non-negative int.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"minWireVersion": Gte(0)}, + ), + ReplicationTestCase( + "response_minWireVersion", + command={"hello": 1}, + use_admin=False, + expected={"minWireVersion": Gte(0)}, msg="minWireVersion should be a non-negative integer", - raw_res=True, - ) - - -def test_hello_response_maxWireVersion(collection): - """Test hello response contains maxWireVersion as int.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxWireVersion": IsType("int")}, + ), + ReplicationTestCase( + "response_maxWireVersion", + command={"hello": 1}, + use_admin=False, + expected={"maxWireVersion": IsType("int")}, msg="maxWireVersion should exist and be int", - raw_res=True, - ) - - -def test_hello_response_wire_version_ordering(collection): - """Test hello response maxWireVersion >= minWireVersion.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxWireVersion": Gte(result.get("minWireVersion", 0))}, - msg="maxWireVersion should be >= minWireVersion", - raw_res=True, - ) - - -def test_hello_response_readOnly(collection): - """Test hello response contains readOnly as boolean.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"readOnly": IsType("bool")}, + ), + ReplicationTestCase( + "response_readOnly", + command={"hello": 1}, + use_admin=False, + expected={"readOnly": IsType("bool")}, msg="readOnly should exist and be boolean", - raw_res=True, - ) - - -def test_hello_response_ok(collection): - """Test hello response contains ok equal to 1.0.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"ok": Eq(1.0)}, + ), + ReplicationTestCase( + "response_ok", + command={"hello": 1}, + use_admin=False, + expected={"ok": Eq(1.0)}, msg="ok should equal 1.0", - raw_res=True, - ) - + ), +] # Property [topologyVersion]: hello response contains topologyVersion # with processId (ObjectId) and counter (non-negative int64). -def test_hello_response_topologyVersion_exists(collection): - """Test hello response contains topologyVersion as object.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"topologyVersion": IsType("object")}, +RESPONSE_TOPOLOGY_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "topology_exists", + command={"hello": 1}, + use_admin=False, + expected={"topologyVersion": IsType("object")}, msg="topologyVersion should exist and be object", - raw_res=True, - ) + ), + ReplicationTestCase( + "topology_processId", + command={"hello": 1}, + use_admin=False, + expected={"topologyVersion": {"processId": IsType("objectId")}}, + msg="topologyVersion.processId should exist and be ObjectId", + ), + ReplicationTestCase( + "topology_counter", + command={"hello": 1}, + use_admin=False, + expected={"topologyVersion": {"counter": Gte(0)}}, + msg="topologyVersion.counter should be non-negative", + ), +] +# Property [Wire Version]: wire version fields indicate protocol compatibility. +RESPONSE_WIRE_VERSION_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "wire_min_non_negative", + command={"hello": 1}, + use_admin=False, + expected={"minWireVersion": Gte(0)}, + msg="minWireVersion should be >= 0", + ), + ReplicationTestCase( + "wire_max_reasonable", + command={"hello": 1}, + use_admin=False, + expected={"maxWireVersion": Gte(21)}, + msg="maxWireVersion should be >= 21 for MongoDB 7.0+", + ), +] -def test_hello_response_topologyVersion_processId(collection): - """Test hello topologyVersion.processId exists and is ObjectId.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( +HELLO_RESPONSE_ALL_TESTS: list[ReplicationTestCase] = ( + RESPONSE_COMMON_TESTS + RESPONSE_TOPOLOGY_TESTS + RESPONSE_WIRE_VERSION_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(HELLO_RESPONSE_ALL_TESTS)) +def test_hello_response_structure(collection, test): + """Test hello response field types and values.""" + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( result, - {"topologyVersion": {"processId": IsType("objectId")}}, - msg="topologyVersion.processId should exist and be ObjectId", + expected=test.build_expected(ctx), + msg=test.msg, raw_res=True, ) -def test_hello_response_topologyVersion_counter(collection): - """Test hello topologyVersion.counter exists and is non-negative.""" +# These tests require comparing two hello calls and cannot use the +# parametrized test case pattern. +def test_hello_response_wire_version_ordering(collection): + """Test hello response maxWireVersion >= minWireVersion.""" result = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( result, - {"topologyVersion": {"counter": Gte(0)}}, - msg="topologyVersion.counter should be non-negative", + expected={"maxWireVersion": Gte(result.get("minWireVersion", 0))}, + msg="maxWireVersion should be >= minWireVersion", raw_res=True, ) @@ -182,32 +180,9 @@ def test_hello_response_topologyVersion_processId_stable(collection): """Test hello topologyVersion.processId is stable across calls.""" r1 = execute_command(collection, {"hello": 1}) r2 = execute_command(collection, {"hello": 1}) - assertProperties( + assertResult( r2, - {"topologyVersion": {"processId": Eq(r1["topologyVersion"]["processId"])}}, + expected={"topologyVersion": {"processId": Eq(r1["topologyVersion"]["processId"])}}, msg="topologyVersion.processId should remain the same across consecutive calls", raw_res=True, ) - - -# Property [Wire Version]: wire version fields indicate protocol compatibility. -def test_hello_wire_version_min_non_negative(collection): - """Test hello minWireVersion is non-negative.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"minWireVersion": Gte(0)}, - msg="minWireVersion should be >= 0", - raw_res=True, - ) - - -def test_hello_wire_version_max_reasonable(collection): - """Test hello maxWireVersion is a reasonable value for modern MongoDB.""" - result = execute_command(collection, {"hello": 1}) - assertProperties( - result, - {"maxWireVersion": Gte(21)}, - msg="maxWireVersion should be >= 21 for MongoDB 7.0+", - raw_res=True, - ) From a9308d930c48ee26d5c7c285c22e389fd2ffe432 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 16:46:14 -0700 Subject: [PATCH 03/12] apply style guide Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_consistency.py | 60 +++++----- .../commands/hello/test_hello_replica_set.py | 109 ++++++++---------- .../hello/test_hello_response_structure.py | 85 +++++++------- .../commands/hello/test_smoke_hello.py | 2 +- 4 files changed, 123 insertions(+), 133 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py index 1d436b6f5..81c9ecdbc 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -20,73 +20,73 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq, Gte -# Property [Execution Context]: hello works on any database. +# Property [Execution Context]: hello succeeds on any database context. HELLO_CONTEXT_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "admin_database", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=True, expected={"ok": Eq(1.0)}, msg="hello should succeed on admin database", ), ReplicationTestCase( "non_admin_database", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"ok": Eq(1.0)}, msg="hello should succeed on non-admin database", ), ReplicationTestCase( "user_created_database", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"ok": Eq(1.0)}, msg="hello should succeed on a user-created database", ), ReplicationTestCase( "nonexistent_database", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"ok": Eq(1.0)}, msg="hello should succeed on any database", ), ] -# Property [Standalone Instance]: on standalone, isWritablePrimary is true, +# Property [Standalone Instance]: on standalone, isWritablePrimary is true and # readOnly is false. HELLO_STANDALONE_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "standalone_isWritablePrimary", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"isWritablePrimary": Eq(True)}, - msg="isWritablePrimary should be true on standalone/primary", + msg="hello should return isWritablePrimary true on standalone/primary", ), ReplicationTestCase( "standalone_readOnly", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"readOnly": Eq(False)}, - msg="readOnly should be false on standalone/primary", + msg="hello should return readOnly false on standalone/primary", ), ] -# Property [Legacy isMaster Compatibility]: isMaster and ismaster still -# work and return compatible output. +# Property [Legacy isMaster Compatibility]: isMaster and ismaster still work +# and return compatible output. HELLO_LEGACY_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "legacy_isMaster_succeeds", - command={"isMaster": 1}, + command=lambda ctx: {"isMaster": 1}, use_admin=False, expected={"ok": Eq(1.0)}, - msg="isMaster should succeed with ok: 1.0", + msg="hello should support isMaster alias with ok: 1.0", ), ReplicationTestCase( "legacy_ismaster_lowercase_succeeds", - command={"ismaster": 1}, + command=lambda ctx: {"ismaster": 1}, use_admin=False, expected={"ok": Eq(1.0)}, - msg="ismaster (lowercase) should succeed with ok: 1.0", + msg="hello should support ismaster (lowercase) alias with ok: 1.0", ), ] @@ -106,14 +106,14 @@ def test_hello_consistency(collection, test): assertResult( result, expected=test.build_expected(ctx), + error_code=test.error_code, msg=test.msg, raw_res=True, ) -# Property [Consistency]: static fields are identical across consecutive -# hello calls. These tests compare two command results and cannot use -# the parametrized test case pattern. +# Property [Static Field Consistency]: static fields are identical across +# consecutive hello calls. def test_hello_consistency_static_fields(collection): """Test hello returns identical static fields across consecutive calls.""" r1 = execute_command(collection, {"hello": 1}) @@ -127,13 +127,15 @@ def test_hello_consistency_static_fields(collection): "minWireVersion": Eq(r1["minWireVersion"]), "maxWireVersion": Eq(r1["maxWireVersion"]), }, - msg="Static fields should be identical across consecutive calls", + msg="hello should return identical static fields across consecutive calls", raw_res=True, ) +# Property [Admin and User DB Consistency]: static fields match between admin +# and user databases. def test_hello_admin_and_user_db_static_fields_match(collection): - """Test hello static fields are identical on admin and user databases.""" + """Test hello static fields match between admin and user databases.""" r_admin = execute_admin_command(collection, {"hello": 1}) r_user = execute_command(collection, {"hello": 1}) assertResult( @@ -145,11 +147,12 @@ def test_hello_admin_and_user_db_static_fields_match(collection): "minWireVersion": Eq(r_admin["minWireVersion"]), "maxWireVersion": Eq(r_admin["maxWireVersion"]), }, - msg="Static fields should match between admin and user db", + msg="hello should return matching static fields on admin and user db", raw_res=True, ) +# Property [localTime Monotonicity]: localTime is non-decreasing across calls. def test_hello_localTime_monotonic(collection): """Test hello localTime is monotonically non-decreasing.""" r1 = execute_command(collection, {"hello": 1}) @@ -157,23 +160,26 @@ def test_hello_localTime_monotonic(collection): assertResult( r2, expected={"localTime": Gte(r1["localTime"])}, - msg="localTime should be non-decreasing across calls", + msg="hello should return non-decreasing localTime across calls", raw_res=True, ) +# Property [connectionId Stability]: connectionId is stable on the same connection. def test_hello_connectionId_stable(collection): - """Test hello connectionId remains the same on same connection.""" + """Test hello connectionId remains stable on same connection.""" r1 = execute_command(collection, {"hello": 1}) r2 = execute_command(collection, {"hello": 1}) assertResult( r2, expected={"connectionId": Eq(r1["connectionId"])}, - msg="connectionId should remain the same across calls on same connection", + msg="hello should return stable connectionId on same connection", raw_res=True, ) +# Property [Legacy Field Compatibility]: hello and isMaster return the same +# values for common fields. def test_hello_legacy_isMaster_fields_match(collection): """Test hello and isMaster return same values for common fields.""" r_hello = execute_command(collection, {"hello": 1}) @@ -190,7 +196,7 @@ def test_hello_legacy_isMaster_fields_match(collection): "connectionId": Eq(r_hello["connectionId"]), "logicalSessionTimeoutMinutes": Eq(r_hello["logicalSessionTimeoutMinutes"]), }, - msg="Common fields should match between hello and isMaster", + msg="hello should return matching fields with isMaster alias", raw_res=True, ) @@ -210,6 +216,6 @@ def test_hello_read_only_behavior(collection): "minWireVersion": Eq(r_before["minWireVersion"]), "maxWireVersion": Eq(r_before["maxWireVersion"]), }, - msg="Static fields should be unchanged after insert", + msg="hello should return unchanged static fields after insert", raw_res=True, ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index 51be9680e..d4386059d 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -28,105 +28,109 @@ pytestmark = [pytest.mark.replica_set] - -# Property [Required Replica Set Fields]: hello response includes -# hosts, setName, setVersion, secondary, primary, me, and lastWrite -# when connected to a replica set member. +# Property [Required Replica Set Fields]: hello response includes hosts, +# setName, setVersion, secondary, primary, me, and lastWrite when connected +# to a replica set member. RS_REQUIRED_FIELD_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "rs_hosts", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"hosts": IsType("array")}, - msg="hosts should exist and be array on replica set member", + msg="hello should return hosts as array on replica set member", ), ReplicationTestCase( "rs_setName", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"setName": NonEmptyStr()}, - msg="setName should exist and be non-empty string", + msg="hello should return setName as non-empty string", ), ReplicationTestCase( "rs_setVersion", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"setVersion": Gte(1)}, - msg="setVersion should be a positive integer", + msg="hello should return setVersion as positive integer", ), ReplicationTestCase( "rs_secondary", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"secondary": IsType("bool")}, - msg="secondary should exist and be boolean", + msg="hello should return secondary as boolean", ), ReplicationTestCase( "rs_primary", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"primary": NonEmptyStr()}, - msg="primary should exist and be non-empty string", + msg="hello should return primary as non-empty string", ), ReplicationTestCase( "rs_me", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"me": NonEmptyStr()}, - msg="me should exist and be non-empty string", + msg="hello should return me as non-empty string", ), ReplicationTestCase( "rs_lastWrite", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"lastWrite": IsType("object")}, - msg="lastWrite should exist and be object", + msg="hello should return lastWrite as object", ), ReplicationTestCase( "rs_lastWrite_opTime", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"lastWrite": {"opTime": IsType("object")}}, - msg="lastWrite.opTime should exist and be object", + msg="hello should return lastWrite.opTime as object", ), ReplicationTestCase( "rs_lastWrite_lastWriteDate", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"lastWrite": {"lastWriteDate": IsType("date")}}, - msg="lastWrite.lastWriteDate should exist and be date", + msg="hello should return lastWrite.lastWriteDate as date", ), ReplicationTestCase( "rs_lastWrite_majorityOpTime", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"lastWrite": {"majorityOpTime": IsType("object")}}, - msg="lastWrite.majorityOpTime should exist and be object", + msg="hello should return lastWrite.majorityOpTime as object", ), ReplicationTestCase( "rs_lastWrite_majorityWriteDate", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"lastWrite": {"majorityWriteDate": IsType("date")}}, - msg="lastWrite.majorityWriteDate should exist and be date", + msg="hello should return lastWrite.majorityWriteDate as date", ), ReplicationTestCase( - "rs_primary_isWritablePrimary_and_not_secondary", - command={"hello": 1}, + "rs_electionId_on_primary", + command=lambda ctx: {"hello": 1}, use_admin=False, - expected={"isWritablePrimary": Eq(True), "secondary": Eq(False)}, - msg="Primary should have isWritablePrimary=true and secondary=false", + expected={"electionId": IsType("objectId")}, + msg="hello should return electionId as ObjectId on primary", ), +] + +# Property [Primary Node Invariants]: on a primary, isWritablePrimary is true, +# secondary is false, primary equals me, and hosts contains both. +RS_PRIMARY_INVARIANT_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( - "rs_electionId_on_primary", - command={"hello": 1}, + "rs_primary_isWritablePrimary_and_not_secondary", + command=lambda ctx: {"hello": 1}, use_admin=False, - expected={"electionId": IsType("objectId")}, - msg="electionId should exist and be ObjectId on primary", + expected={"isWritablePrimary": Eq(True), "secondary": Eq(False)}, + msg="hello should return isWritablePrimary=true and secondary=false on primary", ), ] -HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = RS_REQUIRED_FIELD_TESTS +HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = RS_REQUIRED_FIELD_TESTS + RS_PRIMARY_INVARIANT_TESTS @pytest.mark.parametrize("test", pytest_params(HELLO_RS_ALL_TESTS)) @@ -137,20 +141,21 @@ def test_hello_replica_set(collection, test): assertResult( result, expected=test.build_expected(ctx), + error_code=test.error_code, msg=test.msg, raw_res=True, ) -# Property [Replica Set Behavioral Checks]: these tests reference -# dynamic values from the result itself and require standalone functions. +# Property [Replica Set Behavioral Checks]: these tests reference dynamic +# values from the result itself and require standalone functions. def test_hello_rs_primary_equals_me(collection): - """Test on primary, primary field equals me field.""" + """Test hello primary field equals me field on primary.""" result = execute_command(collection, {"hello": 1}) assertResult( result, expected={"primary": Eq(result.get("me", "MISSING"))}, - msg="On primary, 'primary' should equal 'me'", + msg="hello should return primary equal to me on primary node", raw_res=True, ) @@ -161,7 +166,7 @@ def test_hello_rs_hosts_contains_primary(collection): assertResult( result, expected={"hosts": ContainsElement(result.get("primary", "MISSING"))}, - msg="hosts array should contain the primary", + msg="hello should return hosts array containing the primary", raw_res=True, ) @@ -172,28 +177,6 @@ def test_hello_rs_me_in_hosts(collection): assertResult( result, expected={"hosts": ContainsElement(result.get("me", "MISSING"))}, - msg="me should appear in hosts array", - raw_res=True, - ) - - -def test_hello_rs_lastWriteDate_is_date(collection): - """Test hello lastWrite.lastWriteDate is a date.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"lastWrite": {"lastWriteDate": IsType("date")}}, - msg="lastWrite.lastWriteDate should be a date", - raw_res=True, - ) - - -def test_hello_rs_majorityWriteDate_is_date(collection): - """Test hello lastWrite.majorityWriteDate is a date.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"lastWrite": {"majorityWriteDate": IsType("date")}}, - msg="lastWrite.majorityWriteDate should be a date", + msg="hello should return hosts array containing me", raw_res=True, ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 01151fc02..21288e4ab 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -24,106 +24,106 @@ RESPONSE_COMMON_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "response_isWritablePrimary", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"isWritablePrimary": IsType("bool")}, - msg="isWritablePrimary should exist and be boolean", + msg="hello should return isWritablePrimary as boolean", ), ReplicationTestCase( "response_maxBsonObjectSize", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, - expected={"maxBsonObjectSize": Eq(16777216)}, - msg="maxBsonObjectSize should equal 16777216", + expected={"maxBsonObjectSize": Eq(16_777_216)}, + msg="hello should return maxBsonObjectSize equal to 16777216", ), ReplicationTestCase( "response_maxMessageSizeBytes", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, - expected={"maxMessageSizeBytes": Eq(48000000)}, - msg="maxMessageSizeBytes should equal 48000000", + expected={"maxMessageSizeBytes": Eq(48_000_000)}, + msg="hello should return maxMessageSizeBytes equal to 48000000", ), ReplicationTestCase( "response_maxWriteBatchSize", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, - expected={"maxWriteBatchSize": Eq(100000)}, - msg="maxWriteBatchSize should equal 100000", + expected={"maxWriteBatchSize": Eq(100_000)}, + msg="hello should return maxWriteBatchSize equal to 100000", ), ReplicationTestCase( "response_localTime", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"localTime": IsType("date")}, - msg="localTime should exist and be date", + msg="hello should return localTime as date", ), ReplicationTestCase( "response_logicalSessionTimeoutMinutes", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"logicalSessionTimeoutMinutes": Gte(1)}, - msg="logicalSessionTimeoutMinutes should be a positive integer", + msg="hello should return logicalSessionTimeoutMinutes as positive integer", ), ReplicationTestCase( "response_connectionId", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"connectionId": Gte(1)}, - msg="connectionId should be a positive integer", + msg="hello should return connectionId as positive integer", ), ReplicationTestCase( "response_minWireVersion", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"minWireVersion": Gte(0)}, - msg="minWireVersion should be a non-negative integer", + msg="hello should return minWireVersion as non-negative integer", ), ReplicationTestCase( "response_maxWireVersion", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"maxWireVersion": IsType("int")}, - msg="maxWireVersion should exist and be int", + msg="hello should return maxWireVersion as int", ), ReplicationTestCase( "response_readOnly", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"readOnly": IsType("bool")}, - msg="readOnly should exist and be boolean", + msg="hello should return readOnly as boolean", ), ReplicationTestCase( "response_ok", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"ok": Eq(1.0)}, - msg="ok should equal 1.0", + msg="hello should return ok equal to 1.0", ), ] -# Property [topologyVersion]: hello response contains topologyVersion -# with processId (ObjectId) and counter (non-negative int64). +# Property [topologyVersion]: hello response contains topologyVersion with +# processId (ObjectId) and counter (non-negative int64). RESPONSE_TOPOLOGY_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "topology_exists", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"topologyVersion": IsType("object")}, - msg="topologyVersion should exist and be object", + msg="hello should return topologyVersion as object", ), ReplicationTestCase( "topology_processId", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"topologyVersion": {"processId": IsType("objectId")}}, - msg="topologyVersion.processId should exist and be ObjectId", + msg="hello should return topologyVersion.processId as ObjectId", ), ReplicationTestCase( "topology_counter", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"topologyVersion": {"counter": Gte(0)}}, - msg="topologyVersion.counter should be non-negative", + msg="hello should return topologyVersion.counter as non-negative", ), ] @@ -131,17 +131,17 @@ RESPONSE_WIRE_VERSION_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "wire_min_non_negative", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"minWireVersion": Gte(0)}, - msg="minWireVersion should be >= 0", + msg="hello should return minWireVersion >= 0", ), ReplicationTestCase( "wire_max_reasonable", - command={"hello": 1}, + command=lambda ctx: {"hello": 1}, use_admin=False, expected={"maxWireVersion": Gte(21)}, - msg="maxWireVersion should be >= 21 for MongoDB 7.0+", + msg="hello should return maxWireVersion >= 21 for MongoDB 7.0+", ), ] @@ -158,24 +158,25 @@ def test_hello_response_structure(collection, test): assertResult( result, expected=test.build_expected(ctx), + error_code=test.error_code, msg=test.msg, raw_res=True, ) -# These tests require comparing two hello calls and cannot use the -# parametrized test case pattern. +# Property [Wire Version Ordering]: maxWireVersion is always >= minWireVersion. def test_hello_response_wire_version_ordering(collection): - """Test hello response maxWireVersion >= minWireVersion.""" + """Test hello maxWireVersion >= minWireVersion.""" result = execute_command(collection, {"hello": 1}) assertResult( result, expected={"maxWireVersion": Gte(result.get("minWireVersion", 0))}, - msg="maxWireVersion should be >= minWireVersion", + msg="hello should return maxWireVersion >= minWireVersion", raw_res=True, ) +# Property [topologyVersion Stability]: processId remains stable across calls. def test_hello_response_topologyVersion_processId_stable(collection): """Test hello topologyVersion.processId is stable across calls.""" r1 = execute_command(collection, {"hello": 1}) @@ -183,6 +184,6 @@ def test_hello_response_topologyVersion_processId_stable(collection): assertResult( r2, expected={"topologyVersion": {"processId": Eq(r1["topologyVersion"]["processId"])}}, - msg="topologyVersion.processId should remain the same across consecutive calls", + msg="hello should return stable topologyVersion.processId across calls", raw_res=True, ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py index f3815623b..d7746c83a 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_smoke_hello.py @@ -18,4 +18,4 @@ def test_smoke_hello(collection): result = execute_command(collection, {"hello": 1}) expected = {"ok": 1.0} - assertSuccessPartial(result, expected, msg="Should support hello command") + assertSuccessPartial(result, expected, msg="hello should return ok: 1.0") From 6c10581b9f3172987011d62d6bf955234e84076e Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 16:53:19 -0700 Subject: [PATCH 04/12] apply review Signed-off-by: Alina (Xi) Li --- .../hello/test_hello_command_value.py | 3 +++ .../commands/hello/test_hello_comment.py | 1 + ...alidation.py => test_hello_error_cases.py} | 20 ++++++++++++------- .../hello/test_hello_response_structure.py | 12 ----------- 4 files changed, 17 insertions(+), 19 deletions(-) rename documentdb_tests/compatibility/tests/system/replication/commands/hello/{test_hello_error_validation.py => test_hello_error_cases.py} (76%) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py index 7bbd7ba03..2d2e97cd0 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_command_value.py @@ -35,11 +35,14 @@ for tid, val in [ ("int32", 1), ("bool_true", True), + ("bool_false", False), ("double", 1.0), ("string", "test"), ("null", None), ("object_empty", {}), + ("object", {"key": "val"}), ("array_empty", []), + ("array", [1, 2]), ("int64", Int64(1)), ("decimal128", Decimal128("1")), ("datetime", datetime(2024, 1, 1, tzinfo=timezone.utc)), diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py index 4725b28de..92a207637 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_comment.py @@ -37,6 +37,7 @@ ("int32", 42), ("double", 3.14), ("bool_true", True), + ("bool_false", False), ("null", None), ("object", {"key": "val"}), ("array", [1, "two", 3]), diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py similarity index 76% rename from documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py rename to documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py index 1bda0d6d8..14446d990 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_validation.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py @@ -1,4 +1,4 @@ -"""Tests for hello command error code validation. +"""Tests for hello command error cases. Validates that case-mismatched command names are rejected with COMMAND_NOT_FOUND_ERROR. @@ -14,13 +14,13 @@ from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 ReplicationTestCase, ) -from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.error_codes import COMMAND_NOT_FOUND_ERROR from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params -# Property [Case-Sensitive Command Name]: the hello command is -# case-sensitive. Case mismatches produce COMMAND_NOT_FOUND_ERROR. +# Property [Case-Sensitive Command Name]: the hello command is case-sensitive +# and rejects case mismatches. HELLO_CASE_ERROR_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "case_capital_H", @@ -47,8 +47,14 @@ @pytest.mark.parametrize("test", pytest_params(HELLO_CASE_ERROR_TESTS)) -def test_hello_error_validation(collection, test): - """Test hello command case-sensitive name validation.""" +def test_hello_error_cases(collection, test): + """Test hello command error cases.""" ctx = CommandContext.from_collection(collection) result = execute_command(collection, test.build_command(ctx)) - assertFailureCode(result, test.error_code, msg=test.msg) + assertResult( + result, + expected=test.build_expected(ctx), + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 21288e4ab..50afdf901 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -164,18 +164,6 @@ def test_hello_response_structure(collection, test): ) -# Property [Wire Version Ordering]: maxWireVersion is always >= minWireVersion. -def test_hello_response_wire_version_ordering(collection): - """Test hello maxWireVersion >= minWireVersion.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"maxWireVersion": Gte(result.get("minWireVersion", 0))}, - msg="hello should return maxWireVersion >= minWireVersion", - raw_res=True, - ) - - # Property [topologyVersion Stability]: processId remains stable across calls. def test_hello_response_topologyVersion_processId_stable(collection): """Test hello topologyVersion.processId is stable across calls.""" From 857332f1ecfaccbcc257cfd4a111bf3460f84884 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 18 Jun 2026 17:07:50 -0700 Subject: [PATCH 05/12] add Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_consistency.py | 2 +- .../commands/hello/test_hello_replica_set.py | 58 +++++++------------ .../hello/test_hello_response_structure.py | 2 +- .../utils/replication_test_case.py | 24 ++++++++ 4 files changed, 48 insertions(+), 38 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py index 81c9ecdbc..0b7034204 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -105,7 +105,7 @@ def test_hello_consistency(collection, test): result = execute_command(collection, test.build_command(ctx)) assertResult( result, - expected=test.build_expected(ctx), + expected=test.build_expected(ctx, result), error_code=test.error_code, msg=test.msg, raw_res=True, diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index d4386059d..d284203b1 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -128,6 +128,27 @@ expected={"isWritablePrimary": Eq(True), "secondary": Eq(False)}, msg="hello should return isWritablePrimary=true and secondary=false on primary", ), + ReplicationTestCase( + "rs_primary_equals_me", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: {"primary": Eq(result.get("me", "MISSING"))}, + msg="hello should return primary equal to me on primary node", + ), + ReplicationTestCase( + "rs_hosts_contains_primary", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: {"hosts": ContainsElement(result.get("primary", "MISSING"))}, + msg="hello should return hosts array containing the primary", + ), + ReplicationTestCase( + "rs_me_in_hosts", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: {"hosts": ContainsElement(result.get("me", "MISSING"))}, + msg="hello should return hosts array containing me", + ), ] HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = RS_REQUIRED_FIELD_TESTS + RS_PRIMARY_INVARIANT_TESTS @@ -140,43 +161,8 @@ def test_hello_replica_set(collection, test): result = execute_command(collection, test.build_command(ctx)) assertResult( result, - expected=test.build_expected(ctx), + expected=test.build_expected(ctx, result), error_code=test.error_code, msg=test.msg, raw_res=True, ) - - -# Property [Replica Set Behavioral Checks]: these tests reference dynamic -# values from the result itself and require standalone functions. -def test_hello_rs_primary_equals_me(collection): - """Test hello primary field equals me field on primary.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"primary": Eq(result.get("me", "MISSING"))}, - msg="hello should return primary equal to me on primary node", - raw_res=True, - ) - - -def test_hello_rs_hosts_contains_primary(collection): - """Test hello hosts array contains the primary.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"hosts": ContainsElement(result.get("primary", "MISSING"))}, - msg="hello should return hosts array containing the primary", - raw_res=True, - ) - - -def test_hello_rs_me_in_hosts(collection): - """Test hello me appears in hosts array.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"hosts": ContainsElement(result.get("me", "MISSING"))}, - msg="hello should return hosts array containing me", - raw_res=True, - ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 50afdf901..7a6e6cb0c 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -157,7 +157,7 @@ def test_hello_response_structure(collection, test): result = execute_command(collection, test.build_command(ctx)) assertResult( result, - expected=test.build_expected(ctx), + expected=test.build_expected(ctx, result), error_code=test.error_code, msg=test.msg, raw_res=True, diff --git a/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py b/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py index 5b93302e2..449e89bfa 100644 --- a/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py +++ b/documentdb_tests/compatibility/tests/system/replication/utils/replication_test_case.py @@ -2,9 +2,12 @@ from __future__ import annotations +import inspect from dataclasses import dataclass +from typing import Any from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, CommandTestCase, ) @@ -16,6 +19,10 @@ class ReplicationTestCase(CommandTestCase): Extends CommandTestCase with a ``use_admin`` flag that controls whether the command is executed against the admin database. + The ``expected`` field supports an extended callable signature + ``(ctx, result) -> dict`` for assertions that reference dynamic + values from the command result itself. + Attributes: use_admin: If True (the default), execute against the admin database via ``execute_admin_command``. If False, execute @@ -23,3 +30,20 @@ class ReplicationTestCase(CommandTestCase): """ use_admin: bool = True + + def build_expected( + self, + ctx: CommandContext, + result: dict[str, Any] | None = None, + ) -> dict[str, Any] | list[dict[str, Any]] | None: + """Resolve expected, optionally passing the command result. + + If ``expected`` is a callable that accepts two parameters + (ctx, result), the result is forwarded. Otherwise, falls + back to the parent implementation. + """ + if callable(self.expected) and not isinstance(self.expected, (dict, list)): + sig = inspect.signature(self.expected) + if len(sig.parameters) == 2: + return self.expected(ctx, result) + return super().build_expected(ctx) From 5696def8dceda4d11bb9b0c4355a5e5d87fdea8a Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 15:26:37 -0700 Subject: [PATCH 06/12] split into success / error test files Signed-off-by: Alina (Xi) Li --- .../hello/test_hello_sasl_supported_mechs.py | 66 ++-------------- .../test_hello_sasl_supported_mechs_error.py | 79 +++++++++++++++++++ 2 files changed, 86 insertions(+), 59 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py index e8ebdafed..1943ecfcd 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs.py @@ -1,6 +1,6 @@ -"""Tests for hello command saslSupportedMechs parameter. +"""Tests for hello command saslSupportedMechs parameter acceptance. -Validates valid usage, invalid format, type rejection, and edge cases +Validates valid usage, format edge cases, and accepted variations for the saslSupportedMechs parameter. """ @@ -15,7 +15,6 @@ ReplicationTestCase, ) from documentdb_tests.framework.assertions import assertResult -from documentdb_tests.framework.error_codes import BAD_VALUE_ERROR, TYPE_MISMATCH_ERROR from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq @@ -45,19 +44,9 @@ ), ] -# Property [saslSupportedMechs Invalid Format]: hello rejects strings -# that do not follow the "db.user" format. -SASL_INVALID_FORMAT_TESTS: list[ReplicationTestCase] = [ - ReplicationTestCase( - "sasl_no_dot_separator", - command=lambda ctx: { - "hello": 1, - "saslSupportedMechs": "noDotSeparator", - }, - use_admin=False, - error_code=BAD_VALUE_ERROR, - msg="hello should reject saslSupportedMechs without db.user format", - ), +# Property [saslSupportedMechs Format Edge Cases]: hello accepts +# borderline "db.user" format variations that still contain a dot. +SASL_FORMAT_EDGE_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( "sasl_empty_db_component", command=lambda ctx: { @@ -78,13 +67,6 @@ expected={"ok": Eq(1.0)}, msg="hello should accept saslSupportedMechs with empty username", ), - ReplicationTestCase( - "sasl_empty_string", - command=lambda ctx: {"hello": 1, "saslSupportedMechs": ""}, - use_admin=False, - error_code=BAD_VALUE_ERROR, - msg="hello should reject empty string saslSupportedMechs", - ), ReplicationTestCase( "sasl_dot_only", command=lambda ctx: {"hello": 1, "saslSupportedMechs": "."}, @@ -94,36 +76,6 @@ ), ] -# Property [saslSupportedMechs Type Rejection]: hello rejects non-string -# types for saslSupportedMechs. -SASL_TYPE_REJECTION_TESTS: list[ReplicationTestCase] = [ - ReplicationTestCase( - f"sasl_type_{tid}", - command=lambda ctx, v=val: {"hello": 1, "saslSupportedMechs": v}, - use_admin=False, - error_code=err, - msg=f"hello should reject {tid} as saslSupportedMechs", - ) - for tid, val, err in [ - ("int", 123, TYPE_MISMATCH_ERROR), - ("bool", True, TYPE_MISMATCH_ERROR), - ("array", ["admin.user"], TYPE_MISMATCH_ERROR), - ("object", {"db": "admin"}, BAD_VALUE_ERROR), - ] -] - -# Property [saslSupportedMechs Null]: hello rejects null -# saslSupportedMechs with type mismatch error. -SASL_NULL_TEST: list[ReplicationTestCase] = [ - ReplicationTestCase( - "sasl_type_null", - command=lambda ctx: {"hello": 1, "saslSupportedMechs": None}, - use_admin=False, - error_code=TYPE_MISMATCH_ERROR, - msg="hello should reject null saslSupportedMechs", - ), -] - # Property [saslSupportedMechs Edge Cases]: hello handles edge cases # in the "db.user" format string. SASL_EDGE_CASE_TESTS: list[ReplicationTestCase] = [ @@ -170,17 +122,13 @@ ] HELLO_SASL_ALL_TESTS: list[ReplicationTestCase] = ( - SASL_VALID_TESTS - + SASL_INVALID_FORMAT_TESTS - + SASL_TYPE_REJECTION_TESTS - + SASL_NULL_TEST - + SASL_EDGE_CASE_TESTS + SASL_VALID_TESTS + SASL_FORMAT_EDGE_TESTS + SASL_EDGE_CASE_TESTS ) @pytest.mark.parametrize("test", pytest_params(HELLO_SASL_ALL_TESTS)) def test_hello_sasl_supported_mechs(collection, test): - """Test hello saslSupportedMechs parameter handling.""" + """Test hello saslSupportedMechs parameter acceptance.""" ctx = CommandContext.from_collection(collection) result = execute_command(collection, test.build_command(ctx)) assertResult( diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py new file mode 100644 index 000000000..0cd24adf2 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py @@ -0,0 +1,79 @@ +"""Tests for hello command saslSupportedMechs parameter error cases. + +Validates that hello rejects invalid format strings, non-string types, +and null for the saslSupportedMechs parameter. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, +) +from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 + ReplicationTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import BAD_VALUE_ERROR, TYPE_MISMATCH_ERROR +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +# Property [saslSupportedMechs Invalid Format]: hello rejects strings +# that do not follow the "db.user" format. +SASL_INVALID_FORMAT_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_no_dot_separator", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "noDotSeparator", + }, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject saslSupportedMechs without db.user format", + ), + ReplicationTestCase( + "sasl_empty_string", + command=lambda ctx: {"hello": 1, "saslSupportedMechs": ""}, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject empty string saslSupportedMechs", + ), +] + +# Property [saslSupportedMechs Type Rejection]: hello rejects non-string +# types for saslSupportedMechs. +SASL_TYPE_REJECTION_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + f"sasl_type_{tid}", + command=lambda ctx, v=val: {"hello": 1, "saslSupportedMechs": v}, + use_admin=False, + error_code=err, + msg=f"hello should reject {tid} as saslSupportedMechs", + ) + for tid, val, err in [ + ("int", 123, TYPE_MISMATCH_ERROR), + ("bool", True, TYPE_MISMATCH_ERROR), + ("array", ["admin.user"], TYPE_MISMATCH_ERROR), + ("object", {"db": "admin"}, BAD_VALUE_ERROR), + ("null", None, TYPE_MISMATCH_ERROR), + ] +] + +HELLO_SASL_ERROR_TESTS: list[ReplicationTestCase] = ( + SASL_INVALID_FORMAT_TESTS + SASL_TYPE_REJECTION_TESTS +) + + +@pytest.mark.parametrize("test", pytest_params(HELLO_SASL_ERROR_TESTS)) +def test_hello_sasl_supported_mechs_error(collection, test): + """Test hello saslSupportedMechs parameter error cases.""" + 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, + ) From 00ac0f58e300204811a64708e2d667589815ff6c Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 15:40:49 -0700 Subject: [PATCH 07/12] add missing test cases Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_consistency.py | 20 +++++++++++- .../commands/hello/test_hello_replica_set.py | 31 +++++++++++++++++++ .../hello/test_hello_response_structure.py | 12 +++++++ documentdb_tests/framework/property_checks.py | 17 ++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py index 0b7034204..551f1109e 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -18,7 +18,7 @@ from documentdb_tests.framework.assertions import assertResult 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, Gte +from documentdb_tests.framework.property_checks import Eq, Gte, NotExists # Property [Execution Context]: hello succeeds on any database context. HELLO_CONTEXT_TESTS: list[ReplicationTestCase] = [ @@ -69,6 +69,24 @@ expected={"readOnly": Eq(False)}, msg="hello should return readOnly false on standalone/primary", ), + ReplicationTestCase( + "standalone_rs_fields_absent", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected={ + "hosts": NotExists(), + "setName": NotExists(), + "setVersion": NotExists(), + "secondary": NotExists(), + "primary": NotExists(), + "me": NotExists(), + "electionId": NotExists(), + "lastWrite": NotExists(), + "passives": NotExists(), + "arbiters": NotExists(), + }, + msg="hello should not return replica set fields on standalone", + ), ] # Property [Legacy isMaster Compatibility]: isMaster and ismaster still work diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index d284203b1..32fdee3b2 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -23,6 +23,7 @@ Eq, Gte, IsType, + Lte, NonEmptyStr, ) @@ -166,3 +167,33 @@ def test_hello_replica_set(collection, test): msg=test.msg, raw_res=True, ) + + +# Property [lastWrite Date Ordering]: lastWrite dates have expected ordering. +def test_hello_rs_lastWriteDate_not_future(collection): + """Test hello lastWrite.lastWriteDate is not in the future.""" + from datetime import datetime, timezone + + result = execute_command(collection, {"hello": 1}) + now = datetime.now(tz=timezone.utc) + assertResult( + result, + expected={"lastWrite": {"lastWriteDate": Lte(now)}}, + msg="hello lastWrite.lastWriteDate should be <= current time", + raw_res=True, + ) + + +def test_hello_rs_majorityWriteDate_lte_lastWriteDate(collection): + """Test hello lastWrite.majorityWriteDate <= lastWrite.lastWriteDate.""" + result = execute_command(collection, {"hello": 1}) + assertResult( + result, + expected={ + "lastWrite": { + "majorityWriteDate": Lte(result["lastWrite"]["lastWriteDate"]), + }, + }, + msg="hello majorityWriteDate should be <= lastWriteDate", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 7a6e6cb0c..c09220774 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -164,6 +164,18 @@ def test_hello_response_structure(collection, test): ) +# Property [Wire Version Consistency]: maxWireVersion >= minWireVersion. +def test_hello_response_wire_version_max_gte_min(collection): + """Test hello maxWireVersion >= minWireVersion.""" + result = execute_command(collection, {"hello": 1}) + assertResult( + result, + expected={"maxWireVersion": Gte(result["minWireVersion"])}, + msg="hello should return maxWireVersion >= minWireVersion", + raw_res=True, + ) + + # Property [topologyVersion Stability]: processId remains stable across calls. def test_hello_response_topologyVersion_processId_stable(collection): """Test hello topologyVersion.processId is stable across calls.""" diff --git a/documentdb_tests/framework/property_checks.py b/documentdb_tests/framework/property_checks.py index 0ffb575cf..26c53a3d3 100644 --- a/documentdb_tests/framework/property_checks.py +++ b/documentdb_tests/framework/property_checks.py @@ -311,6 +311,23 @@ def __repr__(self) -> str: return f"{type(self).__name__}({self.minimum!r})" +class Lte(Check): + """Assert that the field is less than or equal to a value.""" + + def __init__(self, maximum: Any) -> None: + self.maximum = maximum + + def check(self, value: Any, path: str) -> str | None: + if value is _FIELD_ABSENT: + return f"expected '{path}' <= {self.maximum!r}, but field is missing" + if value > self.maximum: + return f"expected '{path}' <= {self.maximum!r}, got {value!r}" + return None + + def __repr__(self) -> str: + return f"{type(self).__name__}({self.maximum!r})" + + class NonEmptyStr(Check): """Assert that the field is a non-empty string. From 228408ad3a99904917dd83cbb8a2932bd0f57a78 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 15:52:01 -0700 Subject: [PATCH 08/12] remove duplicate Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_response_structure.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index c09220774..4fba7a805 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -71,13 +71,6 @@ expected={"connectionId": Gte(1)}, msg="hello should return connectionId as positive integer", ), - ReplicationTestCase( - "response_minWireVersion", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"minWireVersion": Gte(0)}, - msg="hello should return minWireVersion as non-negative integer", - ), ReplicationTestCase( "response_maxWireVersion", command=lambda ctx: {"hello": 1}, From a4212314f7aaa1a372b904865fd7d0fa25783ed0 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 15:59:02 -0700 Subject: [PATCH 09/12] fix no-ops and rename, fix test_hello_standalone_rs_fields_absent Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_consistency.py | 70 +++++++++---------- .../commands/hello/test_hello_replica_set.py | 11 +-- .../hello/test_hello_response_structure.py | 14 ---- 3 files changed, 34 insertions(+), 61 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py index 551f1109e..f8cad546f 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_consistency.py @@ -23,70 +23,38 @@ # Property [Execution Context]: hello succeeds on any database context. HELLO_CONTEXT_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( - "admin_database", + "context_admin_database", command=lambda ctx: {"hello": 1}, use_admin=True, expected={"ok": Eq(1.0)}, msg="hello should succeed on admin database", ), ReplicationTestCase( - "non_admin_database", + "context_user_database", command=lambda ctx: {"hello": 1}, use_admin=False, expected={"ok": Eq(1.0)}, msg="hello should succeed on non-admin database", ), - ReplicationTestCase( - "user_created_database", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"ok": Eq(1.0)}, - msg="hello should succeed on a user-created database", - ), - ReplicationTestCase( - "nonexistent_database", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"ok": Eq(1.0)}, - msg="hello should succeed on any database", - ), ] -# Property [Standalone Instance]: on standalone, isWritablePrimary is true and -# readOnly is false. +# Property [Primary/Standalone Defaults]: on a standalone or primary node, +# isWritablePrimary is true and readOnly is false. HELLO_STANDALONE_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( - "standalone_isWritablePrimary", + "primary_isWritablePrimary_true", command=lambda ctx: {"hello": 1}, use_admin=False, expected={"isWritablePrimary": Eq(True)}, msg="hello should return isWritablePrimary true on standalone/primary", ), ReplicationTestCase( - "standalone_readOnly", + "primary_readOnly_false", command=lambda ctx: {"hello": 1}, use_admin=False, expected={"readOnly": Eq(False)}, msg="hello should return readOnly false on standalone/primary", ), - ReplicationTestCase( - "standalone_rs_fields_absent", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={ - "hosts": NotExists(), - "setName": NotExists(), - "setVersion": NotExists(), - "secondary": NotExists(), - "primary": NotExists(), - "me": NotExists(), - "electionId": NotExists(), - "lastWrite": NotExists(), - "passives": NotExists(), - "arbiters": NotExists(), - }, - msg="hello should not return replica set fields on standalone", - ), ] # Property [Legacy isMaster Compatibility]: isMaster and ismaster still work @@ -237,3 +205,29 @@ def test_hello_read_only_behavior(collection): msg="hello should return unchanged static fields after insert", raw_res=True, ) + + +# Property [Standalone RS Fields Absent]: on standalone, replica set fields +# are absent from the hello response. +def test_hello_standalone_rs_fields_absent(collection): + """Test hello does not return replica set fields on standalone.""" + result = execute_command(collection, {"hello": 1}) + if result.get("setName"): + pytest.skip("connected to replica set, not standalone") + assertResult( + result, + expected={ + "hosts": NotExists(), + "setName": NotExists(), + "setVersion": NotExists(), + "secondary": NotExists(), + "primary": NotExists(), + "me": NotExists(), + "electionId": NotExists(), + "lastWrite": NotExists(), + "passives": NotExists(), + "arbiters": NotExists(), + }, + msg="hello should not return replica set fields on standalone", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index 32fdee3b2..e7899b01c 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -34,7 +34,7 @@ # to a replica set member. RS_REQUIRED_FIELD_TESTS: list[ReplicationTestCase] = [ ReplicationTestCase( - "rs_hosts", + "rs_hosts_is_array", command=lambda ctx: {"hello": 1}, use_admin=False, expected={"hosts": IsType("array")}, @@ -55,7 +55,7 @@ msg="hello should return setVersion as positive integer", ), ReplicationTestCase( - "rs_secondary", + "rs_secondary_is_bool", command=lambda ctx: {"hello": 1}, use_admin=False, expected={"secondary": IsType("bool")}, @@ -75,13 +75,6 @@ expected={"me": NonEmptyStr()}, msg="hello should return me as non-empty string", ), - ReplicationTestCase( - "rs_lastWrite", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"lastWrite": IsType("object")}, - msg="hello should return lastWrite as object", - ), ReplicationTestCase( "rs_lastWrite_opTime", command=lambda ctx: {"hello": 1}, diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 4fba7a805..33399d376 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -71,13 +71,6 @@ expected={"connectionId": Gte(1)}, msg="hello should return connectionId as positive integer", ), - ReplicationTestCase( - "response_maxWireVersion", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"maxWireVersion": IsType("int")}, - msg="hello should return maxWireVersion as int", - ), ReplicationTestCase( "response_readOnly", command=lambda ctx: {"hello": 1}, @@ -97,13 +90,6 @@ # Property [topologyVersion]: hello response contains topologyVersion with # processId (ObjectId) and counter (non-negative int64). RESPONSE_TOPOLOGY_TESTS: list[ReplicationTestCase] = [ - ReplicationTestCase( - "topology_exists", - command=lambda ctx: {"hello": 1}, - use_admin=False, - expected={"topologyVersion": IsType("object")}, - msg="hello should return topologyVersion as object", - ), ReplicationTestCase( "topology_processId", command=lambda ctx: {"hello": 1}, From 44a91bde27a03dca3945c4562e66dc09a80bb6d6 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 16:08:20 -0700 Subject: [PATCH 10/12] merge error files Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_error_cases.py | 57 ++++++++++++- .../test_hello_sasl_supported_mechs_error.py | 79 ------------------- 2 files changed, 53 insertions(+), 83 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py index 14446d990..823f344e3 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_error_cases.py @@ -1,7 +1,7 @@ """Tests for hello command error cases. -Validates that case-mismatched command names are rejected with -COMMAND_NOT_FOUND_ERROR. +Validates case-sensitive command name rejection, saslSupportedMechs +invalid format rejection, and saslSupportedMechs type rejection. """ from __future__ import annotations @@ -15,7 +15,11 @@ ReplicationTestCase, ) from documentdb_tests.framework.assertions import assertResult -from documentdb_tests.framework.error_codes import COMMAND_NOT_FOUND_ERROR +from documentdb_tests.framework.error_codes import ( + BAD_VALUE_ERROR, + COMMAND_NOT_FOUND_ERROR, + TYPE_MISMATCH_ERROR, +) from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params @@ -45,8 +49,53 @@ ), ] +# Property [saslSupportedMechs Invalid Format]: hello rejects strings +# that do not follow the "db.user" format. +SASL_INVALID_FORMAT_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "sasl_no_dot_separator", + command=lambda ctx: { + "hello": 1, + "saslSupportedMechs": "noDotSeparator", + }, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject saslSupportedMechs without db.user format", + ), + ReplicationTestCase( + "sasl_empty_string", + command=lambda ctx: {"hello": 1, "saslSupportedMechs": ""}, + use_admin=False, + error_code=BAD_VALUE_ERROR, + msg="hello should reject empty string saslSupportedMechs", + ), +] + +# Property [saslSupportedMechs Type Rejection]: hello rejects non-string +# types for saslSupportedMechs. +SASL_TYPE_REJECTION_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + f"sasl_type_{tid}", + command=lambda ctx, v=val: {"hello": 1, "saslSupportedMechs": v}, + use_admin=False, + error_code=err, + msg=f"hello should reject {tid} as saslSupportedMechs", + ) + for tid, val, err in [ + ("int", 123, TYPE_MISMATCH_ERROR), + ("bool", True, TYPE_MISMATCH_ERROR), + ("array", ["admin.user"], TYPE_MISMATCH_ERROR), + ("object", {"db": "admin"}, BAD_VALUE_ERROR), + ("null", None, TYPE_MISMATCH_ERROR), + ] +] + +HELLO_ERROR_ALL_TESTS: list[ReplicationTestCase] = ( + HELLO_CASE_ERROR_TESTS + SASL_INVALID_FORMAT_TESTS + SASL_TYPE_REJECTION_TESTS +) + -@pytest.mark.parametrize("test", pytest_params(HELLO_CASE_ERROR_TESTS)) +@pytest.mark.parametrize("test", pytest_params(HELLO_ERROR_ALL_TESTS)) def test_hello_error_cases(collection, test): """Test hello command error cases.""" ctx = CommandContext.from_collection(collection) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py deleted file mode 100644 index 0cd24adf2..000000000 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_sasl_supported_mechs_error.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Tests for hello command saslSupportedMechs parameter error cases. - -Validates that hello rejects invalid format strings, non-string types, -and null for the saslSupportedMechs parameter. -""" - -from __future__ import annotations - -import pytest - -from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( - CommandContext, -) -from documentdb_tests.compatibility.tests.system.replication.utils.replication_test_case import ( # noqa: E501 - ReplicationTestCase, -) -from documentdb_tests.framework.assertions import assertResult -from documentdb_tests.framework.error_codes import BAD_VALUE_ERROR, TYPE_MISMATCH_ERROR -from documentdb_tests.framework.executor import execute_command -from documentdb_tests.framework.parametrize import pytest_params - -# Property [saslSupportedMechs Invalid Format]: hello rejects strings -# that do not follow the "db.user" format. -SASL_INVALID_FORMAT_TESTS: list[ReplicationTestCase] = [ - ReplicationTestCase( - "sasl_no_dot_separator", - command=lambda ctx: { - "hello": 1, - "saslSupportedMechs": "noDotSeparator", - }, - use_admin=False, - error_code=BAD_VALUE_ERROR, - msg="hello should reject saslSupportedMechs without db.user format", - ), - ReplicationTestCase( - "sasl_empty_string", - command=lambda ctx: {"hello": 1, "saslSupportedMechs": ""}, - use_admin=False, - error_code=BAD_VALUE_ERROR, - msg="hello should reject empty string saslSupportedMechs", - ), -] - -# Property [saslSupportedMechs Type Rejection]: hello rejects non-string -# types for saslSupportedMechs. -SASL_TYPE_REJECTION_TESTS: list[ReplicationTestCase] = [ - ReplicationTestCase( - f"sasl_type_{tid}", - command=lambda ctx, v=val: {"hello": 1, "saslSupportedMechs": v}, - use_admin=False, - error_code=err, - msg=f"hello should reject {tid} as saslSupportedMechs", - ) - for tid, val, err in [ - ("int", 123, TYPE_MISMATCH_ERROR), - ("bool", True, TYPE_MISMATCH_ERROR), - ("array", ["admin.user"], TYPE_MISMATCH_ERROR), - ("object", {"db": "admin"}, BAD_VALUE_ERROR), - ("null", None, TYPE_MISMATCH_ERROR), - ] -] - -HELLO_SASL_ERROR_TESTS: list[ReplicationTestCase] = ( - SASL_INVALID_FORMAT_TESTS + SASL_TYPE_REJECTION_TESTS -) - - -@pytest.mark.parametrize("test", pytest_params(HELLO_SASL_ERROR_TESTS)) -def test_hello_sasl_supported_mechs_error(collection, test): - """Test hello saslSupportedMechs parameter error cases.""" - 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, - ) From 2f146d651c00560f94df4f90d8d99db58f2d4cc1 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 16:11:18 -0700 Subject: [PATCH 11/12] convert test_hello_response_wire_version_max_gte_min to use ReplicationTestCase Signed-off-by: Alina (Xi) Li --- .../hello/test_hello_response_structure.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py index 33399d376..a3d4eec53 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_response_structure.py @@ -122,6 +122,15 @@ expected={"maxWireVersion": Gte(21)}, msg="hello should return maxWireVersion >= 21 for MongoDB 7.0+", ), + ReplicationTestCase( + "wire_max_gte_min", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: { + "maxWireVersion": Gte(result.get("minWireVersion", 0)), + }, + msg="hello should return maxWireVersion >= minWireVersion", + ), ] HELLO_RESPONSE_ALL_TESTS: list[ReplicationTestCase] = ( @@ -143,18 +152,6 @@ def test_hello_response_structure(collection, test): ) -# Property [Wire Version Consistency]: maxWireVersion >= minWireVersion. -def test_hello_response_wire_version_max_gte_min(collection): - """Test hello maxWireVersion >= minWireVersion.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={"maxWireVersion": Gte(result["minWireVersion"])}, - msg="hello should return maxWireVersion >= minWireVersion", - raw_res=True, - ) - - # Property [topologyVersion Stability]: processId remains stable across calls. def test_hello_response_topologyVersion_processId_stable(collection): """Test hello topologyVersion.processId is stable across calls.""" From f6673c05fd02f2a13ef0a8ed86b51fbeccf43766 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 19 Jun 2026 16:14:49 -0700 Subject: [PATCH 12/12] convert to ReplicationTestCase Signed-off-by: Alina (Xi) Li --- .../commands/hello/test_hello_replica_set.py | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py index e7899b01c..692729df3 100644 --- a/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py +++ b/documentdb_tests/compatibility/tests/system/replication/commands/hello/test_hello_replica_set.py @@ -7,6 +7,8 @@ from __future__ import annotations +from datetime import datetime, timezone + import pytest from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( @@ -145,7 +147,33 @@ ), ] -HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = RS_REQUIRED_FIELD_TESTS + RS_PRIMARY_INVARIANT_TESTS +# Property [lastWrite Date Ordering]: lastWrite dates have expected ordering. +RS_LASTWRITE_DATE_TESTS: list[ReplicationTestCase] = [ + ReplicationTestCase( + "rs_lastWriteDate_not_future", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: { + "lastWrite": {"lastWriteDate": Lte(datetime.now(tz=timezone.utc))}, + }, + msg="hello lastWrite.lastWriteDate should be <= current time", + ), + ReplicationTestCase( + "rs_majorityWriteDate_lte_lastWriteDate", + command=lambda ctx: {"hello": 1}, + use_admin=False, + expected=lambda ctx, result: { + "lastWrite": { + "majorityWriteDate": Lte(result["lastWrite"]["lastWriteDate"]), + }, + }, + msg="hello majorityWriteDate should be <= lastWriteDate", + ), +] + +HELLO_RS_ALL_TESTS: list[ReplicationTestCase] = ( + RS_REQUIRED_FIELD_TESTS + RS_PRIMARY_INVARIANT_TESTS + RS_LASTWRITE_DATE_TESTS +) @pytest.mark.parametrize("test", pytest_params(HELLO_RS_ALL_TESTS)) @@ -160,33 +188,3 @@ def test_hello_replica_set(collection, test): msg=test.msg, raw_res=True, ) - - -# Property [lastWrite Date Ordering]: lastWrite dates have expected ordering. -def test_hello_rs_lastWriteDate_not_future(collection): - """Test hello lastWrite.lastWriteDate is not in the future.""" - from datetime import datetime, timezone - - result = execute_command(collection, {"hello": 1}) - now = datetime.now(tz=timezone.utc) - assertResult( - result, - expected={"lastWrite": {"lastWriteDate": Lte(now)}}, - msg="hello lastWrite.lastWriteDate should be <= current time", - raw_res=True, - ) - - -def test_hello_rs_majorityWriteDate_lte_lastWriteDate(collection): - """Test hello lastWrite.majorityWriteDate <= lastWrite.lastWriteDate.""" - result = execute_command(collection, {"hello": 1}) - assertResult( - result, - expected={ - "lastWrite": { - "majorityWriteDate": Lte(result["lastWrite"]["lastWriteDate"]), - }, - }, - msg="hello majorityWriteDate should be <= lastWriteDate", - raw_res=True, - )