From f122485975dea155d3224321db93ba59d4685981 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 16 Jun 2026 13:56:54 -0700 Subject: [PATCH 01/15] inital generated tests Signed-off-by: Alina (Xi) Li --- .../diagnostic/commands/validate/__init__.py | 0 .../validate/test_validate_argument_type.py | 152 +++++++++++++++ .../validate/test_validate_background_type.py | 111 +++++++++++ ...test_validate_checkBSONConformance_type.py | 171 ++++++++++++++++ .../test_validate_collection_variants.py | 88 +++++++++ .../validate/test_validate_core_behavior.py | 155 +++++++++++++++ .../validate/test_validate_edge_cases.py | 153 +++++++++++++++ .../validate/test_validate_full_type.py | 169 ++++++++++++++++ .../validate/test_validate_indexes.py | 143 ++++++++++++++ .../validate/test_validate_metadata_type.py | 169 ++++++++++++++++ .../test_validate_option_combinations.py | 166 ++++++++++++++++ .../test_validate_response_structure.py | 183 ++++++++++++++++++ documentdb_tests/framework/error_codes.py | 1 + 13 files changed, 1661 insertions(+) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/__init__.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py new file mode 100644 index 000000000..aa22aa392 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -0,0 +1,152 @@ +"""Tests for validate command argument type handling. + +Validates that the validate parameter (collection name) must be a string and +rejects all other BSON types with the correct error code. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.framework.error_codes import INVALID_NAMESPACE_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 [Type Rejection]: validate rejects all non-string BSON types for the collection name. +INVALID_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "double", + command={"validate": 1.0}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject double for collection name", + ), + DiagnosticTestCase( + "int32", + command={"validate": 1}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject int32 for collection name", + ), + DiagnosticTestCase( + "int64", + command={"validate": Int64(1)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject int64 for collection name", + ), + DiagnosticTestCase( + "decimal128", + command={"validate": Decimal128("1")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Decimal128 for collection name", + ), + DiagnosticTestCase( + "bool_true", + command={"validate": True}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject bool true for collection name", + ), + DiagnosticTestCase( + "bool_false", + command={"validate": False}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject bool false for collection name", + ), + DiagnosticTestCase( + "null", + command={"validate": None}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject null for collection name", + ), + DiagnosticTestCase( + "object", + command={"validate": {"a": 1}}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject object for collection name", + ), + DiagnosticTestCase( + "array", + command={"validate": [1, 2]}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject array for collection name", + ), + DiagnosticTestCase( + "binary", + command={"validate": Binary(b"data")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Binary for collection name", + ), + DiagnosticTestCase( + "objectid", + command={"validate": ObjectId()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject ObjectId for collection name", + ), + DiagnosticTestCase( + "datetime", + command={"validate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject datetime for collection name", + ), + DiagnosticTestCase( + "regex", + command={"validate": Regex(".*")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Regex for collection name", + ), + DiagnosticTestCase( + "timestamp", + command={"validate": Timestamp(0, 0)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Timestamp for collection name", + ), + DiagnosticTestCase( + "code", + command={"validate": Code("function(){}")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject JavaScript Code for collection name", + ), + DiagnosticTestCase( + "minkey", + command={"validate": MinKey()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject MinKey for collection name", + ), + DiagnosticTestCase( + "maxkey", + command={"validate": MaxKey()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject MaxKey for collection name", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(INVALID_TYPE_TESTS)) +def test_validate_rejects_non_string_types(collection, test): + """Test that validate rejects non-string BSON types for the collection name.""" + result = execute_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) + + +# Property [String Acceptance]: validate accepts a valid string collection name. +VALID_STRING_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "valid_collection_name", + checks={"ok": Eq(1.0)}, + msg="validate should accept a valid collection name string", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(VALID_STRING_TESTS)) +def test_validate_accepts_string(collection, test): + """Test that validate accepts a valid string collection name.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py new file mode 100644 index 000000000..7a4641eca --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py @@ -0,0 +1,111 @@ +"""Tests for validate command 'background' parameter type coercion. + +Validates that the background parameter accepts all BSON types via coercion. +Note: background: true is not supported on standalone mode, so truthy values +are tested with assertFailureCode for the standalone error. +""" + +from __future__ import annotations + +import pytest +from bson import Decimal128, Int64 + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.framework.error_codes import COMMAND_NOT_SUPPORTED_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 [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. +FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_false", + checks={"ok": Eq(1.0)}, + msg="background should accept bool false", + ), + DiagnosticTestCase( + "int32_0", + checks={"ok": Eq(1.0)}, + msg="background should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_0", + checks={"ok": Eq(1.0)}, + msg="background should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_0", + checks={"ok": Eq(1.0)}, + msg="background should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_0", + checks={"ok": Eq(1.0)}, + msg="background should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + checks={"ok": Eq(1.0)}, + msg="background should accept null (treated as omitted/false)", + ), +] + +_FALSY_VALUES = { + "bool_false": False, + "int32_0": 0, + "double_0": 0.0, + "int64_0": Int64(0), + "decimal128_0": Decimal128("0"), + "null": None, +} + + +@pytest.mark.parametrize("test", pytest_params(FALSY_TYPE_TESTS)) +def test_validate_background_falsy_types(collection, test): + """Test that validate accepts falsy types for the background parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "background": _FALSY_VALUES[test.id]}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [Truthy Standalone Error]: validate rejects truthy background values on standalone mode. +TRUTHY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: true not supported on standalone", + ), + DiagnosticTestCase( + "int32_1", + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: int 1 (truthy) not supported on standalone", + ), + DiagnosticTestCase( + "string", + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: string (truthy) not supported on standalone", + ), +] + +_TRUTHY_VALUES = { + "bool_true": True, + "int32_1": 1, + "string": "true", +} + + +@pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) +def test_validate_background_truthy_standalone_error(collection, test): + """Test that background with truthy values errors on standalone mode.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "background": _TRUTHY_VALUES[test.id]}, + ) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py new file mode 100644 index 000000000..da5394070 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py @@ -0,0 +1,171 @@ +"""Tests for validate command 'checkBSONConformance' parameter type coercion. + +Validates that the checkBSONConformance parameter accepts all BSON types via +coercion. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Type Coercion]: validate accepts all BSON types for the +# checkBSONConformance parameter via coercion. +ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept bool true", + ), + DiagnosticTestCase( + "bool_false", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept bool false", + ), + DiagnosticTestCase( + "int32_1", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept int32 1 (coerces to true)", + ), + DiagnosticTestCase( + "int32_0", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_1", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept double 1.0 (coerces to true)", + ), + DiagnosticTestCase( + "double_0", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_1", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Int64(1) (coerces to true)", + ), + DiagnosticTestCase( + "int64_0", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_1", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Decimal128('1') (coerces to true)", + ), + DiagnosticTestCase( + "decimal128_0", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept null (treated as omitted/false)", + ), + DiagnosticTestCase( + "string", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept string (coerces to truthy)", + ), + DiagnosticTestCase( + "object", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept object (coerces to truthy)", + ), + DiagnosticTestCase( + "array", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept array (coerces to truthy)", + ), + DiagnosticTestCase( + "binary", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Binary (coerces to truthy)", + ), + DiagnosticTestCase( + "objectid", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept ObjectId (coerces to truthy)", + ), + DiagnosticTestCase( + "datetime", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept datetime (coerces to truthy)", + ), + DiagnosticTestCase( + "regex", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Regex (coerces to truthy)", + ), + DiagnosticTestCase( + "timestamp", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept Timestamp (coerces to truthy)", + ), + DiagnosticTestCase( + "code", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept JavaScript Code (coerces to truthy)", + ), + DiagnosticTestCase( + "minkey", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept MinKey (coerces to truthy)", + ), + DiagnosticTestCase( + "maxkey", + checks={"ok": Eq(1.0)}, + msg="checkBSONConformance should accept MaxKey (coerces to truthy)", + ), +] + +_VALUES = { + "bool_true": True, + "bool_false": False, + "int32_1": 1, + "int32_0": 0, + "double_1": 1.0, + "double_0": 0.0, + "int64_1": Int64(1), + "int64_0": Int64(0), + "decimal128_1": Decimal128("1"), + "decimal128_0": Decimal128("0"), + "null": None, + "string": "true", + "object": {}, + "array": [], + "binary": Binary(b""), + "objectid": ObjectId(), + "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), + "regex": Regex(".*"), + "timestamp": Timestamp(0, 0), + "code": Code("function(){}"), + "minkey": MinKey(), + "maxkey": MaxKey(), +} + + +@pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) +def test_validate_checkBSONConformance_accepted_types(collection, test): + """Test that validate accepts all BSON types for checkBSONConformance.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "checkBSONConformance": _VALUES[test.id]}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py new file mode 100644 index 000000000..189d11920 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py @@ -0,0 +1,88 @@ +"""Tests for validate command on different collection types. + +Validates behavior on regular, capped, view, timeseries, and clustered collections. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial +from documentdb_tests.framework.error_codes import COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR +from documentdb_tests.framework.executor import execute_command + + +def test_validate_regular_collection(collection): + """Test validate on a regular collection succeeds.""" + collection.insert_one({"_id": 1, "x": 1}) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed on a regular collection", + ) + + +def test_validate_capped_collection(database_client, collection): + """Test validate on a capped collection succeeds.""" + coll_name = f"{collection.name}_capped" + database_client.create_collection(coll_name, capped=True, size=1_048_576) + coll = database_client[coll_name] + coll.insert_one({"_id": 1, "x": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed on a capped collection", + ) + + +def test_validate_view_rejected(database_client, collection): + """Test validate on a view returns an error.""" + source_name = f"{collection.name}_view_source" + view_name = f"{collection.name}_view" + database_client.create_collection(source_name) + database_client[source_name].insert_one({"_id": 1}) + database_client.command("create", view_name, viewOn=source_name, pipeline=[]) + coll = database_client[view_name] + result = execute_command(coll, {"validate": coll.name}) + assertFailureCode( + result, + COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + msg="validate should reject views", + ) + + +def test_validate_timeseries_collection(database_client, collection): + """Test validate on a time series collection succeeds.""" + coll_name = f"{collection.name}_timeseries" + database_client.create_collection( + coll_name, + timeseries={"timeField": "ts", "metaField": "meta"}, + ) + coll = database_client[coll_name] + coll.insert_one({"ts": datetime(2024, 1, 1, tzinfo=timezone.utc), "meta": "a", "v": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate should succeed on a timeseries collection", + ) + + +def test_validate_clustered_collection(database_client, collection): + """Test validate on a clustered collection succeeds.""" + coll_name = f"{collection.name}_clustered" + database_client.command( + "create", + coll_name, + clusteredIndex={"key": {"_id": 1}, "unique": True}, + ) + coll = database_client[coll_name] + coll.insert_one({"_id": 1, "x": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate should succeed on a clustered collection", + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py new file mode 100644 index 000000000..0c6ff1d7a --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -0,0 +1,155 @@ +"""Tests for validate command core behavior. + +Validates basic functionality, counts, consistency across calls, and comment parameter. +""" + +from __future__ import annotations + +from documentdb_tests.framework.assertions import ( + assertFailureCode, + assertProperties, + assertSuccessPartial, +) +from documentdb_tests.framework.error_codes import NAMESPACE_NOT_FOUND_ERROR +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.property_checks import Eq + + +def test_validate_populated_collection(collection): + """Test validate on a populated collection returns valid: true with correct counts.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 5}, + msg="Populated collection should validate with correct nrecords", + ) + + +def test_validate_empty_collection(database_client, collection): + """Test validate on an empty collection returns nrecords: 0, valid: true.""" + coll_name = f"{collection.name}_empty" + database_client.create_collection(coll_name) + coll = database_client[coll_name] + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 0, "nIndexes": 1}, + msg="Empty collection should be valid with nrecords: 0 and nIndexes: 1", + ) + + +def test_validate_non_existent_collection(collection): + """Test validate on a non-existent collection returns NamespaceNotFound error.""" + result = execute_command(collection, {"validate": f"{collection.name}_nonexistent_xyz"}) + assertFailureCode( + result, + NAMESPACE_NOT_FOUND_ERROR, + msg="Non-existent collection should return NamespaceNotFound error", + ) + + +def test_validate_after_insert_and_delete_all(collection): + """Test validate after inserting and deleting all documents shows nrecords: 0.""" + collection.insert_many([{"_id": i} for i in range(5)]) + collection.delete_many({}) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 0}, + msg="After deleting all docs, nrecords should be 0", + ) + + +def test_validate_with_secondary_indexes(collection): + """Test validate with secondary indexes reports correct nIndexes.""" + collection.insert_many([{"_id": i, "x": i, "y": i} for i in range(5)]) + collection.create_index("x") + collection.create_index([("y", -1)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "nIndexes": 3}, + msg="nIndexes should be 3 (_id + x_1 + y_-1)", + ) + + +def test_validate_consistent_across_calls(collection): + """Test validate returns consistent results across multiple calls.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + result1 = execute_command(collection, {"validate": collection.name}) + result2 = execute_command(collection, {"validate": collection.name}) + assertProperties( + result1, + { + "nrecords": Eq(result2["nrecords"]), + "nIndexes": Eq(result2["nIndexes"]), + "valid": Eq(result2["valid"]), + }, + raw_res=True, + msg="Two consecutive validates should return identical key fields", + ) + + +def test_validate_reflects_modifications(collection): + """Test validate reflects modifications between calls.""" + collection.insert_many([{"_id": i} for i in range(3)]) + execute_command(collection, {"validate": collection.name}) + collection.insert_many([{"_id": i} for i in range(3, 8)]) + result2 = execute_command(collection, {"validate": collection.name}) + assertProperties( + result2, + {"nrecords": Eq(8)}, + raw_res=True, + msg="Second validate should reflect updated nrecords after insert", + ) + + +def test_validate_after_dropping_indexes(collection): + """Test validate after dropping secondary indexes shows nIndexes: 1.""" + collection.insert_one({"_id": 1, "x": 1}) + collection.create_index("x") + collection.drop_indexes() + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "nIndexes": 1}, + msg="After dropping secondary indexes, nIndexes should be 1 (only _id)", + ) + + +def test_validate_with_comment(collection): + """Test validate accepts the comment parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "comment": "test comment"}, + ) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate with comment parameter should succeed", + ) + + +def test_validate_with_full_true(collection): + """Test validate with full: true returns valid: true on healthy collection.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + result = execute_command(collection, {"validate": collection.name, "full": True}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate with full: true should succeed on healthy collection", + ) + + +def test_validate_metadata_true(collection): + """Test validate with metadata: true succeeds.""" + collection.insert_one({"_id": 1, "x": 1}) + collection.create_index("x") + result = execute_command(collection, {"validate": collection.name, "metadata": True}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate with metadata: true should succeed", + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py new file mode 100644 index 000000000..19bd01d80 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -0,0 +1,153 @@ +"""Tests for validate command edge cases. + +Validates behavior with collection name edge cases, document variety, and +large collections. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from bson import Binary, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.framework.executor import execute_command + + +def test_validate_long_collection_name(database_client, collection): + """Test validate with a very long collection name succeeds.""" + db_name = database_client.name + # Namespace is "db.coll" so max coll length is 255 - len(db_name) - 1. + max_coll_len = 255 - len(db_name) - 1 + coll_name = f"{collection.name}_" + "a" * (max_coll_len - len(collection.name) - 1) + coll = database_client[coll_name] + coll.insert_one({"_id": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial(result, {"ok": 1.0}, msg="Long collection name should succeed") + + +def test_validate_unicode_collection_name(database_client, collection): + """Test validate with unicode characters in collection name succeeds.""" + # U+00E9 e-acute, U+00E8 e-grave, U+00EA e-circumflex. + coll_name = f"{collection.name}_\u00e9\u00e8\u00ea" + coll = database_client[coll_name] + coll.insert_one({"_id": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial(result, {"ok": 1.0}, msg="Unicode collection name should succeed") + + +def test_validate_numeric_looking_collection_name(database_client, collection): + """Test validate with a numeric-looking collection name succeeds.""" + coll_name = f"{collection.name}_12345" + coll = database_client[coll_name] + coll.insert_one({"_id": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial(result, {"ok": 1.0}, msg="Numeric-looking collection name should succeed") + + +def test_validate_large_document_count(collection): + """Test validate with 1000 documents reports correct nrecords.""" + collection.insert_many([{"_id": i, "x": i} for i in range(1000)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 1000}, + msg="nrecords should be 1000 for 1000 inserted documents", + ) + + +def test_validate_document_with_all_bson_types(collection): + """Test validate on a collection with a document containing all BSON types.""" + collection.insert_one( + { + "_id": 1, + "double_val": 3.14, + "string_val": "hello", + "object_val": {"nested": 1}, + "array_val": [1, 2, 3], + "binary_val": Binary(b"data"), + "objectid_val": ObjectId(), + "bool_val": True, + "date_val": datetime(2024, 1, 1, tzinfo=timezone.utc), + "null_val": None, + "regex_val": Regex("test"), + "int32_val": 42, + "timestamp_val": Timestamp(1, 1), + "int64_val": Int64(123456789), + "decimal128_val": Decimal128("1.23"), + "minkey_val": MinKey(), + "maxkey_val": MaxKey(), + } + ) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="Document with all BSON types should be valid", + ) + + +def test_validate_deeply_nested_document(collection): + """Test validate on a collection with a deeply nested document.""" + doc = {"_id": 1} + nested = doc + for i in range(10): + nested[f"level_{i}"] = {} + nested = nested[f"level_{i}"] + nested["value"] = "deep" + collection.insert_one(doc) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="Deeply nested document should be valid", + ) + + +def test_validate_documents_with_arrays(collection): + """Test validate on a collection with documents containing arrays.""" + collection.insert_many( + [ + {"_id": 1, "arr": []}, + {"_id": 2, "arr": [1, 2, 3]}, + {"_id": 3, "arr": list(range(100))}, + ] + ) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 3}, + msg="Documents with arrays should be valid", + ) + + +def test_validate_documents_with_binary_data(collection): + """Test validate on a collection with documents containing Binary fields.""" + collection.insert_many( + [ + {"_id": 1, "data": Binary(b"small")}, + {"_id": 2, "data": Binary(b"\x00" * 1024)}, + ] + ) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nrecords": 2}, + msg="Documents with binary data should be valid", + ) + + +def test_validate_many_indexes(collection): + """Test validate with 5 secondary indexes reports correct nIndexes.""" + collection.insert_many([{"_id": i, "a": i, "b": i, "c": i, "d": i, "e": i} for i in range(5)]) + collection.create_index("a") + collection.create_index("b") + collection.create_index("c") + collection.create_index("d") + collection.create_index("e") + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 6}, + msg="nIndexes should be 6 (_id + 5 secondary indexes)", + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py new file mode 100644 index 000000000..55c79afb7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py @@ -0,0 +1,169 @@ +"""Tests for validate command 'full' parameter type coercion. + +Validates that the full parameter accepts all BSON types via coercion. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Type Coercion]: validate accepts all BSON types for the full parameter via coercion. +ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + checks={"ok": Eq(1.0)}, + msg="full should accept bool true", + ), + DiagnosticTestCase( + "bool_false", + checks={"ok": Eq(1.0)}, + msg="full should accept bool false", + ), + DiagnosticTestCase( + "int32_1", + checks={"ok": Eq(1.0)}, + msg="full should accept int32 1 (coerces to true)", + ), + DiagnosticTestCase( + "int32_0", + checks={"ok": Eq(1.0)}, + msg="full should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_1", + checks={"ok": Eq(1.0)}, + msg="full should accept double 1.0 (coerces to true)", + ), + DiagnosticTestCase( + "double_0", + checks={"ok": Eq(1.0)}, + msg="full should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_1", + checks={"ok": Eq(1.0)}, + msg="full should accept Int64(1) (coerces to true)", + ), + DiagnosticTestCase( + "int64_0", + checks={"ok": Eq(1.0)}, + msg="full should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_1", + checks={"ok": Eq(1.0)}, + msg="full should accept Decimal128('1') (coerces to true)", + ), + DiagnosticTestCase( + "decimal128_0", + checks={"ok": Eq(1.0)}, + msg="full should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + checks={"ok": Eq(1.0)}, + msg="full should accept null (treated as omitted/false)", + ), + DiagnosticTestCase( + "string", + checks={"ok": Eq(1.0)}, + msg="full should accept string (coerces to truthy)", + ), + DiagnosticTestCase( + "object", + checks={"ok": Eq(1.0)}, + msg="full should accept object (coerces to truthy)", + ), + DiagnosticTestCase( + "array", + checks={"ok": Eq(1.0)}, + msg="full should accept array (coerces to truthy)", + ), + DiagnosticTestCase( + "binary", + checks={"ok": Eq(1.0)}, + msg="full should accept Binary (coerces to truthy)", + ), + DiagnosticTestCase( + "objectid", + checks={"ok": Eq(1.0)}, + msg="full should accept ObjectId (coerces to truthy)", + ), + DiagnosticTestCase( + "datetime", + checks={"ok": Eq(1.0)}, + msg="full should accept datetime (coerces to truthy)", + ), + DiagnosticTestCase( + "regex", + checks={"ok": Eq(1.0)}, + msg="full should accept Regex (coerces to truthy)", + ), + DiagnosticTestCase( + "timestamp", + checks={"ok": Eq(1.0)}, + msg="full should accept Timestamp (coerces to truthy)", + ), + DiagnosticTestCase( + "code", + checks={"ok": Eq(1.0)}, + msg="full should accept JavaScript Code (coerces to truthy)", + ), + DiagnosticTestCase( + "minkey", + checks={"ok": Eq(1.0)}, + msg="full should accept MinKey (coerces to truthy)", + ), + DiagnosticTestCase( + "maxkey", + checks={"ok": Eq(1.0)}, + msg="full should accept MaxKey (coerces to truthy)", + ), +] + +_VALUES = { + "bool_true": True, + "bool_false": False, + "int32_1": 1, + "int32_0": 0, + "double_1": 1.0, + "double_0": 0.0, + "int64_1": Int64(1), + "int64_0": Int64(0), + "decimal128_1": Decimal128("1"), + "decimal128_0": Decimal128("0"), + "null": None, + "string": "true", + "object": {}, + "array": [], + "binary": Binary(b""), + "objectid": ObjectId(), + "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), + "regex": Regex(".*"), + "timestamp": Timestamp(0, 0), + "code": Code("function(){}"), + "minkey": MinKey(), + "maxkey": MaxKey(), +} + + +@pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) +def test_validate_full_accepted_types(collection, test): + """Test that validate accepts all BSON types for the full parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "full": _VALUES[test.id]}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py new file mode 100644 index 000000000..3034d3ddd --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py @@ -0,0 +1,143 @@ +"""Tests for validate command with various index types. + +Validates that validate succeeds and correctly reports on collections with +different index types including unique, sparse, TTL, text, 2dsphere, hashed, +wildcard, compound, and partial filter indexes. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.property_checks import Eq + + +def test_validate_unique_index(collection): + """Test validate with a unique index reports it in results.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + collection.create_index("x", unique=True) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 2}, + msg="validate should succeed and report unique index", + ) + + +def test_validate_sparse_index(collection): + """Test validate with a sparse index shows fewer keys than nrecords.""" + collection.insert_many( + [{"_id": i, "x": i} for i in range(5)] + + [{"_id": i} for i in range(5, 10)] # 5 docs without 'x' field + ) + collection.create_index("x", sparse=True) + result = execute_command(collection, {"validate": collection.name}) + assertProperties( + result, + {"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(10), "nIndexes": Eq(2)}, + raw_res=True, + msg="validate should succeed with sparse index", + ) + + +def test_validate_ttl_index(collection): + """Test validate with a TTL index reports it in results.""" + collection.insert_one({"_id": 1, "created": datetime(2024, 1, 1, tzinfo=timezone.utc)}) + collection.create_index("created", expireAfterSeconds=3600) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 2}, + msg="validate should succeed and report TTL index", + ) + + +def test_validate_text_index(collection): + """Test validate with a text index succeeds.""" + collection.insert_many([{"_id": i, "content": f"document text {i}"} for i in range(5)]) + collection.create_index([("content", "text")]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed with text index", + ) + + +def test_validate_2dsphere_index(collection): + """Test validate with a 2dsphere index succeeds.""" + collection.insert_one({"_id": 1, "location": {"type": "Point", "coordinates": [0.0, 0.0]}}) + collection.create_index([("location", "2dsphere")]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed with 2dsphere index", + ) + + +def test_validate_hashed_index(collection): + """Test validate with a hashed index succeeds.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + collection.create_index([("x", "hashed")]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 2}, + msg="validate should succeed with hashed index", + ) + + +def test_validate_wildcard_index(collection): + """Test validate with a wildcard index succeeds.""" + collection.insert_many([{"_id": i, "a": i, "b": str(i)} for i in range(5)]) + collection.create_index([("$**", 1)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed with wildcard index", + ) + + +def test_validate_compound_index(collection): + """Test validate with a compound index reports it in results.""" + collection.insert_many([{"_id": i, "a": i, "b": -i} for i in range(5)]) + collection.create_index([("a", 1), ("b", -1)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 2}, + msg="validate should succeed and report compound index", + ) + + +def test_validate_partial_filter_index(collection): + """Test validate with a partial filter index succeeds.""" + collection.insert_many([{"_id": i, "x": i} for i in range(10)]) + collection.create_index( + "x", + partialFilterExpression={"x": {"$gt": 4}}, + ) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 2}, + msg="validate should succeed with partial filter index", + ) + + +def test_validate_multiple_indexes(collection): + """Test validate with multiple index types reports correct nIndexes.""" + collection.insert_many([{"_id": i, "a": i, "b": str(i)} for i in range(5)]) + collection.create_index("a", unique=True) + collection.create_index("b") + collection.create_index([("a", 1), ("b", 1)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True, "nIndexes": 4}, + msg="validate should report all 4 indexes (_id + 3 secondary)", + ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py new file mode 100644 index 000000000..069bea5c5 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py @@ -0,0 +1,169 @@ +"""Tests for validate command 'metadata' parameter type coercion. + +Validates that the metadata parameter accepts all BSON types via coercion. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Type Coercion]: validate accepts all BSON types for the metadata parameter via coercion. +ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + checks={"ok": Eq(1.0)}, + msg="metadata should accept bool true", + ), + DiagnosticTestCase( + "bool_false", + checks={"ok": Eq(1.0)}, + msg="metadata should accept bool false", + ), + DiagnosticTestCase( + "int32_1", + checks={"ok": Eq(1.0)}, + msg="metadata should accept int32 1 (coerces to true)", + ), + DiagnosticTestCase( + "int32_0", + checks={"ok": Eq(1.0)}, + msg="metadata should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_1", + checks={"ok": Eq(1.0)}, + msg="metadata should accept double 1.0 (coerces to true)", + ), + DiagnosticTestCase( + "double_0", + checks={"ok": Eq(1.0)}, + msg="metadata should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_1", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Int64(1) (coerces to true)", + ), + DiagnosticTestCase( + "int64_0", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_1", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Decimal128('1') (coerces to true)", + ), + DiagnosticTestCase( + "decimal128_0", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + checks={"ok": Eq(1.0)}, + msg="metadata should accept null (treated as omitted/false)", + ), + DiagnosticTestCase( + "string", + checks={"ok": Eq(1.0)}, + msg="metadata should accept string (coerces to truthy)", + ), + DiagnosticTestCase( + "object", + checks={"ok": Eq(1.0)}, + msg="metadata should accept object (coerces to truthy)", + ), + DiagnosticTestCase( + "array", + checks={"ok": Eq(1.0)}, + msg="metadata should accept array (coerces to truthy)", + ), + DiagnosticTestCase( + "binary", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Binary (coerces to truthy)", + ), + DiagnosticTestCase( + "objectid", + checks={"ok": Eq(1.0)}, + msg="metadata should accept ObjectId (coerces to truthy)", + ), + DiagnosticTestCase( + "datetime", + checks={"ok": Eq(1.0)}, + msg="metadata should accept datetime (coerces to truthy)", + ), + DiagnosticTestCase( + "regex", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Regex (coerces to truthy)", + ), + DiagnosticTestCase( + "timestamp", + checks={"ok": Eq(1.0)}, + msg="metadata should accept Timestamp (coerces to truthy)", + ), + DiagnosticTestCase( + "code", + checks={"ok": Eq(1.0)}, + msg="metadata should accept JavaScript Code (coerces to truthy)", + ), + DiagnosticTestCase( + "minkey", + checks={"ok": Eq(1.0)}, + msg="metadata should accept MinKey (coerces to truthy)", + ), + DiagnosticTestCase( + "maxkey", + checks={"ok": Eq(1.0)}, + msg="metadata should accept MaxKey (coerces to truthy)", + ), +] + +_VALUES = { + "bool_true": True, + "bool_false": False, + "int32_1": 1, + "int32_0": 0, + "double_1": 1.0, + "double_0": 0.0, + "int64_1": Int64(1), + "int64_0": Int64(0), + "decimal128_1": Decimal128("1"), + "decimal128_0": Decimal128("0"), + "null": None, + "string": "true", + "object": {}, + "array": [], + "binary": Binary(b""), + "objectid": ObjectId(), + "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), + "regex": Regex(".*"), + "timestamp": Timestamp(0, 0), + "code": Code("function(){}"), + "minkey": MinKey(), + "maxkey": MaxKey(), +} + + +@pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) +def test_validate_metadata_accepted_types(collection, test): + """Test that validate accepts all BSON types for the metadata parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, "metadata": _VALUES[test.id]}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py new file mode 100644 index 000000000..128188a81 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py @@ -0,0 +1,166 @@ +"""Tests for validate command option combinations and error conditions. + +Validates valid and invalid option combinations, repair/fixMultikey specifics, +and unrecognized field handling. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.framework.error_codes import INVALID_OPTIONS_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 [Valid Combinations]: validate succeeds with valid option combinations. +VALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "minimal_command", + checks={"ok": Eq(1.0)}, + msg="Minimal validate command should succeed", + ), + DiagnosticTestCase( + "all_defaults_explicit", + checks={"ok": Eq(1.0)}, + msg="All options set to false explicitly should succeed", + ), + DiagnosticTestCase( + "full_true", + checks={"ok": Eq(1.0)}, + msg="validate with full: true should succeed", + ), + DiagnosticTestCase( + "checkBSONConformance_true", + checks={"ok": Eq(1.0)}, + msg="validate with checkBSONConformance: true should succeed", + ), + DiagnosticTestCase( + "full_and_checkBSONConformance", + checks={"ok": Eq(1.0)}, + msg="validate with full: true and checkBSONConformance: true should succeed", + ), + DiagnosticTestCase( + "metadata_true", + checks={"ok": Eq(1.0)}, + msg="validate with metadata: true should succeed", + ), + DiagnosticTestCase( + "fixMultikey_true_alone", + checks={"ok": Eq(1.0)}, + msg="validate with fixMultikey: true alone should succeed", + ), + DiagnosticTestCase( + "repair_true_alone", + checks={"ok": Eq(1.0)}, + msg="validate with repair: true alone should succeed", + ), + DiagnosticTestCase( + "repair_true_with_fixMultikey", + checks={"ok": Eq(1.0)}, + msg="validate with repair: true and fixMultikey: true should succeed", + ), + DiagnosticTestCase( + "background_false", + checks={"ok": Eq(1.0)}, + msg="validate with background: false should succeed", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(VALID_COMBINATION_TESTS)) +def test_validate_valid_option_combinations(collection, test): + """Test that validate succeeds with valid option combinations.""" + collection.insert_one({"_id": 1}) + commands = { + "minimal_command": {"validate": collection.name}, + "all_defaults_explicit": { + "validate": collection.name, + "full": False, + "repair": False, + "metadata": False, + "checkBSONConformance": False, + }, + "full_true": {"validate": collection.name, "full": True}, + "checkBSONConformance_true": { + "validate": collection.name, + "checkBSONConformance": True, + }, + "full_and_checkBSONConformance": { + "validate": collection.name, + "full": True, + "checkBSONConformance": True, + }, + "metadata_true": {"validate": collection.name, "metadata": True}, + "fixMultikey_true_alone": {"validate": collection.name, "fixMultikey": True}, + "repair_true_alone": {"validate": collection.name, "repair": True}, + "repair_true_with_fixMultikey": { + "validate": collection.name, + "repair": True, + "fixMultikey": True, + }, + "background_false": {"validate": collection.name, "background": False}, + } + result = execute_command(collection, commands[test.id]) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [Invalid Combinations]: validate rejects incompatible option combinations. +INVALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "metadata_with_full", + error_code=INVALID_OPTIONS_ERROR, + msg="metadata: true with full: true should error", + ), + DiagnosticTestCase( + "metadata_with_repair", + error_code=INVALID_OPTIONS_ERROR, + msg="metadata: true with repair: true should error", + ), + DiagnosticTestCase( + "metadata_with_checkBSONConformance", + error_code=INVALID_OPTIONS_ERROR, + msg="metadata: true with checkBSONConformance: true should error", + ), + DiagnosticTestCase( + "checkBSONConformance_with_repair", + error_code=INVALID_OPTIONS_ERROR, + msg="checkBSONConformance: true with repair: true should error", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(INVALID_COMBINATION_TESTS)) +def test_validate_invalid_option_combinations(collection, test): + """Test that validate errors on invalid option combinations.""" + collection.insert_one({"_id": 1}) + commands = { + "metadata_with_full": { + "validate": collection.name, + "metadata": True, + "full": True, + }, + "metadata_with_repair": { + "validate": collection.name, + "metadata": True, + "repair": True, + "fixMultikey": True, + }, + "metadata_with_checkBSONConformance": { + "validate": collection.name, + "metadata": True, + "checkBSONConformance": True, + }, + "checkBSONConformance_with_repair": { + "validate": collection.name, + "checkBSONConformance": True, + "repair": True, + "fixMultikey": True, + }, + } + result = execute_command(collection, commands[test.id]) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py new file mode 100644 index 000000000..6bc64e140 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -0,0 +1,183 @@ +"""Tests for validate command response structure. + +Validates presence, types, and values of response fields for healthy collections. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq, Exists, Gte, IsType + +# Property [Response Structure]: validate returns expected field types and values for +# healthy collections. +PROPERTY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "ok_is_1", + checks={"ok": Eq(1.0)}, + msg="'ok' field should be 1.0", + ), + DiagnosticTestCase( + "ns_is_string", + checks={"ns": IsType("string")}, + msg="'ns' field should be a string", + ), + DiagnosticTestCase( + "nInvalidDocuments_is_int", + checks={"nInvalidDocuments": IsType("int")}, + msg="'nInvalidDocuments' field should be an int", + ), + DiagnosticTestCase( + "nNonCompliantDocuments_is_int", + checks={"nNonCompliantDocuments": IsType("int")}, + msg="'nNonCompliantDocuments' field should be an int", + ), + DiagnosticTestCase( + "nrecords_is_int", + checks={"nrecords": IsType("int")}, + msg="'nrecords' field should be an int", + ), + DiagnosticTestCase( + "nIndexes_is_int", + checks={"nIndexes": IsType("int")}, + msg="'nIndexes' field should be an int", + ), + DiagnosticTestCase( + "keysPerIndex_is_object", + checks={"keysPerIndex": IsType("object")}, + msg="'keysPerIndex' field should be an object", + ), + DiagnosticTestCase( + "indexDetails_is_object", + checks={"indexDetails": IsType("object")}, + msg="'indexDetails' field should be an object", + ), + DiagnosticTestCase( + "valid_is_bool", + checks={"valid": IsType("bool")}, + msg="'valid' field should be a bool", + ), + DiagnosticTestCase( + "repaired_is_bool", + checks={"repaired": IsType("bool")}, + msg="'repaired' field should be a bool", + ), + DiagnosticTestCase( + "warnings_is_array", + checks={"warnings": IsType("array")}, + msg="'warnings' field should be an array", + ), + DiagnosticTestCase( + "errors_is_array", + checks={"errors": IsType("array")}, + msg="'errors' field should be an array", + ), + DiagnosticTestCase( + "extraIndexEntries_is_array", + checks={"extraIndexEntries": IsType("array")}, + msg="'extraIndexEntries' field should be an array", + ), + DiagnosticTestCase( + "missingIndexEntries_is_array", + checks={"missingIndexEntries": IsType("array")}, + msg="'missingIndexEntries' field should be an array", + ), + DiagnosticTestCase( + "corruptRecords_is_array", + checks={"corruptRecords": IsType("array")}, + msg="'corruptRecords' field should be an array", + ), + DiagnosticTestCase( + "uuid_exists", + checks={"uuid": Exists()}, + msg="'uuid' field should exist (since 6.2)", + ), + DiagnosticTestCase( + "nInvalidDocuments_zero_healthy", + checks={"nInvalidDocuments": Eq(0)}, + msg="'nInvalidDocuments' should be 0 for a healthy collection", + ), + DiagnosticTestCase( + "nNonCompliantDocuments_zero_healthy", + checks={"nNonCompliantDocuments": Eq(0)}, + msg="'nNonCompliantDocuments' should be 0 for a healthy collection", + ), + DiagnosticTestCase( + "valid_true_healthy", + checks={"valid": Eq(True)}, + msg="'valid' should be true for a healthy collection", + ), + DiagnosticTestCase( + "repaired_false_no_repair", + checks={"repaired": Eq(False)}, + msg="'repaired' should be false when no repair requested", + ), + DiagnosticTestCase( + "warnings_empty_healthy", + checks={"warnings": Eq([])}, + msg="'warnings' should be empty for a healthy collection", + ), + DiagnosticTestCase( + "errors_empty_healthy", + checks={"errors": Eq([])}, + msg="'errors' should be empty for a healthy collection", + ), + DiagnosticTestCase( + "extraIndexEntries_empty_healthy", + checks={"extraIndexEntries": Eq([])}, + msg="'extraIndexEntries' should be empty for a healthy collection", + ), + DiagnosticTestCase( + "missingIndexEntries_empty_healthy", + checks={"missingIndexEntries": Eq([])}, + msg="'missingIndexEntries' should be empty for a healthy collection", + ), + DiagnosticTestCase( + "corruptRecords_empty_healthy", + checks={"corruptRecords": Eq([])}, + msg="'corruptRecords' should be empty for a healthy collection", + ), + DiagnosticTestCase( + "nIndexes_gte_1", + checks={"nIndexes": Gte(1)}, + msg="'nIndexes' should be >= 1 (at least _id index)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(PROPERTY_TESTS)) +def test_validate_response_properties(collection, test): + """Test validate response fields have expected types and values.""" + collection.insert_many([{"_id": i, "x": i} for i in range(5)]) + result = execute_command(collection, {"validate": collection.name}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +def test_validate_ns_matches_namespace(collection): + """Test validate ns field matches the actual database.collection namespace.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name}) + expected_ns = f"{collection.database.name}.{collection.name}" + assertSuccessPartial(result, {"ns": expected_ns}, msg="ns should match actual namespace") + + +def test_validate_nrecords_matches_count(collection): + """Test validate nrecords matches the number of inserted documents.""" + collection.insert_many([{"_id": i} for i in range(10)]) + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial(result, {"nrecords": 10}, msg="nrecords should match document count") + + +def test_validate_nIndexes_with_secondary(collection): + """Test validate nIndexes includes secondary indexes.""" + collection.insert_one({"_id": 1, "x": 1, "y": 1}) + collection.create_index("x") + collection.create_index("y") + result = execute_command(collection, {"validate": collection.name}) + assertSuccessPartial(result, {"nIndexes": 3}, msg="nIndexes should be 3 (_id + x + y)") diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index 423b3fe65..90d6e54eb 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -35,6 +35,7 @@ INDEX_OPTIONS_CONFLICT_ERROR = 85 INDEX_KEY_SPECS_CONFLICT_ERROR = 86 OPERATION_FAILED_ERROR = 96 +COMMAND_NOT_SUPPORTED_ERROR = 115 DOCUMENT_VALIDATION_FAILURE_ERROR = 121 NOT_A_REPLICA_SET_ERROR = 123 CAPPED_POSITION_LOST_ERROR = 136 From e34570843085608a698affe172ab1c1a21aafcba Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 16 Jun 2026 15:08:53 -0700 Subject: [PATCH 02/15] update with style guide Signed-off-by: Alina (Xi) Li --- .../commands/validate/test_validate_argument_type.py | 12 ++++++++++++ .../commands/validate/test_validate_edge_cases.py | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py index aa22aa392..af031b7af 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -70,12 +70,24 @@ error_code=INVALID_NAMESPACE_ERROR, msg="validate should reject object for collection name", ), + DiagnosticTestCase( + "empty_object", + command={"validate": {}}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty object for collection name", + ), DiagnosticTestCase( "array", command={"validate": [1, 2]}, error_code=INVALID_NAMESPACE_ERROR, msg="validate should reject array for collection name", ), + DiagnosticTestCase( + "empty_array", + command={"validate": []}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty array for collection name", + ), DiagnosticTestCase( "binary", command={"validate": Binary(b"data")}, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index 19bd01d80..b43720b08 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -46,13 +46,13 @@ def test_validate_numeric_looking_collection_name(database_client, collection): def test_validate_large_document_count(collection): - """Test validate with 1000 documents reports correct nrecords.""" - collection.insert_many([{"_id": i, "x": i} for i in range(1000)]) + """Test validate with 1_000 documents reports correct nrecords.""" + collection.insert_many([{"_id": i, "x": i} for i in range(1_000)]) result = execute_command(collection, {"validate": collection.name}) assertSuccessPartial( result, - {"ok": 1.0, "valid": True, "nrecords": 1000}, - msg="nrecords should be 1000 for 1000 inserted documents", + {"ok": 1.0, "valid": True, "nrecords": 1_000}, + msg="validate should report correct nrecords for large document count", ) @@ -73,7 +73,7 @@ def test_validate_document_with_all_bson_types(collection): "regex_val": Regex("test"), "int32_val": 42, "timestamp_val": Timestamp(1, 1), - "int64_val": Int64(123456789), + "int64_val": Int64(123_456_789), "decimal128_val": Decimal128("1.23"), "minkey_val": MinKey(), "maxkey_val": MaxKey(), @@ -126,7 +126,7 @@ def test_validate_documents_with_binary_data(collection): collection.insert_many( [ {"_id": 1, "data": Binary(b"small")}, - {"_id": 2, "data": Binary(b"\x00" * 1024)}, + {"_id": 2, "data": Binary(b"\x00" * 1_024)}, ] ) result = execute_command(collection, {"validate": collection.name}) From 01c02a423a025787e12628c5d42e80a4bf4319ce Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Jun 2026 14:51:01 -0700 Subject: [PATCH 03/15] remove duplicates and formats Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_argument_type.py | 21 +---- .../validate/test_validate_background_type.py | 28 +++---- ...test_validate_checkBSONConformance_type.py | 49 ++++++------ .../test_validate_collection_variants.py | 11 --- .../validate/test_validate_core_behavior.py | 36 --------- .../validate/test_validate_full_type.py | 49 ++++++------ .../validate/test_validate_metadata_type.py | 49 ++++++------ .../test_validate_option_combinations.py | 79 ++++--------------- 8 files changed, 95 insertions(+), 227 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py index af031b7af..c32867d03 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -14,11 +14,10 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.framework.assertions import assertFailureCode from documentdb_tests.framework.error_codes import INVALID_NAMESPACE_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 [Type Rejection]: validate rejects all non-string BSON types for the collection name. INVALID_TYPE_TESTS: list[DiagnosticTestCase] = [ @@ -144,21 +143,3 @@ def test_validate_rejects_non_string_types(collection, test): """Test that validate rejects non-string BSON types for the collection name.""" result = execute_command(collection, test.command) assertFailureCode(result, test.error_code, msg=test.msg) - - -# Property [String Acceptance]: validate accepts a valid string collection name. -VALID_STRING_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "valid_collection_name", - checks={"ok": Eq(1.0)}, - msg="validate should accept a valid collection name string", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(VALID_STRING_TESTS)) -def test_validate_accepts_string(collection, test): - """Test that validate accepts a valid string collection name.""" - collection.insert_one({"_id": 1}) - result = execute_command(collection, {"validate": collection.name}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py index 7a4641eca..cde1d55f2 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py @@ -23,45 +23,42 @@ FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_false", + command={"background": False}, checks={"ok": Eq(1.0)}, msg="background should accept bool false", ), DiagnosticTestCase( "int32_0", + command={"background": 0}, checks={"ok": Eq(1.0)}, msg="background should accept int32 0 (coerces to false)", ), DiagnosticTestCase( "double_0", + command={"background": 0.0}, checks={"ok": Eq(1.0)}, msg="background should accept double 0.0 (coerces to false)", ), DiagnosticTestCase( "int64_0", + command={"background": Int64(0)}, checks={"ok": Eq(1.0)}, msg="background should accept Int64(0) (coerces to false)", ), DiagnosticTestCase( "decimal128_0", + command={"background": Decimal128("0")}, checks={"ok": Eq(1.0)}, msg="background should accept Decimal128('0') (coerces to false)", ), DiagnosticTestCase( "null", + command={"background": None}, checks={"ok": Eq(1.0)}, msg="background should accept null (treated as omitted/false)", ), ] -_FALSY_VALUES = { - "bool_false": False, - "int32_0": 0, - "double_0": 0.0, - "int64_0": Int64(0), - "decimal128_0": Decimal128("0"), - "null": None, -} - @pytest.mark.parametrize("test", pytest_params(FALSY_TYPE_TESTS)) def test_validate_background_falsy_types(collection, test): @@ -69,7 +66,7 @@ def test_validate_background_falsy_types(collection, test): collection.insert_one({"_id": 1}) result = execute_command( collection, - {"validate": collection.name, "background": _FALSY_VALUES[test.id]}, + {"validate": collection.name, **test.command}, ) assertProperties(result, test.checks, msg=test.msg, raw_res=True) @@ -78,27 +75,24 @@ def test_validate_background_falsy_types(collection, test): TRUTHY_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", + command={"background": True}, error_code=COMMAND_NOT_SUPPORTED_ERROR, msg="background: true not supported on standalone", ), DiagnosticTestCase( "int32_1", + command={"background": 1}, error_code=COMMAND_NOT_SUPPORTED_ERROR, msg="background: int 1 (truthy) not supported on standalone", ), DiagnosticTestCase( "string", + command={"background": "true"}, error_code=COMMAND_NOT_SUPPORTED_ERROR, msg="background: string (truthy) not supported on standalone", ), ] -_TRUTHY_VALUES = { - "bool_true": True, - "int32_1": 1, - "string": "true", -} - @pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) def test_validate_background_truthy_standalone_error(collection, test): @@ -106,6 +100,6 @@ def test_validate_background_truthy_standalone_error(collection, test): collection.insert_one({"_id": 1}) result = execute_command( collection, - {"validate": collection.name, "background": _TRUTHY_VALUES[test.id]}, + {"validate": collection.name, **test.command}, ) assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py index da5394070..addedec24 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py @@ -24,141 +24,138 @@ ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", + command={"checkBSONConformance": True}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept bool true", ), DiagnosticTestCase( "bool_false", + command={"checkBSONConformance": False}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept bool false", ), DiagnosticTestCase( "int32_1", + command={"checkBSONConformance": 1}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept int32 1 (coerces to true)", ), DiagnosticTestCase( "int32_0", + command={"checkBSONConformance": 0}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept int32 0 (coerces to false)", ), DiagnosticTestCase( "double_1", + command={"checkBSONConformance": 1.0}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept double 1.0 (coerces to true)", ), DiagnosticTestCase( "double_0", + command={"checkBSONConformance": 0.0}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept double 0.0 (coerces to false)", ), DiagnosticTestCase( "int64_1", + command={"checkBSONConformance": Int64(1)}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Int64(1) (coerces to true)", ), DiagnosticTestCase( "int64_0", + command={"checkBSONConformance": Int64(0)}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Int64(0) (coerces to false)", ), DiagnosticTestCase( "decimal128_1", + command={"checkBSONConformance": Decimal128("1")}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Decimal128('1') (coerces to true)", ), DiagnosticTestCase( "decimal128_0", + command={"checkBSONConformance": Decimal128("0")}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Decimal128('0') (coerces to false)", ), DiagnosticTestCase( "null", + command={"checkBSONConformance": None}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept null (treated as omitted/false)", ), DiagnosticTestCase( "string", + command={"checkBSONConformance": "true"}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept string (coerces to truthy)", ), DiagnosticTestCase( "object", + command={"checkBSONConformance": {}}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept object (coerces to truthy)", ), DiagnosticTestCase( "array", + command={"checkBSONConformance": []}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept array (coerces to truthy)", ), DiagnosticTestCase( "binary", + command={"checkBSONConformance": Binary(b"")}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Binary (coerces to truthy)", ), DiagnosticTestCase( "objectid", + command={"checkBSONConformance": ObjectId()}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept ObjectId (coerces to truthy)", ), DiagnosticTestCase( "datetime", + command={"checkBSONConformance": datetime(2024, 1, 1, tzinfo=timezone.utc)}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept datetime (coerces to truthy)", ), DiagnosticTestCase( "regex", + command={"checkBSONConformance": Regex(".*")}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Regex (coerces to truthy)", ), DiagnosticTestCase( "timestamp", + command={"checkBSONConformance": Timestamp(0, 0)}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept Timestamp (coerces to truthy)", ), DiagnosticTestCase( "code", + command={"checkBSONConformance": Code("function(){}")}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept JavaScript Code (coerces to truthy)", ), DiagnosticTestCase( "minkey", + command={"checkBSONConformance": MinKey()}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept MinKey (coerces to truthy)", ), DiagnosticTestCase( "maxkey", + command={"checkBSONConformance": MaxKey()}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept MaxKey (coerces to truthy)", ), ] -_VALUES = { - "bool_true": True, - "bool_false": False, - "int32_1": 1, - "int32_0": 0, - "double_1": 1.0, - "double_0": 0.0, - "int64_1": Int64(1), - "int64_0": Int64(0), - "decimal128_1": Decimal128("1"), - "decimal128_0": Decimal128("0"), - "null": None, - "string": "true", - "object": {}, - "array": [], - "binary": Binary(b""), - "objectid": ObjectId(), - "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), - "regex": Regex(".*"), - "timestamp": Timestamp(0, 0), - "code": Code("function(){}"), - "minkey": MinKey(), - "maxkey": MaxKey(), -} - @pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) def test_validate_checkBSONConformance_accepted_types(collection, test): @@ -166,6 +163,6 @@ def test_validate_checkBSONConformance_accepted_types(collection, test): collection.insert_one({"_id": 1}) result = execute_command( collection, - {"validate": collection.name, "checkBSONConformance": _VALUES[test.id]}, + {"validate": collection.name, **test.command}, ) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py index 189d11920..5c80113a9 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py @@ -12,17 +12,6 @@ from documentdb_tests.framework.executor import execute_command -def test_validate_regular_collection(collection): - """Test validate on a regular collection succeeds.""" - collection.insert_one({"_id": 1, "x": 1}) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, - msg="validate should succeed on a regular collection", - ) - - def test_validate_capped_collection(database_client, collection): """Test validate on a capped collection succeeds.""" coll_name = f"{collection.name}_capped" diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 0c6ff1d7a..0d7cb78ab 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -61,19 +61,6 @@ def test_validate_after_insert_and_delete_all(collection): ) -def test_validate_with_secondary_indexes(collection): - """Test validate with secondary indexes reports correct nIndexes.""" - collection.insert_many([{"_id": i, "x": i, "y": i} for i in range(5)]) - collection.create_index("x") - collection.create_index([("y", -1)]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "nIndexes": 3}, - msg="nIndexes should be 3 (_id + x_1 + y_-1)", - ) - - def test_validate_consistent_across_calls(collection): """Test validate returns consistent results across multiple calls.""" collection.insert_many([{"_id": i, "x": i} for i in range(5)]) @@ -130,26 +117,3 @@ def test_validate_with_comment(collection): {"ok": 1.0}, msg="validate with comment parameter should succeed", ) - - -def test_validate_with_full_true(collection): - """Test validate with full: true returns valid: true on healthy collection.""" - collection.insert_many([{"_id": i, "x": i} for i in range(5)]) - result = execute_command(collection, {"validate": collection.name, "full": True}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, - msg="validate with full: true should succeed on healthy collection", - ) - - -def test_validate_metadata_true(collection): - """Test validate with metadata: true succeeds.""" - collection.insert_one({"_id": 1, "x": 1}) - collection.create_index("x") - result = execute_command(collection, {"validate": collection.name, "metadata": True}) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="validate with metadata: true should succeed", - ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py index 55c79afb7..b8509ae0b 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py @@ -22,141 +22,138 @@ ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", + command={"full": True}, checks={"ok": Eq(1.0)}, msg="full should accept bool true", ), DiagnosticTestCase( "bool_false", + command={"full": False}, checks={"ok": Eq(1.0)}, msg="full should accept bool false", ), DiagnosticTestCase( "int32_1", + command={"full": 1}, checks={"ok": Eq(1.0)}, msg="full should accept int32 1 (coerces to true)", ), DiagnosticTestCase( "int32_0", + command={"full": 0}, checks={"ok": Eq(1.0)}, msg="full should accept int32 0 (coerces to false)", ), DiagnosticTestCase( "double_1", + command={"full": 1.0}, checks={"ok": Eq(1.0)}, msg="full should accept double 1.0 (coerces to true)", ), DiagnosticTestCase( "double_0", + command={"full": 0.0}, checks={"ok": Eq(1.0)}, msg="full should accept double 0.0 (coerces to false)", ), DiagnosticTestCase( "int64_1", + command={"full": Int64(1)}, checks={"ok": Eq(1.0)}, msg="full should accept Int64(1) (coerces to true)", ), DiagnosticTestCase( "int64_0", + command={"full": Int64(0)}, checks={"ok": Eq(1.0)}, msg="full should accept Int64(0) (coerces to false)", ), DiagnosticTestCase( "decimal128_1", + command={"full": Decimal128("1")}, checks={"ok": Eq(1.0)}, msg="full should accept Decimal128('1') (coerces to true)", ), DiagnosticTestCase( "decimal128_0", + command={"full": Decimal128("0")}, checks={"ok": Eq(1.0)}, msg="full should accept Decimal128('0') (coerces to false)", ), DiagnosticTestCase( "null", + command={"full": None}, checks={"ok": Eq(1.0)}, msg="full should accept null (treated as omitted/false)", ), DiagnosticTestCase( "string", + command={"full": "true"}, checks={"ok": Eq(1.0)}, msg="full should accept string (coerces to truthy)", ), DiagnosticTestCase( "object", + command={"full": {}}, checks={"ok": Eq(1.0)}, msg="full should accept object (coerces to truthy)", ), DiagnosticTestCase( "array", + command={"full": []}, checks={"ok": Eq(1.0)}, msg="full should accept array (coerces to truthy)", ), DiagnosticTestCase( "binary", + command={"full": Binary(b"")}, checks={"ok": Eq(1.0)}, msg="full should accept Binary (coerces to truthy)", ), DiagnosticTestCase( "objectid", + command={"full": ObjectId()}, checks={"ok": Eq(1.0)}, msg="full should accept ObjectId (coerces to truthy)", ), DiagnosticTestCase( "datetime", + command={"full": datetime(2024, 1, 1, tzinfo=timezone.utc)}, checks={"ok": Eq(1.0)}, msg="full should accept datetime (coerces to truthy)", ), DiagnosticTestCase( "regex", + command={"full": Regex(".*")}, checks={"ok": Eq(1.0)}, msg="full should accept Regex (coerces to truthy)", ), DiagnosticTestCase( "timestamp", + command={"full": Timestamp(0, 0)}, checks={"ok": Eq(1.0)}, msg="full should accept Timestamp (coerces to truthy)", ), DiagnosticTestCase( "code", + command={"full": Code("function(){}")}, checks={"ok": Eq(1.0)}, msg="full should accept JavaScript Code (coerces to truthy)", ), DiagnosticTestCase( "minkey", + command={"full": MinKey()}, checks={"ok": Eq(1.0)}, msg="full should accept MinKey (coerces to truthy)", ), DiagnosticTestCase( "maxkey", + command={"full": MaxKey()}, checks={"ok": Eq(1.0)}, msg="full should accept MaxKey (coerces to truthy)", ), ] -_VALUES = { - "bool_true": True, - "bool_false": False, - "int32_1": 1, - "int32_0": 0, - "double_1": 1.0, - "double_0": 0.0, - "int64_1": Int64(1), - "int64_0": Int64(0), - "decimal128_1": Decimal128("1"), - "decimal128_0": Decimal128("0"), - "null": None, - "string": "true", - "object": {}, - "array": [], - "binary": Binary(b""), - "objectid": ObjectId(), - "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), - "regex": Regex(".*"), - "timestamp": Timestamp(0, 0), - "code": Code("function(){}"), - "minkey": MinKey(), - "maxkey": MaxKey(), -} - @pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) def test_validate_full_accepted_types(collection, test): @@ -164,6 +161,6 @@ def test_validate_full_accepted_types(collection, test): collection.insert_one({"_id": 1}) result = execute_command( collection, - {"validate": collection.name, "full": _VALUES[test.id]}, + {"validate": collection.name, **test.command}, ) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py index 069bea5c5..148a9580a 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py @@ -22,141 +22,138 @@ ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", + command={"metadata": True}, checks={"ok": Eq(1.0)}, msg="metadata should accept bool true", ), DiagnosticTestCase( "bool_false", + command={"metadata": False}, checks={"ok": Eq(1.0)}, msg="metadata should accept bool false", ), DiagnosticTestCase( "int32_1", + command={"metadata": 1}, checks={"ok": Eq(1.0)}, msg="metadata should accept int32 1 (coerces to true)", ), DiagnosticTestCase( "int32_0", + command={"metadata": 0}, checks={"ok": Eq(1.0)}, msg="metadata should accept int32 0 (coerces to false)", ), DiagnosticTestCase( "double_1", + command={"metadata": 1.0}, checks={"ok": Eq(1.0)}, msg="metadata should accept double 1.0 (coerces to true)", ), DiagnosticTestCase( "double_0", + command={"metadata": 0.0}, checks={"ok": Eq(1.0)}, msg="metadata should accept double 0.0 (coerces to false)", ), DiagnosticTestCase( "int64_1", + command={"metadata": Int64(1)}, checks={"ok": Eq(1.0)}, msg="metadata should accept Int64(1) (coerces to true)", ), DiagnosticTestCase( "int64_0", + command={"metadata": Int64(0)}, checks={"ok": Eq(1.0)}, msg="metadata should accept Int64(0) (coerces to false)", ), DiagnosticTestCase( "decimal128_1", + command={"metadata": Decimal128("1")}, checks={"ok": Eq(1.0)}, msg="metadata should accept Decimal128('1') (coerces to true)", ), DiagnosticTestCase( "decimal128_0", + command={"metadata": Decimal128("0")}, checks={"ok": Eq(1.0)}, msg="metadata should accept Decimal128('0') (coerces to false)", ), DiagnosticTestCase( "null", + command={"metadata": None}, checks={"ok": Eq(1.0)}, msg="metadata should accept null (treated as omitted/false)", ), DiagnosticTestCase( "string", + command={"metadata": "true"}, checks={"ok": Eq(1.0)}, msg="metadata should accept string (coerces to truthy)", ), DiagnosticTestCase( "object", + command={"metadata": {}}, checks={"ok": Eq(1.0)}, msg="metadata should accept object (coerces to truthy)", ), DiagnosticTestCase( "array", + command={"metadata": []}, checks={"ok": Eq(1.0)}, msg="metadata should accept array (coerces to truthy)", ), DiagnosticTestCase( "binary", + command={"metadata": Binary(b"")}, checks={"ok": Eq(1.0)}, msg="metadata should accept Binary (coerces to truthy)", ), DiagnosticTestCase( "objectid", + command={"metadata": ObjectId()}, checks={"ok": Eq(1.0)}, msg="metadata should accept ObjectId (coerces to truthy)", ), DiagnosticTestCase( "datetime", + command={"metadata": datetime(2024, 1, 1, tzinfo=timezone.utc)}, checks={"ok": Eq(1.0)}, msg="metadata should accept datetime (coerces to truthy)", ), DiagnosticTestCase( "regex", + command={"metadata": Regex(".*")}, checks={"ok": Eq(1.0)}, msg="metadata should accept Regex (coerces to truthy)", ), DiagnosticTestCase( "timestamp", + command={"metadata": Timestamp(0, 0)}, checks={"ok": Eq(1.0)}, msg="metadata should accept Timestamp (coerces to truthy)", ), DiagnosticTestCase( "code", + command={"metadata": Code("function(){}")}, checks={"ok": Eq(1.0)}, msg="metadata should accept JavaScript Code (coerces to truthy)", ), DiagnosticTestCase( "minkey", + command={"metadata": MinKey()}, checks={"ok": Eq(1.0)}, msg="metadata should accept MinKey (coerces to truthy)", ), DiagnosticTestCase( "maxkey", + command={"metadata": MaxKey()}, checks={"ok": Eq(1.0)}, msg="metadata should accept MaxKey (coerces to truthy)", ), ] -_VALUES = { - "bool_true": True, - "bool_false": False, - "int32_1": 1, - "int32_0": 0, - "double_1": 1.0, - "double_0": 0.0, - "int64_1": Int64(1), - "int64_0": Int64(0), - "decimal128_1": Decimal128("1"), - "decimal128_0": Decimal128("0"), - "null": None, - "string": "true", - "object": {}, - "array": [], - "binary": Binary(b""), - "objectid": ObjectId(), - "datetime": datetime(2024, 1, 1, tzinfo=timezone.utc), - "regex": Regex(".*"), - "timestamp": Timestamp(0, 0), - "code": Code("function(){}"), - "minkey": MinKey(), - "maxkey": MaxKey(), -} - @pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) def test_validate_metadata_accepted_types(collection, test): @@ -164,6 +161,6 @@ def test_validate_metadata_accepted_types(collection, test): collection.insert_one({"_id": 1}) result = execute_command( collection, - {"validate": collection.name, "metadata": _VALUES[test.id]}, + {"validate": collection.name, **test.command}, ) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py index 128188a81..fc8370055 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py @@ -19,56 +19,54 @@ # Property [Valid Combinations]: validate succeeds with valid option combinations. VALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "minimal_command", - checks={"ok": Eq(1.0)}, - msg="Minimal validate command should succeed", - ), DiagnosticTestCase( "all_defaults_explicit", + command={"full": False, "repair": False, "metadata": False, "checkBSONConformance": False}, checks={"ok": Eq(1.0)}, msg="All options set to false explicitly should succeed", ), DiagnosticTestCase( "full_true", + command={"full": True}, checks={"ok": Eq(1.0)}, msg="validate with full: true should succeed", ), DiagnosticTestCase( "checkBSONConformance_true", + command={"checkBSONConformance": True}, checks={"ok": Eq(1.0)}, msg="validate with checkBSONConformance: true should succeed", ), DiagnosticTestCase( "full_and_checkBSONConformance", + command={"full": True, "checkBSONConformance": True}, checks={"ok": Eq(1.0)}, msg="validate with full: true and checkBSONConformance: true should succeed", ), DiagnosticTestCase( "metadata_true", + command={"metadata": True}, checks={"ok": Eq(1.0)}, msg="validate with metadata: true should succeed", ), DiagnosticTestCase( "fixMultikey_true_alone", + command={"fixMultikey": True}, checks={"ok": Eq(1.0)}, msg="validate with fixMultikey: true alone should succeed", ), DiagnosticTestCase( "repair_true_alone", + command={"repair": True}, checks={"ok": Eq(1.0)}, msg="validate with repair: true alone should succeed", ), DiagnosticTestCase( "repair_true_with_fixMultikey", + command={"repair": True, "fixMultikey": True}, checks={"ok": Eq(1.0)}, msg="validate with repair: true and fixMultikey: true should succeed", ), - DiagnosticTestCase( - "background_false", - checks={"ok": Eq(1.0)}, - msg="validate with background: false should succeed", - ), ] @@ -76,36 +74,7 @@ def test_validate_valid_option_combinations(collection, test): """Test that validate succeeds with valid option combinations.""" collection.insert_one({"_id": 1}) - commands = { - "minimal_command": {"validate": collection.name}, - "all_defaults_explicit": { - "validate": collection.name, - "full": False, - "repair": False, - "metadata": False, - "checkBSONConformance": False, - }, - "full_true": {"validate": collection.name, "full": True}, - "checkBSONConformance_true": { - "validate": collection.name, - "checkBSONConformance": True, - }, - "full_and_checkBSONConformance": { - "validate": collection.name, - "full": True, - "checkBSONConformance": True, - }, - "metadata_true": {"validate": collection.name, "metadata": True}, - "fixMultikey_true_alone": {"validate": collection.name, "fixMultikey": True}, - "repair_true_alone": {"validate": collection.name, "repair": True}, - "repair_true_with_fixMultikey": { - "validate": collection.name, - "repair": True, - "fixMultikey": True, - }, - "background_false": {"validate": collection.name, "background": False}, - } - result = execute_command(collection, commands[test.id]) + result = execute_command(collection, {"validate": collection.name, **test.command}) assertProperties(result, test.checks, msg=test.msg, raw_res=True) @@ -113,21 +82,25 @@ def test_validate_valid_option_combinations(collection, test): INVALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "metadata_with_full", + command={"metadata": True, "full": True}, error_code=INVALID_OPTIONS_ERROR, msg="metadata: true with full: true should error", ), DiagnosticTestCase( "metadata_with_repair", + command={"metadata": True, "repair": True, "fixMultikey": True}, error_code=INVALID_OPTIONS_ERROR, msg="metadata: true with repair: true should error", ), DiagnosticTestCase( "metadata_with_checkBSONConformance", + command={"metadata": True, "checkBSONConformance": True}, error_code=INVALID_OPTIONS_ERROR, msg="metadata: true with checkBSONConformance: true should error", ), DiagnosticTestCase( "checkBSONConformance_with_repair", + command={"checkBSONConformance": True, "repair": True, "fixMultikey": True}, error_code=INVALID_OPTIONS_ERROR, msg="checkBSONConformance: true with repair: true should error", ), @@ -138,29 +111,5 @@ def test_validate_valid_option_combinations(collection, test): def test_validate_invalid_option_combinations(collection, test): """Test that validate errors on invalid option combinations.""" collection.insert_one({"_id": 1}) - commands = { - "metadata_with_full": { - "validate": collection.name, - "metadata": True, - "full": True, - }, - "metadata_with_repair": { - "validate": collection.name, - "metadata": True, - "repair": True, - "fixMultikey": True, - }, - "metadata_with_checkBSONConformance": { - "validate": collection.name, - "metadata": True, - "checkBSONConformance": True, - }, - "checkBSONConformance_with_repair": { - "validate": collection.name, - "checkBSONConformance": True, - "repair": True, - "fixMultikey": True, - }, - } - result = execute_command(collection, commands[test.id]) + result = execute_command(collection, {"validate": collection.name, **test.command}) assertFailureCode(result, test.error_code, msg=test.msg) From deba2342781c5cf53b1090475e707fe1c570ef9e Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Jun 2026 15:12:51 -0700 Subject: [PATCH 04/15] change according to review guide Signed-off-by: Alina (Xi) Li --- docs/testing/TEST_COVERAGE.md | 4 +- .../validate/test_validate_core_behavior.py | 14 ++-- .../validate/test_validate_edge_cases.py | 22 ++++--- .../test_validate_option_combinations.py | 10 +-- .../test_validate_response_structure.py | 64 ++++++++++--------- 5 files changed, 64 insertions(+), 50 deletions(-) diff --git a/docs/testing/TEST_COVERAGE.md b/docs/testing/TEST_COVERAGE.md index 50e253cb0..165ab70c5 100644 --- a/docs/testing/TEST_COVERAGE.md +++ b/docs/testing/TEST_COVERAGE.md @@ -526,9 +526,11 @@ For each invalid_type in [string, object, array, ...]: - BSON type ordering → `tests/core/bson_types/`. Operators that use it (e.g. `$max`, `$gt`, `$sort`) get 1-2 wiring cases, not the full type-pair matrix. - Collation comparison → `tests/core/collation/`. Commands that accept `collation` test syntactic acceptance only. Sub-fields testing and semantic behavior is in 'tests/core/collation/'. - GeoJSON parsing and validation → `geospatial/specifiers/geometry/`. Geo operators that accept GeoJSON — test that the operator wires to the GeoJSON parser, not GeoJSON syntax tests. - - Wire-protocol namespace validation → TBD. Commands that take a namespace as their first field — single representative case, not the full character matrix. - Field path validation → Issue #118. + **Not foundational** (test exhaustively per command per §1 and §8e): + - Wire-protocol namespace validation — commands that take a collection name as their first field must test the full canonical BSON type set for non-string type rejection. Different engines may handle specific BSON types differently for specific commands, so this is a per-command input property, not a uniform foundational behavior. + **Test naming convention**: wiring tests typically use the suffix `_bson_wiring.py` or `__wiring.py`. Compare to `tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py` for the right shape — small, representative, explicitly named. diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 0d7cb78ab..64b68b72f 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -22,7 +22,7 @@ def test_validate_populated_collection(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nrecords": 5}, - msg="Populated collection should validate with correct nrecords", + msg="validate should return valid: true with correct nrecords for a populated collection", ) @@ -35,7 +35,7 @@ def test_validate_empty_collection(database_client, collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nrecords": 0, "nIndexes": 1}, - msg="Empty collection should be valid with nrecords: 0 and nIndexes: 1", + msg="validate should return nrecords: 0 and nIndexes: 1 for an empty collection", ) @@ -45,7 +45,7 @@ def test_validate_non_existent_collection(collection): assertFailureCode( result, NAMESPACE_NOT_FOUND_ERROR, - msg="Non-existent collection should return NamespaceNotFound error", + msg="validate should return NamespaceNotFound for a non-existent collection", ) @@ -57,7 +57,7 @@ def test_validate_after_insert_and_delete_all(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nrecords": 0}, - msg="After deleting all docs, nrecords should be 0", + msg="validate should return nrecords: 0 after deleting all documents", ) @@ -74,7 +74,7 @@ def test_validate_consistent_across_calls(collection): "valid": Eq(result2["valid"]), }, raw_res=True, - msg="Two consecutive validates should return identical key fields", + msg="validate should return identical key fields across consecutive calls", ) @@ -88,7 +88,7 @@ def test_validate_reflects_modifications(collection): result2, {"nrecords": Eq(8)}, raw_res=True, - msg="Second validate should reflect updated nrecords after insert", + msg="validate should reflect updated nrecords after additional inserts", ) @@ -101,7 +101,7 @@ def test_validate_after_dropping_indexes(collection): assertSuccessPartial( result, {"ok": 1.0, "nIndexes": 1}, - msg="After dropping secondary indexes, nIndexes should be 1 (only _id)", + msg="validate should return nIndexes: 1 after dropping secondary indexes", ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index b43720b08..a523b458e 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -23,7 +23,9 @@ def test_validate_long_collection_name(database_client, collection): coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial(result, {"ok": 1.0}, msg="Long collection name should succeed") + assertSuccessPartial( + result, {"ok": 1.0}, msg="validate should succeed with a long collection name" + ) def test_validate_unicode_collection_name(database_client, collection): @@ -33,7 +35,9 @@ def test_validate_unicode_collection_name(database_client, collection): coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial(result, {"ok": 1.0}, msg="Unicode collection name should succeed") + assertSuccessPartial( + result, {"ok": 1.0}, msg="validate should succeed with unicode collection name" + ) def test_validate_numeric_looking_collection_name(database_client, collection): @@ -42,7 +46,9 @@ def test_validate_numeric_looking_collection_name(database_client, collection): coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial(result, {"ok": 1.0}, msg="Numeric-looking collection name should succeed") + assertSuccessPartial( + result, {"ok": 1.0}, msg="validate should succeed with numeric-looking collection name" + ) def test_validate_large_document_count(collection): @@ -83,7 +89,7 @@ def test_validate_document_with_all_bson_types(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True}, - msg="Document with all BSON types should be valid", + msg="validate should return valid: true for a document with all BSON types", ) @@ -100,7 +106,7 @@ def test_validate_deeply_nested_document(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True}, - msg="Deeply nested document should be valid", + msg="validate should return valid: true for a deeply nested document", ) @@ -117,7 +123,7 @@ def test_validate_documents_with_arrays(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nrecords": 3}, - msg="Documents with arrays should be valid", + msg="validate should return valid: true for documents with arrays", ) @@ -133,7 +139,7 @@ def test_validate_documents_with_binary_data(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nrecords": 2}, - msg="Documents with binary data should be valid", + msg="validate should return valid: true for documents with binary data", ) @@ -149,5 +155,5 @@ def test_validate_many_indexes(collection): assertSuccessPartial( result, {"ok": 1.0, "valid": True, "nIndexes": 6}, - msg="nIndexes should be 6 (_id + 5 secondary indexes)", + msg="validate should report nIndexes: 6 with 5 secondary indexes", ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py index fc8370055..75399ddfe 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py @@ -23,7 +23,7 @@ "all_defaults_explicit", command={"full": False, "repair": False, "metadata": False, "checkBSONConformance": False}, checks={"ok": Eq(1.0)}, - msg="All options set to false explicitly should succeed", + msg="validate should succeed with all options set to false explicitly", ), DiagnosticTestCase( "full_true", @@ -84,25 +84,25 @@ def test_validate_valid_option_combinations(collection, test): "metadata_with_full", command={"metadata": True, "full": True}, error_code=INVALID_OPTIONS_ERROR, - msg="metadata: true with full: true should error", + msg="validate should error with metadata: true and full: true", ), DiagnosticTestCase( "metadata_with_repair", command={"metadata": True, "repair": True, "fixMultikey": True}, error_code=INVALID_OPTIONS_ERROR, - msg="metadata: true with repair: true should error", + msg="validate should error with metadata: true and repair: true", ), DiagnosticTestCase( "metadata_with_checkBSONConformance", command={"metadata": True, "checkBSONConformance": True}, error_code=INVALID_OPTIONS_ERROR, - msg="metadata: true with checkBSONConformance: true should error", + msg="validate should error with metadata: true and checkBSONConformance: true", ), DiagnosticTestCase( "checkBSONConformance_with_repair", command={"checkBSONConformance": True, "repair": True, "fixMultikey": True}, error_code=INVALID_OPTIONS_ERROR, - msg="checkBSONConformance: true with repair: true should error", + msg="validate should error with checkBSONConformance: true and repair: true", ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index 6bc64e140..24e1d5503 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -21,132 +21,132 @@ DiagnosticTestCase( "ok_is_1", checks={"ok": Eq(1.0)}, - msg="'ok' field should be 1.0", + msg="validate should return ok: 1.0", ), DiagnosticTestCase( "ns_is_string", checks={"ns": IsType("string")}, - msg="'ns' field should be a string", + msg="validate should return ns as a string", ), DiagnosticTestCase( "nInvalidDocuments_is_int", checks={"nInvalidDocuments": IsType("int")}, - msg="'nInvalidDocuments' field should be an int", + msg="validate should return nInvalidDocuments as an int", ), DiagnosticTestCase( "nNonCompliantDocuments_is_int", checks={"nNonCompliantDocuments": IsType("int")}, - msg="'nNonCompliantDocuments' field should be an int", + msg="validate should return nNonCompliantDocuments as an int", ), DiagnosticTestCase( "nrecords_is_int", checks={"nrecords": IsType("int")}, - msg="'nrecords' field should be an int", + msg="validate should return nrecords as an int", ), DiagnosticTestCase( "nIndexes_is_int", checks={"nIndexes": IsType("int")}, - msg="'nIndexes' field should be an int", + msg="validate should return nIndexes as an int", ), DiagnosticTestCase( "keysPerIndex_is_object", checks={"keysPerIndex": IsType("object")}, - msg="'keysPerIndex' field should be an object", + msg="validate should return keysPerIndex as an object", ), DiagnosticTestCase( "indexDetails_is_object", checks={"indexDetails": IsType("object")}, - msg="'indexDetails' field should be an object", + msg="validate should return indexDetails as an object", ), DiagnosticTestCase( "valid_is_bool", checks={"valid": IsType("bool")}, - msg="'valid' field should be a bool", + msg="validate should return valid as a bool", ), DiagnosticTestCase( "repaired_is_bool", checks={"repaired": IsType("bool")}, - msg="'repaired' field should be a bool", + msg="validate should return repaired as a bool", ), DiagnosticTestCase( "warnings_is_array", checks={"warnings": IsType("array")}, - msg="'warnings' field should be an array", + msg="validate should return warnings as an array", ), DiagnosticTestCase( "errors_is_array", checks={"errors": IsType("array")}, - msg="'errors' field should be an array", + msg="validate should return errors as an array", ), DiagnosticTestCase( "extraIndexEntries_is_array", checks={"extraIndexEntries": IsType("array")}, - msg="'extraIndexEntries' field should be an array", + msg="validate should return extraIndexEntries as an array", ), DiagnosticTestCase( "missingIndexEntries_is_array", checks={"missingIndexEntries": IsType("array")}, - msg="'missingIndexEntries' field should be an array", + msg="validate should return missingIndexEntries as an array", ), DiagnosticTestCase( "corruptRecords_is_array", checks={"corruptRecords": IsType("array")}, - msg="'corruptRecords' field should be an array", + msg="validate should return corruptRecords as an array", ), DiagnosticTestCase( "uuid_exists", checks={"uuid": Exists()}, - msg="'uuid' field should exist (since 6.2)", + msg="validate should return uuid field", ), DiagnosticTestCase( "nInvalidDocuments_zero_healthy", checks={"nInvalidDocuments": Eq(0)}, - msg="'nInvalidDocuments' should be 0 for a healthy collection", + msg="validate should return nInvalidDocuments: 0 for a healthy collection", ), DiagnosticTestCase( "nNonCompliantDocuments_zero_healthy", checks={"nNonCompliantDocuments": Eq(0)}, - msg="'nNonCompliantDocuments' should be 0 for a healthy collection", + msg="validate should return nNonCompliantDocuments: 0 for a healthy collection", ), DiagnosticTestCase( "valid_true_healthy", checks={"valid": Eq(True)}, - msg="'valid' should be true for a healthy collection", + msg="validate should return valid: true for a healthy collection", ), DiagnosticTestCase( "repaired_false_no_repair", checks={"repaired": Eq(False)}, - msg="'repaired' should be false when no repair requested", + msg="validate should return repaired: false when no repair requested", ), DiagnosticTestCase( "warnings_empty_healthy", checks={"warnings": Eq([])}, - msg="'warnings' should be empty for a healthy collection", + msg="validate should return empty warnings for a healthy collection", ), DiagnosticTestCase( "errors_empty_healthy", checks={"errors": Eq([])}, - msg="'errors' should be empty for a healthy collection", + msg="validate should return empty errors for a healthy collection", ), DiagnosticTestCase( "extraIndexEntries_empty_healthy", checks={"extraIndexEntries": Eq([])}, - msg="'extraIndexEntries' should be empty for a healthy collection", + msg="validate should return empty extraIndexEntries for a healthy collection", ), DiagnosticTestCase( "missingIndexEntries_empty_healthy", checks={"missingIndexEntries": Eq([])}, - msg="'missingIndexEntries' should be empty for a healthy collection", + msg="validate should return empty missingIndexEntries for a healthy collection", ), DiagnosticTestCase( "corruptRecords_empty_healthy", checks={"corruptRecords": Eq([])}, - msg="'corruptRecords' should be empty for a healthy collection", + msg="validate should return empty corruptRecords for a healthy collection", ), DiagnosticTestCase( "nIndexes_gte_1", checks={"nIndexes": Gte(1)}, - msg="'nIndexes' should be >= 1 (at least _id index)", + msg="validate should return nIndexes >= 1 (at least _id index)", ), ] @@ -164,14 +164,18 @@ def test_validate_ns_matches_namespace(collection): collection.insert_one({"_id": 1}) result = execute_command(collection, {"validate": collection.name}) expected_ns = f"{collection.database.name}.{collection.name}" - assertSuccessPartial(result, {"ns": expected_ns}, msg="ns should match actual namespace") + assertSuccessPartial( + result, {"ns": expected_ns}, msg="validate should return ns matching the actual namespace" + ) def test_validate_nrecords_matches_count(collection): """Test validate nrecords matches the number of inserted documents.""" collection.insert_many([{"_id": i} for i in range(10)]) result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial(result, {"nrecords": 10}, msg="nrecords should match document count") + assertSuccessPartial( + result, {"nrecords": 10}, msg="validate should return nrecords matching document count" + ) def test_validate_nIndexes_with_secondary(collection): @@ -180,4 +184,6 @@ def test_validate_nIndexes_with_secondary(collection): collection.create_index("x") collection.create_index("y") result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial(result, {"nIndexes": 3}, msg="nIndexes should be 3 (_id + x + y)") + assertSuccessPartial( + result, {"nIndexes": 3}, msg="validate should return nIndexes: 3 with two secondary indexes" + ) From 7c68964e41af59f94ec010ecbdd9ae84eadc8318 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Jun 2026 16:09:48 -0700 Subject: [PATCH 05/15] add setup field Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_core_behavior.py | 146 +++++---- .../validate/test_validate_edge_cases.py | 256 ++++++++------- .../validate/test_validate_indexes.py | 301 +++++++++++------- .../test_validate_response_structure.py | 60 ++-- 4 files changed, 447 insertions(+), 316 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 64b68b72f..b9ef08b2b 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -5,60 +5,93 @@ from __future__ import annotations -from documentdb_tests.framework.assertions import ( - assertFailureCode, - assertProperties, - assertSuccessPartial, +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, ) +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties from documentdb_tests.framework.error_codes import NAMESPACE_NOT_FOUND_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 - -def test_validate_populated_collection(collection): - """Test validate on a populated collection returns valid: true with correct counts.""" - collection.insert_many([{"_id": i, "x": i} for i in range(5)]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nrecords": 5}, +# Property [Core Behavior]: validate returns expected results for populated, +# empty, and non-existent collections and supports common parameters. +CORE_BEHAVIOR_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "populated_collection", + setup=[{"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}], + checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(5)}, msg="validate should return valid: true with correct nrecords for a populated collection", - ) - - -def test_validate_empty_collection(database_client, collection): - """Test validate on an empty collection returns nrecords: 0, valid: true.""" - coll_name = f"{collection.name}_empty" - database_client.create_collection(coll_name) - coll = database_client[coll_name] - result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nrecords": 0, "nIndexes": 1}, + ), + DiagnosticTestCase( + "empty_collection", + setup=[{"create": ""}], + checks={ + "ok": Eq(1.0), + "valid": Eq(True), + "nrecords": Eq(0), + "nIndexes": Eq(1), + }, msg="validate should return nrecords: 0 and nIndexes: 1 for an empty collection", - ) + ), + DiagnosticTestCase( + "after_insert_and_delete_all", + setup=[ + {"insert": "", "documents": [{"_id": i} for i in range(5)]}, + {"delete": "", "deletes": [{"q": {}, "limit": 0}]}, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(0)}, + msg="validate should return nrecords: 0 after deleting all documents", + ), + DiagnosticTestCase( + "after_dropping_indexes", + setup=[ + {"insert": "", "documents": [{"_id": 1, "x": 1}]}, + {"createIndexes": "", "indexes": [{"key": {"x": 1}, "name": "x_1"}]}, + {"dropIndexes": "", "index": "*"}, + ], + checks={"ok": Eq(1.0), "nIndexes": Eq(1)}, + msg="validate should return nIndexes: 1 after dropping secondary indexes", + ), + DiagnosticTestCase( + "with_comment", + setup=[{"insert": "", "documents": [{"_id": 1}]}], + command={"comment": "test comment"}, + checks={"ok": Eq(1.0)}, + msg="validate should succeed with comment parameter", + ), +] + +# Property [Non-Existent Collection]: validate returns NamespaceNotFound for +# a collection that does not exist. +NON_EXISTENT_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "non_existent_collection", + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="validate should return NamespaceNotFound for a non-existent collection", + ), +] -def test_validate_non_existent_collection(collection): - """Test validate on a non-existent collection returns NamespaceNotFound error.""" - result = execute_command(collection, {"validate": f"{collection.name}_nonexistent_xyz"}) - assertFailureCode( - result, - NAMESPACE_NOT_FOUND_ERROR, - msg="validate should return NamespaceNotFound for a non-existent collection", - ) +@pytest.mark.parametrize("test", pytest_params(CORE_BEHAVIOR_TESTS)) +def test_validate_core_behavior(collection, test): + """Test validate core behavior with various collection states.""" + for cmd in test.setup: + execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + cmd = {"validate": collection.name} + if test.command: + cmd.update(test.command) + result = execute_command(collection, cmd) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_validate_after_insert_and_delete_all(collection): - """Test validate after inserting and deleting all documents shows nrecords: 0.""" - collection.insert_many([{"_id": i} for i in range(5)]) - collection.delete_many({}) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nrecords": 0}, - msg="validate should return nrecords: 0 after deleting all documents", - ) +@pytest.mark.parametrize("test", pytest_params(NON_EXISTENT_TESTS)) +def test_validate_non_existent(collection, test): + """Test validate on non-existent collections returns expected error.""" + result = execute_command(collection, {"validate": f"{collection.name}_nonexistent_xyz"}) + assertFailureCode(result, test.error_code, msg=test.msg) def test_validate_consistent_across_calls(collection): @@ -90,30 +123,3 @@ def test_validate_reflects_modifications(collection): raw_res=True, msg="validate should reflect updated nrecords after additional inserts", ) - - -def test_validate_after_dropping_indexes(collection): - """Test validate after dropping secondary indexes shows nIndexes: 1.""" - collection.insert_one({"_id": 1, "x": 1}) - collection.create_index("x") - collection.drop_indexes() - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "nIndexes": 1}, - msg="validate should return nIndexes: 1 after dropping secondary indexes", - ) - - -def test_validate_with_comment(collection): - """Test validate accepts the comment parameter.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, "comment": "test comment"}, - ) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="validate with comment parameter should succeed", - ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index a523b458e..9ab2862f4 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -8,10 +8,154 @@ from datetime import datetime, timezone +import pytest from bson import Binary, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp -from documentdb_tests.framework.assertions import assertSuccessPartial +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial 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 [Document Variety]: validate succeeds for collections with diverse +# document shapes, sizes, and BSON types. +DOCUMENT_VARIETY_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "large_document_count", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "x": i} for i in range(1_000)], + } + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(1_000)}, + msg="validate should report correct nrecords for large document count", + ), + DiagnosticTestCase( + "all_bson_types", + setup=[ + { + "insert": "", + "documents": [ + { + "_id": 1, + "double_val": 3.14, + "string_val": "hello", + "object_val": {"nested": 1}, + "array_val": [1, 2, 3], + "binary_val": Binary(b"data"), + "objectid_val": ObjectId(), + "bool_val": True, + "date_val": datetime(2024, 1, 1, tzinfo=timezone.utc), + "null_val": None, + "regex_val": Regex("test"), + "int32_val": 42, + "timestamp_val": Timestamp(1, 1), + "int64_val": Int64(123_456_789), + "decimal128_val": Decimal128("1.23"), + "minkey_val": MinKey(), + "maxkey_val": MaxKey(), + } + ], + } + ], + checks={"ok": Eq(1.0), "valid": Eq(True)}, + msg="validate should return valid: true for a document with all BSON types", + ), + DiagnosticTestCase( + "deeply_nested_document", + setup=[ + { + "insert": "", + "documents": [ + { + "_id": 1, + "level_0": { + "level_1": { + "level_2": { + "level_3": { + "level_4": { + "level_5": { + "level_6": { + "level_7": { + "level_8": {"level_9": {"value": "deep"}} + } + } + } + } + } + } + } + }, + } + ], + } + ], + checks={"ok": Eq(1.0), "valid": Eq(True)}, + msg="validate should return valid: true for a deeply nested document", + ), + DiagnosticTestCase( + "documents_with_arrays", + setup=[ + { + "insert": "", + "documents": [ + {"_id": 1, "arr": []}, + {"_id": 2, "arr": [1, 2, 3]}, + {"_id": 3, "arr": list(range(100))}, + ], + } + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(3)}, + msg="validate should return valid: true for documents with arrays", + ), + DiagnosticTestCase( + "documents_with_binary_data", + setup=[ + { + "insert": "", + "documents": [ + {"_id": 1, "data": Binary(b"small")}, + {"_id": 2, "data": Binary(b"\x00" * 1_024)}, + ], + } + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(2)}, + msg="validate should return valid: true for documents with binary data", + ), + DiagnosticTestCase( + "many_indexes", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "a": i, "b": i, "c": i, "d": i, "e": i} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [ + {"key": {"a": 1}, "name": "a_1"}, + {"key": {"b": 1}, "name": "b_1"}, + {"key": {"c": 1}, "name": "c_1"}, + {"key": {"d": 1}, "name": "d_1"}, + {"key": {"e": 1}, "name": "e_1"}, + ], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(6)}, + msg="validate should report nIndexes: 6 with 5 secondary indexes", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(DOCUMENT_VARIETY_TESTS)) +def test_validate_document_variety(collection, test): + """Test validate with diverse document shapes and index counts.""" + for cmd in test.setup: + execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + result = execute_command(collection, {"validate": collection.name}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) def test_validate_long_collection_name(database_client, collection): @@ -46,114 +190,8 @@ def test_validate_numeric_looking_collection_name(database_client, collection): coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, {"ok": 1.0}, msg="validate should succeed with numeric-looking collection name" - ) - - -def test_validate_large_document_count(collection): - """Test validate with 1_000 documents reports correct nrecords.""" - collection.insert_many([{"_id": i, "x": i} for i in range(1_000)]) - result = execute_command(collection, {"validate": collection.name}) assertSuccessPartial( result, - {"ok": 1.0, "valid": True, "nrecords": 1_000}, - msg="validate should report correct nrecords for large document count", - ) - - -def test_validate_document_with_all_bson_types(collection): - """Test validate on a collection with a document containing all BSON types.""" - collection.insert_one( - { - "_id": 1, - "double_val": 3.14, - "string_val": "hello", - "object_val": {"nested": 1}, - "array_val": [1, 2, 3], - "binary_val": Binary(b"data"), - "objectid_val": ObjectId(), - "bool_val": True, - "date_val": datetime(2024, 1, 1, tzinfo=timezone.utc), - "null_val": None, - "regex_val": Regex("test"), - "int32_val": 42, - "timestamp_val": Timestamp(1, 1), - "int64_val": Int64(123_456_789), - "decimal128_val": Decimal128("1.23"), - "minkey_val": MinKey(), - "maxkey_val": MaxKey(), - } - ) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, - msg="validate should return valid: true for a document with all BSON types", - ) - - -def test_validate_deeply_nested_document(collection): - """Test validate on a collection with a deeply nested document.""" - doc = {"_id": 1} - nested = doc - for i in range(10): - nested[f"level_{i}"] = {} - nested = nested[f"level_{i}"] - nested["value"] = "deep" - collection.insert_one(doc) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, - msg="validate should return valid: true for a deeply nested document", - ) - - -def test_validate_documents_with_arrays(collection): - """Test validate on a collection with documents containing arrays.""" - collection.insert_many( - [ - {"_id": 1, "arr": []}, - {"_id": 2, "arr": [1, 2, 3]}, - {"_id": 3, "arr": list(range(100))}, - ] - ) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nrecords": 3}, - msg="validate should return valid: true for documents with arrays", - ) - - -def test_validate_documents_with_binary_data(collection): - """Test validate on a collection with documents containing Binary fields.""" - collection.insert_many( - [ - {"_id": 1, "data": Binary(b"small")}, - {"_id": 2, "data": Binary(b"\x00" * 1_024)}, - ] - ) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nrecords": 2}, - msg="validate should return valid: true for documents with binary data", - ) - - -def test_validate_many_indexes(collection): - """Test validate with 5 secondary indexes reports correct nIndexes.""" - collection.insert_many([{"_id": i, "a": i, "b": i, "c": i, "d": i, "e": i} for i in range(5)]) - collection.create_index("a") - collection.create_index("b") - collection.create_index("c") - collection.create_index("d") - collection.create_index("e") - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 6}, - msg="validate should report nIndexes: 6 with 5 secondary indexes", + {"ok": 1.0}, + msg="validate should succeed with numeric-looking collection name", ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py index 3034d3ddd..d22e53629 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py @@ -9,135 +9,202 @@ from datetime import datetime, timezone -from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq - -def test_validate_unique_index(collection): - """Test validate with a unique index reports it in results.""" - collection.insert_many([{"_id": i, "x": i} for i in range(5)]) - collection.create_index("x", unique=True) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 2}, +# Property [Index Types]: validate succeeds and reports correct nIndexes for +# collections with various index types. +INDEX_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "unique_index", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}, + { + "createIndexes": "", + "indexes": [{"key": {"x": 1}, "name": "x_1", "unique": True}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(2)}, msg="validate should succeed and report unique index", - ) - - -def test_validate_sparse_index(collection): - """Test validate with a sparse index shows fewer keys than nrecords.""" - collection.insert_many( - [{"_id": i, "x": i} for i in range(5)] - + [{"_id": i} for i in range(5, 10)] # 5 docs without 'x' field - ) - collection.create_index("x", sparse=True) - result = execute_command(collection, {"validate": collection.name}) - assertProperties( - result, - {"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(10), "nIndexes": Eq(2)}, - raw_res=True, + ), + DiagnosticTestCase( + "sparse_index", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "x": i} for i in range(5)] + + [{"_id": i} for i in range(5, 10)], + }, + { + "createIndexes": "", + "indexes": [{"key": {"x": 1}, "name": "x_1_sparse", "sparse": True}], + }, + ], + checks={ + "ok": Eq(1.0), + "valid": Eq(True), + "nrecords": Eq(10), + "nIndexes": Eq(2), + }, msg="validate should succeed with sparse index", - ) - - -def test_validate_ttl_index(collection): - """Test validate with a TTL index reports it in results.""" - collection.insert_one({"_id": 1, "created": datetime(2024, 1, 1, tzinfo=timezone.utc)}) - collection.create_index("created", expireAfterSeconds=3600) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 2}, + ), + DiagnosticTestCase( + "ttl_index", + setup=[ + { + "insert": "", + "documents": [ + { + "_id": 1, + "created": datetime(2024, 1, 1, tzinfo=timezone.utc), + } + ], + }, + { + "createIndexes": "", + "indexes": [ + { + "key": {"created": 1}, + "name": "created_ttl", + "expireAfterSeconds": 3600, + } + ], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(2)}, msg="validate should succeed and report TTL index", - ) - - -def test_validate_text_index(collection): - """Test validate with a text index succeeds.""" - collection.insert_many([{"_id": i, "content": f"document text {i}"} for i in range(5)]) - collection.create_index([("content", "text")]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, + ), + DiagnosticTestCase( + "text_index", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "content": f"document text {i}"} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [{"key": {"content": "text"}, "name": "content_text"}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True)}, msg="validate should succeed with text index", - ) - - -def test_validate_2dsphere_index(collection): - """Test validate with a 2dsphere index succeeds.""" - collection.insert_one({"_id": 1, "location": {"type": "Point", "coordinates": [0.0, 0.0]}}) - collection.create_index([("location", "2dsphere")]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, + ), + DiagnosticTestCase( + "2dsphere_index", + setup=[ + { + "insert": "", + "documents": [ + { + "_id": 1, + "location": { + "type": "Point", + "coordinates": [0.0, 0.0], + }, + } + ], + }, + { + "createIndexes": "", + "indexes": [{"key": {"location": "2dsphere"}, "name": "location_2dsphere"}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True)}, msg="validate should succeed with 2dsphere index", - ) - - -def test_validate_hashed_index(collection): - """Test validate with a hashed index succeeds.""" - collection.insert_many([{"_id": i, "x": i} for i in range(5)]) - collection.create_index([("x", "hashed")]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 2}, + ), + DiagnosticTestCase( + "hashed_index", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}, + { + "createIndexes": "", + "indexes": [{"key": {"x": "hashed"}, "name": "x_hashed"}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(2)}, msg="validate should succeed with hashed index", - ) - - -def test_validate_wildcard_index(collection): - """Test validate with a wildcard index succeeds.""" - collection.insert_many([{"_id": i, "a": i, "b": str(i)} for i in range(5)]) - collection.create_index([("$**", 1)]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, + ), + DiagnosticTestCase( + "wildcard_index", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "a": i, "b": str(i)} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [{"key": {"$**": 1}, "name": "wildcard"}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True)}, msg="validate should succeed with wildcard index", - ) - - -def test_validate_compound_index(collection): - """Test validate with a compound index reports it in results.""" - collection.insert_many([{"_id": i, "a": i, "b": -i} for i in range(5)]) - collection.create_index([("a", 1), ("b", -1)]) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 2}, + ), + DiagnosticTestCase( + "compound_index", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "a": i, "b": -i} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [{"key": {"a": 1, "b": -1}, "name": "a_1_b_neg1"}], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(2)}, msg="validate should succeed and report compound index", - ) - - -def test_validate_partial_filter_index(collection): - """Test validate with a partial filter index succeeds.""" - collection.insert_many([{"_id": i, "x": i} for i in range(10)]) - collection.create_index( - "x", - partialFilterExpression={"x": {"$gt": 4}}, - ) - result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 2}, + ), + DiagnosticTestCase( + "partial_filter_index", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(10)]}, + { + "createIndexes": "", + "indexes": [ + { + "key": {"x": 1}, + "name": "x_partial", + "partialFilterExpression": {"x": {"$gt": 4}}, + } + ], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(2)}, msg="validate should succeed with partial filter index", - ) + ), + DiagnosticTestCase( + "multiple_indexes", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "a": i, "b": str(i)} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [ + {"key": {"a": 1}, "name": "a_unique", "unique": True}, + {"key": {"b": 1}, "name": "b_1"}, + {"key": {"a": 1, "b": 1}, "name": "a_1_b_1"}, + ], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(4)}, + msg="validate should report all 4 indexes (_id + 3 secondary)", + ), +] -def test_validate_multiple_indexes(collection): - """Test validate with multiple index types reports correct nIndexes.""" - collection.insert_many([{"_id": i, "a": i, "b": str(i)} for i in range(5)]) - collection.create_index("a", unique=True) - collection.create_index("b") - collection.create_index([("a", 1), ("b", 1)]) +@pytest.mark.parametrize("test", pytest_params(INDEX_TYPE_TESTS)) +def test_validate_index_types(collection, test): + """Test validate with various index types.""" + for cmd in test.setup: + execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True, "nIndexes": 4}, - msg="validate should report all 4 indexes (_id + 3 secondary)", - ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index 24e1d5503..d699d84ab 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -159,31 +159,51 @@ def test_validate_response_properties(collection, test): assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_validate_ns_matches_namespace(collection): - """Test validate ns field matches the actual database.collection namespace.""" - collection.insert_one({"_id": 1}) - result = execute_command(collection, {"validate": collection.name}) - expected_ns = f"{collection.database.name}.{collection.name}" - assertSuccessPartial( - result, {"ns": expected_ns}, msg="validate should return ns matching the actual namespace" - ) +# Property [Response Values]: validate returns correct dynamic values matching +# collection state (nrecords, nIndexes, ns). +RESPONSE_VALUE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "nrecords_matches_count", + setup=[ + {"insert": "", "documents": [{"_id": i} for i in range(10)]}, + ], + checks={"nrecords": Eq(10)}, + msg="validate should return nrecords matching document count", + ), + DiagnosticTestCase( + "nIndexes_with_secondary", + setup=[ + {"insert": "", "documents": [{"_id": 1, "x": 1, "y": 1}]}, + { + "createIndexes": "", + "indexes": [ + {"key": {"x": 1}, "name": "x_1"}, + {"key": {"y": 1}, "name": "y_1"}, + ], + }, + ], + checks={"nIndexes": Eq(3)}, + msg="validate should return nIndexes: 3 with two secondary indexes", + ), +] -def test_validate_nrecords_matches_count(collection): - """Test validate nrecords matches the number of inserted documents.""" - collection.insert_many([{"_id": i} for i in range(10)]) +@pytest.mark.parametrize("test", pytest_params(RESPONSE_VALUE_TESTS)) +def test_validate_response_values(collection, test): + """Test validate response values match collection state.""" + for cmd in test.setup: + execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) result = execute_command(collection, {"validate": collection.name}) - assertSuccessPartial( - result, {"nrecords": 10}, msg="validate should return nrecords matching document count" - ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_validate_nIndexes_with_secondary(collection): - """Test validate nIndexes includes secondary indexes.""" - collection.insert_one({"_id": 1, "x": 1, "y": 1}) - collection.create_index("x") - collection.create_index("y") +def test_validate_ns_matches_namespace(collection): + """Test validate ns field matches the actual database.collection namespace.""" + collection.insert_one({"_id": 1}) result = execute_command(collection, {"validate": collection.name}) + expected_ns = f"{collection.database.name}.{collection.name}" assertSuccessPartial( - result, {"nIndexes": 3}, msg="validate should return nIndexes: 3 with two secondary indexes" + result, + {"ns": expected_ns}, + msg="validate should return ns matching the actual namespace", ) From 7de16e5b2b1b2832325d53eeefa80119491125f6 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Jun 2026 16:27:37 -0700 Subject: [PATCH 06/15] split files Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_argument_type.py | 58 +++++++- .../validate/test_validate_background_type.py | 105 ------------- .../test_validate_collection_variants.py | 77 ---------- .../validate/test_validate_core_behavior.py | 140 +++++++++++++++--- .../validate/test_validate_error_cases.py | 128 ++++++++++++++++ .../test_validate_option_combinations.py | 115 -------------- 6 files changed, 302 insertions(+), 321 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py index c32867d03..efad57046 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -1,7 +1,8 @@ """Tests for validate command argument type handling. Validates that the validate parameter (collection name) must be a string and -rejects all other BSON types with the correct error code. +rejects all other BSON types with the correct error code. Also validates that +the background parameter accepts falsy BSON types via coercion. """ from __future__ import annotations @@ -14,10 +15,11 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) -from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties from documentdb_tests.framework.error_codes import INVALID_NAMESPACE_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 [Type Rejection]: validate rejects all non-string BSON types for the collection name. INVALID_TYPE_TESTS: list[DiagnosticTestCase] = [ @@ -143,3 +145,55 @@ def test_validate_rejects_non_string_types(collection, test): """Test that validate rejects non-string BSON types for the collection name.""" result = execute_command(collection, test.command) assertFailureCode(result, test.error_code, msg=test.msg) + + +# Property [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. +FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_false", + command={"background": False}, + checks={"ok": Eq(1.0)}, + msg="background should accept bool false", + ), + DiagnosticTestCase( + "int32_0", + command={"background": 0}, + checks={"ok": Eq(1.0)}, + msg="background should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_0", + command={"background": 0.0}, + checks={"ok": Eq(1.0)}, + msg="background should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_0", + command={"background": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="background should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_0", + command={"background": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="background should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + command={"background": None}, + checks={"ok": Eq(1.0)}, + msg="background should accept null (treated as omitted/false)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(FALSY_TYPE_TESTS)) +def test_validate_background_falsy_types(collection, test): + """Test that validate accepts falsy types for the background parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, **test.command}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py deleted file mode 100644 index cde1d55f2..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_background_type.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Tests for validate command 'background' parameter type coercion. - -Validates that the background parameter accepts all BSON types via coercion. -Note: background: true is not supported on standalone mode, so truthy values -are tested with assertFailureCode for the standalone error. -""" - -from __future__ import annotations - -import pytest -from bson import Decimal128, Int64 - -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, -) -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties -from documentdb_tests.framework.error_codes import COMMAND_NOT_SUPPORTED_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 [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. -FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "bool_false", - command={"background": False}, - checks={"ok": Eq(1.0)}, - msg="background should accept bool false", - ), - DiagnosticTestCase( - "int32_0", - command={"background": 0}, - checks={"ok": Eq(1.0)}, - msg="background should accept int32 0 (coerces to false)", - ), - DiagnosticTestCase( - "double_0", - command={"background": 0.0}, - checks={"ok": Eq(1.0)}, - msg="background should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_0", - command={"background": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="background should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"background": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="background should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"background": None}, - checks={"ok": Eq(1.0)}, - msg="background should accept null (treated as omitted/false)", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(FALSY_TYPE_TESTS)) -def test_validate_background_falsy_types(collection, test): - """Test that validate accepts falsy types for the background parameter.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - -# Property [Truthy Standalone Error]: validate rejects truthy background values on standalone mode. -TRUTHY_TYPE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "bool_true", - command={"background": True}, - error_code=COMMAND_NOT_SUPPORTED_ERROR, - msg="background: true not supported on standalone", - ), - DiagnosticTestCase( - "int32_1", - command={"background": 1}, - error_code=COMMAND_NOT_SUPPORTED_ERROR, - msg="background: int 1 (truthy) not supported on standalone", - ), - DiagnosticTestCase( - "string", - command={"background": "true"}, - error_code=COMMAND_NOT_SUPPORTED_ERROR, - msg="background: string (truthy) not supported on standalone", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) -def test_validate_background_truthy_standalone_error(collection, test): - """Test that background with truthy values errors on standalone mode.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) - assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py deleted file mode 100644 index 5c80113a9..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_collection_variants.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Tests for validate command on different collection types. - -Validates behavior on regular, capped, view, timeseries, and clustered collections. -""" - -from __future__ import annotations - -from datetime import datetime, timezone - -from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial -from documentdb_tests.framework.error_codes import COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR -from documentdb_tests.framework.executor import execute_command - - -def test_validate_capped_collection(database_client, collection): - """Test validate on a capped collection succeeds.""" - coll_name = f"{collection.name}_capped" - database_client.create_collection(coll_name, capped=True, size=1_048_576) - coll = database_client[coll_name] - coll.insert_one({"_id": 1, "x": 1}) - result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, - {"ok": 1.0, "valid": True}, - msg="validate should succeed on a capped collection", - ) - - -def test_validate_view_rejected(database_client, collection): - """Test validate on a view returns an error.""" - source_name = f"{collection.name}_view_source" - view_name = f"{collection.name}_view" - database_client.create_collection(source_name) - database_client[source_name].insert_one({"_id": 1}) - database_client.command("create", view_name, viewOn=source_name, pipeline=[]) - coll = database_client[view_name] - result = execute_command(coll, {"validate": coll.name}) - assertFailureCode( - result, - COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, - msg="validate should reject views", - ) - - -def test_validate_timeseries_collection(database_client, collection): - """Test validate on a time series collection succeeds.""" - coll_name = f"{collection.name}_timeseries" - database_client.create_collection( - coll_name, - timeseries={"timeField": "ts", "metaField": "meta"}, - ) - coll = database_client[coll_name] - coll.insert_one({"ts": datetime(2024, 1, 1, tzinfo=timezone.utc), "meta": "a", "v": 1}) - result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="validate should succeed on a timeseries collection", - ) - - -def test_validate_clustered_collection(database_client, collection): - """Test validate on a clustered collection succeeds.""" - coll_name = f"{collection.name}_clustered" - database_client.command( - "create", - coll_name, - clusteredIndex={"key": {"_id": 1}, "unique": True}, - ) - coll = database_client[coll_name] - coll.insert_one({"_id": 1, "x": 1}) - result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, - {"ok": 1.0}, - msg="validate should succeed on a clustered collection", - ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index b9ef08b2b..7fcc50c6c 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -1,23 +1,26 @@ """Tests for validate command core behavior. -Validates basic functionality, counts, consistency across calls, and comment parameter. +Validates basic functionality, counts, consistency across calls, comment parameter, +behavior on different collection types (capped, timeseries, clustered), and valid +option combinations. """ from __future__ import annotations +from datetime import datetime, timezone + import pytest from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, ) -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties -from documentdb_tests.framework.error_codes import NAMESPACE_NOT_FOUND_ERROR +from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial 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 [Core Behavior]: validate returns expected results for populated, -# empty, and non-existent collections and supports common parameters. +# Property [Core Behavior]: validate returns expected results for populated +# and empty collections and supports common parameters. CORE_BEHAVIOR_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "populated_collection", @@ -64,16 +67,6 @@ ), ] -# Property [Non-Existent Collection]: validate returns NamespaceNotFound for -# a collection that does not exist. -NON_EXISTENT_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "non_existent_collection", - error_code=NAMESPACE_NOT_FOUND_ERROR, - msg="validate should return NamespaceNotFound for a non-existent collection", - ), -] - @pytest.mark.parametrize("test", pytest_params(CORE_BEHAVIOR_TESTS)) def test_validate_core_behavior(collection, test): @@ -87,13 +80,6 @@ def test_validate_core_behavior(collection, test): assertProperties(result, test.checks, msg=test.msg, raw_res=True) -@pytest.mark.parametrize("test", pytest_params(NON_EXISTENT_TESTS)) -def test_validate_non_existent(collection, test): - """Test validate on non-existent collections returns expected error.""" - result = execute_command(collection, {"validate": f"{collection.name}_nonexistent_xyz"}) - assertFailureCode(result, test.error_code, msg=test.msg) - - def test_validate_consistent_across_calls(collection): """Test validate returns consistent results across multiple calls.""" collection.insert_many([{"_id": i, "x": i} for i in range(5)]) @@ -123,3 +109,113 @@ def test_validate_reflects_modifications(collection): raw_res=True, msg="validate should reflect updated nrecords after additional inserts", ) + + +def test_validate_capped_collection(database_client, collection): + """Test validate on a capped collection succeeds.""" + coll_name = f"{collection.name}_capped" + database_client.create_collection(coll_name, capped=True, size=1_048_576) + coll = database_client[coll_name] + coll.insert_one({"_id": 1, "x": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0, "valid": True}, + msg="validate should succeed on a capped collection", + ) + + +def test_validate_timeseries_collection(database_client, collection): + """Test validate on a time series collection succeeds.""" + coll_name = f"{collection.name}_timeseries" + database_client.create_collection( + coll_name, + timeseries={"timeField": "ts", "metaField": "meta"}, + ) + coll = database_client[coll_name] + coll.insert_one({"ts": datetime(2024, 1, 1, tzinfo=timezone.utc), "meta": "a", "v": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate should succeed on a timeseries collection", + ) + + +def test_validate_clustered_collection(database_client, collection): + """Test validate on a clustered collection succeeds.""" + coll_name = f"{collection.name}_clustered" + database_client.command( + "create", + coll_name, + clusteredIndex={"key": {"_id": 1}, "unique": True}, + ) + coll = database_client[coll_name] + coll.insert_one({"_id": 1, "x": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate should succeed on a clustered collection", + ) + + +# Property [Valid Combinations]: validate succeeds with valid option combinations. +VALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "all_defaults_explicit", + command={"full": False, "repair": False, "metadata": False, "checkBSONConformance": False}, + checks={"ok": Eq(1.0)}, + msg="validate should succeed with all options set to false explicitly", + ), + DiagnosticTestCase( + "full_true", + command={"full": True}, + checks={"ok": Eq(1.0)}, + msg="validate with full: true should succeed", + ), + DiagnosticTestCase( + "checkBSONConformance_true", + command={"checkBSONConformance": True}, + checks={"ok": Eq(1.0)}, + msg="validate with checkBSONConformance: true should succeed", + ), + DiagnosticTestCase( + "full_and_checkBSONConformance", + command={"full": True, "checkBSONConformance": True}, + checks={"ok": Eq(1.0)}, + msg="validate with full: true and checkBSONConformance: true should succeed", + ), + DiagnosticTestCase( + "metadata_true", + command={"metadata": True}, + checks={"ok": Eq(1.0)}, + msg="validate with metadata: true should succeed", + ), + DiagnosticTestCase( + "fixMultikey_true_alone", + command={"fixMultikey": True}, + checks={"ok": Eq(1.0)}, + msg="validate with fixMultikey: true alone should succeed", + ), + DiagnosticTestCase( + "repair_true_alone", + command={"repair": True}, + checks={"ok": Eq(1.0)}, + msg="validate with repair: true alone should succeed", + ), + DiagnosticTestCase( + "repair_true_with_fixMultikey", + command={"repair": True, "fixMultikey": True}, + checks={"ok": Eq(1.0)}, + msg="validate with repair: true and fixMultikey: true should succeed", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(VALID_COMBINATION_TESTS)) +def test_validate_valid_option_combinations(collection, test): + """Test that validate succeeds with valid option combinations.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name, **test.command}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py new file mode 100644 index 000000000..5eebe5cd9 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py @@ -0,0 +1,128 @@ +"""Tests for validate command error cases. + +Validates that validate returns expected errors for non-existent collections, +views, invalid option combinations, and truthy background values on standalone. +""" + +from __future__ import annotations + +import pytest + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import ( + COMMAND_NOT_SUPPORTED_ERROR, + COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + INVALID_OPTIONS_ERROR, + NAMESPACE_NOT_FOUND_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +# Property [Non-Existent Collection]: validate returns NamespaceNotFound for +# a collection that does not exist. +NON_EXISTENT_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "non_existent_collection", + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="validate should return NamespaceNotFound for a non-existent collection", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(NON_EXISTENT_TESTS)) +def test_validate_non_existent(collection, test): + """Test validate on non-existent collections returns expected error.""" + result = execute_command(collection, {"validate": f"{collection.name}_nonexistent_xyz"}) + assertFailureCode(result, test.error_code, msg=test.msg) + + +# Property [View Rejection]: validate rejects views. +def test_validate_view_rejected(database_client, collection): + """Test validate on a view returns CommandNotSupportedOnView error.""" + source_name = f"{collection.name}_view_source" + view_name = f"{collection.name}_view" + database_client.create_collection(source_name) + database_client[source_name].insert_one({"_id": 1}) + database_client.command("create", view_name, viewOn=source_name, pipeline=[]) + coll = database_client[view_name] + result = execute_command(coll, {"validate": coll.name}) + assertFailureCode( + result, + COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + msg="validate should reject views", + ) + + +# Property [Invalid Combinations]: validate rejects incompatible option combinations. +INVALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "metadata_with_full", + command={"metadata": True, "full": True}, + error_code=INVALID_OPTIONS_ERROR, + msg="validate should error with metadata: true and full: true", + ), + DiagnosticTestCase( + "metadata_with_repair", + command={"metadata": True, "repair": True, "fixMultikey": True}, + error_code=INVALID_OPTIONS_ERROR, + msg="validate should error with metadata: true and repair: true", + ), + DiagnosticTestCase( + "metadata_with_checkBSONConformance", + command={"metadata": True, "checkBSONConformance": True}, + error_code=INVALID_OPTIONS_ERROR, + msg="validate should error with metadata: true and checkBSONConformance: true", + ), + DiagnosticTestCase( + "checkBSONConformance_with_repair", + command={"checkBSONConformance": True, "repair": True, "fixMultikey": True}, + error_code=INVALID_OPTIONS_ERROR, + msg="validate should error with checkBSONConformance: true and repair: true", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(INVALID_COMBINATION_TESTS)) +def test_validate_invalid_option_combinations(collection, test): + """Test that validate errors on invalid option combinations.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name, **test.command}) + assertFailureCode(result, test.error_code, msg=test.msg) + + +# Property [Truthy Standalone Error]: validate rejects truthy background +# values on standalone mode. +TRUTHY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + command={"background": True}, + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: true not supported on standalone", + ), + DiagnosticTestCase( + "int32_1", + command={"background": 1}, + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: int 1 (truthy) not supported on standalone", + ), + DiagnosticTestCase( + "string", + command={"background": "true"}, + error_code=COMMAND_NOT_SUPPORTED_ERROR, + msg="background: string (truthy) not supported on standalone", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) +def test_validate_background_truthy_standalone_error(collection, test): + """Test that background with truthy values errors on standalone mode.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, **test.command}, + ) + assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py deleted file mode 100644 index 75399ddfe..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_option_combinations.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Tests for validate command option combinations and error conditions. - -Validates valid and invalid option combinations, repair/fixMultikey specifics, -and unrecognized field handling. -""" - -from __future__ import annotations - -import pytest - -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, -) -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties -from documentdb_tests.framework.error_codes import INVALID_OPTIONS_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 [Valid Combinations]: validate succeeds with valid option combinations. -VALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "all_defaults_explicit", - command={"full": False, "repair": False, "metadata": False, "checkBSONConformance": False}, - checks={"ok": Eq(1.0)}, - msg="validate should succeed with all options set to false explicitly", - ), - DiagnosticTestCase( - "full_true", - command={"full": True}, - checks={"ok": Eq(1.0)}, - msg="validate with full: true should succeed", - ), - DiagnosticTestCase( - "checkBSONConformance_true", - command={"checkBSONConformance": True}, - checks={"ok": Eq(1.0)}, - msg="validate with checkBSONConformance: true should succeed", - ), - DiagnosticTestCase( - "full_and_checkBSONConformance", - command={"full": True, "checkBSONConformance": True}, - checks={"ok": Eq(1.0)}, - msg="validate with full: true and checkBSONConformance: true should succeed", - ), - DiagnosticTestCase( - "metadata_true", - command={"metadata": True}, - checks={"ok": Eq(1.0)}, - msg="validate with metadata: true should succeed", - ), - DiagnosticTestCase( - "fixMultikey_true_alone", - command={"fixMultikey": True}, - checks={"ok": Eq(1.0)}, - msg="validate with fixMultikey: true alone should succeed", - ), - DiagnosticTestCase( - "repair_true_alone", - command={"repair": True}, - checks={"ok": Eq(1.0)}, - msg="validate with repair: true alone should succeed", - ), - DiagnosticTestCase( - "repair_true_with_fixMultikey", - command={"repair": True, "fixMultikey": True}, - checks={"ok": Eq(1.0)}, - msg="validate with repair: true and fixMultikey: true should succeed", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(VALID_COMBINATION_TESTS)) -def test_validate_valid_option_combinations(collection, test): - """Test that validate succeeds with valid option combinations.""" - collection.insert_one({"_id": 1}) - result = execute_command(collection, {"validate": collection.name, **test.command}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - -# Property [Invalid Combinations]: validate rejects incompatible option combinations. -INVALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "metadata_with_full", - command={"metadata": True, "full": True}, - error_code=INVALID_OPTIONS_ERROR, - msg="validate should error with metadata: true and full: true", - ), - DiagnosticTestCase( - "metadata_with_repair", - command={"metadata": True, "repair": True, "fixMultikey": True}, - error_code=INVALID_OPTIONS_ERROR, - msg="validate should error with metadata: true and repair: true", - ), - DiagnosticTestCase( - "metadata_with_checkBSONConformance", - command={"metadata": True, "checkBSONConformance": True}, - error_code=INVALID_OPTIONS_ERROR, - msg="validate should error with metadata: true and checkBSONConformance: true", - ), - DiagnosticTestCase( - "checkBSONConformance_with_repair", - command={"checkBSONConformance": True, "repair": True, "fixMultikey": True}, - error_code=INVALID_OPTIONS_ERROR, - msg="validate should error with checkBSONConformance: true and repair: true", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(INVALID_COMBINATION_TESTS)) -def test_validate_invalid_option_combinations(collection, test): - """Test that validate errors on invalid option combinations.""" - collection.insert_one({"_id": 1}) - result = execute_command(collection, {"validate": collection.name, **test.command}) - assertFailureCode(result, test.error_code, msg=test.msg) From 823bc5b25881b808dd42ac16dbb638fe304804d7 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Jun 2026 16:52:13 -0700 Subject: [PATCH 07/15] add missing tests Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_argument_type.py | 29 +- .../validate/test_validate_core_behavior.py | 7 + .../validate/test_validate_edge_cases.py | 53 +-- .../validate/test_validate_error_cases.py | 6 + .../commands/validate/test_validate_repair.py | 355 ++++++++++++++++++ .../test_validate_response_structure.py | 99 ++++- 6 files changed, 521 insertions(+), 28 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py index efad57046..831e05e50 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -16,7 +16,10 @@ DiagnosticTestCase, ) from documentdb_tests.framework.assertions import assertFailureCode, assertProperties -from documentdb_tests.framework.error_codes import INVALID_NAMESPACE_ERROR +from documentdb_tests.framework.error_codes import ( + INVALID_NAMESPACE_ERROR, + NAMESPACE_NOT_FOUND_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 @@ -147,6 +150,30 @@ def test_validate_rejects_non_string_types(collection, test): assertFailureCode(result, test.error_code, msg=test.msg) +# Property [Invalid String Values]: validate rejects invalid string values for collection name. +INVALID_STRING_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "empty_string", + command={"validate": ""}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty string for collection name", + ), + DiagnosticTestCase( + "dollar_prefix", + command={"validate": "$invalid"}, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="validate should return NamespaceNotFound for dollar-prefixed collection name", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(INVALID_STRING_TESTS)) +def test_validate_rejects_invalid_string_values(collection, test): + """Test that validate rejects invalid string values for collection name.""" + result = execute_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) + + # Property [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 7fcc50c6c..d3e220019 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -65,6 +65,13 @@ checks={"ok": Eq(1.0)}, msg="validate should succeed with comment parameter", ), + DiagnosticTestCase( + "unrecognized_field_ignored", + setup=[{"insert": "", "documents": [{"_id": 1}]}], + command={"unknownField": 1}, + checks={"ok": Eq(1.0), "valid": Eq(True)}, + msg="validate should ignore unrecognized fields and succeed", + ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index 9ab2862f4..c0ad182db 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -158,40 +158,43 @@ def test_validate_document_variety(collection, test): assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_validate_long_collection_name(database_client, collection): - """Test validate with a very long collection name succeeds.""" - db_name = database_client.name - # Namespace is "db.coll" so max coll length is 255 - len(db_name) - 1. - max_coll_len = 255 - len(db_name) - 1 - coll_name = f"{collection.name}_" + "a" * (max_coll_len - len(collection.name) - 1) - coll = database_client[coll_name] - coll.insert_one({"_id": 1}) - result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, {"ok": 1.0}, msg="validate should succeed with a long collection name" - ) +# Property [Collection Name Edge Cases]: validate succeeds with unusual but +# valid collection name suffixes. +COLLECTION_NAME_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "unicode_name", + command={"suffix": "_\u00e9\u00e8\u00ea"}, + checks={"ok": Eq(1.0)}, + msg="validate should succeed with unicode collection name", + ), + DiagnosticTestCase( + "numeric_looking_name", + command={"suffix": "_12345"}, + checks={"ok": Eq(1.0)}, + msg="validate should succeed with numeric-looking collection name", + ), +] -def test_validate_unicode_collection_name(database_client, collection): - """Test validate with unicode characters in collection name succeeds.""" - # U+00E9 e-acute, U+00E8 e-grave, U+00EA e-circumflex. - coll_name = f"{collection.name}_\u00e9\u00e8\u00ea" +@pytest.mark.parametrize("test", pytest_params(COLLECTION_NAME_TESTS)) +def test_validate_collection_name_edge_cases(database_client, collection, test): + """Test validate succeeds with unusual but valid collection names.""" + coll_name = f"{collection.name}{test.command['suffix']}" coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertSuccessPartial( - result, {"ok": 1.0}, msg="validate should succeed with unicode collection name" - ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) -def test_validate_numeric_looking_collection_name(database_client, collection): - """Test validate with a numeric-looking collection name succeeds.""" - coll_name = f"{collection.name}_12345" +def test_validate_long_collection_name(database_client, collection): + """Test validate with a collection name at the max namespace length.""" + db_name = database_client.name + # Namespace is "db.coll" so max coll length is 255 - len(db_name) - 1. + max_coll_len = 255 - len(db_name) - 1 + coll_name = f"{collection.name}_" + "a" * (max_coll_len - len(collection.name) - 1) coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) assertSuccessPartial( - result, - {"ok": 1.0}, - msg="validate should succeed with numeric-looking collection name", + result, {"ok": 1.0}, msg="validate should succeed with a long collection name" ) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py index 5eebe5cd9..f2e42a5c4 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py @@ -82,6 +82,12 @@ def test_validate_view_rejected(database_client, collection): error_code=INVALID_OPTIONS_ERROR, msg="validate should error with checkBSONConformance: true and repair: true", ), + DiagnosticTestCase( + "metadata_with_background", + command={"metadata": True, "background": True}, + error_code=INVALID_OPTIONS_ERROR, + msg="validate should error with metadata: true and background: true", + ), ] diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py new file mode 100644 index 000000000..e2ec8eea2 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py @@ -0,0 +1,355 @@ +"""Tests for validate command repair and fixMultikey options. + +Validates type coercion for repair and fixMultikey parameters and verifies +repairMode values for different repair configurations. +""" + +from __future__ import annotations + +from datetime import datetime, timezone + +import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp + +from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( + DiagnosticTestCase, +) +from documentdb_tests.framework.assertions import assertProperties +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.property_checks import Eq + +# Property [Type Coercion]: validate accepts all BSON types for the repair parameter via coercion. +REPAIR_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + command={"repair": True}, + checks={"ok": Eq(1.0)}, + msg="repair should accept bool true", + ), + DiagnosticTestCase( + "bool_false", + command={"repair": False}, + checks={"ok": Eq(1.0)}, + msg="repair should accept bool false", + ), + DiagnosticTestCase( + "int32_1", + command={"repair": 1}, + checks={"ok": Eq(1.0)}, + msg="repair should accept int32 1 (coerces to true)", + ), + DiagnosticTestCase( + "int32_0", + command={"repair": 0}, + checks={"ok": Eq(1.0)}, + msg="repair should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_1", + command={"repair": 1.0}, + checks={"ok": Eq(1.0)}, + msg="repair should accept double 1.0 (coerces to true)", + ), + DiagnosticTestCase( + "double_0", + command={"repair": 0.0}, + checks={"ok": Eq(1.0)}, + msg="repair should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_1", + command={"repair": Int64(1)}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Int64(1) (coerces to true)", + ), + DiagnosticTestCase( + "int64_0", + command={"repair": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_1", + command={"repair": Decimal128("1")}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Decimal128('1') (coerces to true)", + ), + DiagnosticTestCase( + "decimal128_0", + command={"repair": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + command={"repair": None}, + checks={"ok": Eq(1.0)}, + msg="repair should accept null (treated as omitted/false)", + ), + DiagnosticTestCase( + "string", + command={"repair": "true"}, + checks={"ok": Eq(1.0)}, + msg="repair should accept string (coerces to truthy)", + ), + DiagnosticTestCase( + "object", + command={"repair": {}}, + checks={"ok": Eq(1.0)}, + msg="repair should accept object (coerces to truthy)", + ), + DiagnosticTestCase( + "array", + command={"repair": []}, + checks={"ok": Eq(1.0)}, + msg="repair should accept array (coerces to truthy)", + ), + DiagnosticTestCase( + "binary", + command={"repair": Binary(b"")}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Binary (coerces to truthy)", + ), + DiagnosticTestCase( + "objectid", + command={"repair": ObjectId()}, + checks={"ok": Eq(1.0)}, + msg="repair should accept ObjectId (coerces to truthy)", + ), + DiagnosticTestCase( + "datetime", + command={"repair": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + checks={"ok": Eq(1.0)}, + msg="repair should accept datetime (coerces to truthy)", + ), + DiagnosticTestCase( + "regex", + command={"repair": Regex(".*")}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Regex (coerces to truthy)", + ), + DiagnosticTestCase( + "timestamp", + command={"repair": Timestamp(0, 0)}, + checks={"ok": Eq(1.0)}, + msg="repair should accept Timestamp (coerces to truthy)", + ), + DiagnosticTestCase( + "code", + command={"repair": Code("function(){}")}, + checks={"ok": Eq(1.0)}, + msg="repair should accept JavaScript Code (coerces to truthy)", + ), + DiagnosticTestCase( + "minkey", + command={"repair": MinKey()}, + checks={"ok": Eq(1.0)}, + msg="repair should accept MinKey (coerces to truthy)", + ), + DiagnosticTestCase( + "maxkey", + command={"repair": MaxKey()}, + checks={"ok": Eq(1.0)}, + msg="repair should accept MaxKey (coerces to truthy)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(REPAIR_TYPE_TESTS)) +def test_validate_repair_accepted_types(collection, test): + """Test that validate accepts all BSON types for the repair parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, **test.command}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [Type Coercion]: validate accepts all BSON types for the fixMultikey parameter. +FIXMULTIKEY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_true", + command={"fixMultikey": True}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept bool true", + ), + DiagnosticTestCase( + "bool_false", + command={"fixMultikey": False}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept bool false", + ), + DiagnosticTestCase( + "int32_1", + command={"fixMultikey": 1}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept int32 1 (coerces to true)", + ), + DiagnosticTestCase( + "int32_0", + command={"fixMultikey": 0}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_1", + command={"fixMultikey": 1.0}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept double 1.0 (coerces to true)", + ), + DiagnosticTestCase( + "double_0", + command={"fixMultikey": 0.0}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_1", + command={"fixMultikey": Int64(1)}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Int64(1) (coerces to true)", + ), + DiagnosticTestCase( + "int64_0", + command={"fixMultikey": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_1", + command={"fixMultikey": Decimal128("1")}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Decimal128('1') (coerces to true)", + ), + DiagnosticTestCase( + "decimal128_0", + command={"fixMultikey": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + command={"fixMultikey": None}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept null (treated as omitted/false)", + ), + DiagnosticTestCase( + "string", + command={"fixMultikey": "true"}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept string (coerces to truthy)", + ), + DiagnosticTestCase( + "object", + command={"fixMultikey": {}}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept object (coerces to truthy)", + ), + DiagnosticTestCase( + "array", + command={"fixMultikey": []}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept array (coerces to truthy)", + ), + DiagnosticTestCase( + "binary", + command={"fixMultikey": Binary(b"")}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Binary (coerces to truthy)", + ), + DiagnosticTestCase( + "objectid", + command={"fixMultikey": ObjectId()}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept ObjectId (coerces to truthy)", + ), + DiagnosticTestCase( + "datetime", + command={"fixMultikey": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept datetime (coerces to truthy)", + ), + DiagnosticTestCase( + "regex", + command={"fixMultikey": Regex(".*")}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Regex (coerces to truthy)", + ), + DiagnosticTestCase( + "timestamp", + command={"fixMultikey": Timestamp(0, 0)}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept Timestamp (coerces to truthy)", + ), + DiagnosticTestCase( + "code", + command={"fixMultikey": Code("function(){}")}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept JavaScript Code (coerces to truthy)", + ), + DiagnosticTestCase( + "minkey", + command={"fixMultikey": MinKey()}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept MinKey (coerces to truthy)", + ), + DiagnosticTestCase( + "maxkey", + command={"fixMultikey": MaxKey()}, + checks={"ok": Eq(1.0)}, + msg="fixMultikey should accept MaxKey (coerces to truthy)", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(FIXMULTIKEY_TYPE_TESTS)) +def test_validate_fixMultikey_accepted_types(collection, test): + """Test that validate accepts all BSON types for the fixMultikey parameter.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, **test.command}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +# Property [Repair Mode]: validate returns correct repairMode for different configurations. +REPAIR_MODE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "no_repair_mode_none", + command={}, + checks={"ok": Eq(1.0), "repairMode": Eq("None"), "repaired": Eq(False)}, + msg="validate should return repairMode: 'None' with no repair options", + ), + DiagnosticTestCase( + "repair_and_fixMultikey_mode_fix_errors", + command={"repair": True, "fixMultikey": True}, + checks={"ok": Eq(1.0), "repairMode": Eq("FixErrors"), "repaired": Eq(False)}, + msg="validate with repair+fixMultikey should return repairMode: 'FixErrors'", + ), + DiagnosticTestCase( + "fixMultikey_alone_mode_adjust", + command={"fixMultikey": True}, + checks={"ok": Eq(1.0), "repairMode": Eq("AdjustMultikey"), "repaired": Eq(False)}, + msg="validate with fixMultikey alone should return repairMode: 'AdjustMultikey'", + ), + DiagnosticTestCase( + "repair_alone_mode_fix_errors", + command={"repair": True}, + checks={"ok": Eq(1.0), "repairMode": Eq("FixErrors"), "repaired": Eq(False)}, + msg="validate with repair alone should return repairMode: 'FixErrors'", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(REPAIR_MODE_TESTS)) +def test_validate_repair_mode(collection, test): + """Test that validate returns correct repairMode for different configurations.""" + collection.insert_one({"_id": 1}) + result = execute_command( + collection, + {"validate": collection.name, **test.command}, + ) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index d699d84ab..992781eac 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -1,6 +1,7 @@ """Tests for validate command response structure. -Validates presence, types, and values of response fields for healthy collections. +Validates presence, types, and values of response fields for healthy collections, +including keysPerIndex/indexDetails structure and full/metadata mode responses. """ from __future__ import annotations @@ -13,7 +14,7 @@ from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params -from documentdb_tests.framework.property_checks import Eq, Exists, Gte, IsType +from documentdb_tests.framework.property_checks import Eq, Exists, Gte, IsType, NotExists # Property [Response Structure]: validate returns expected field types and values for # healthy collections. @@ -148,6 +149,16 @@ checks={"nIndexes": Gte(1)}, msg="validate should return nIndexes >= 1 (at least _id index)", ), + DiagnosticTestCase( + "repairMode_is_string", + checks={"repairMode": IsType("string")}, + msg="validate should return repairMode as a string", + ), + DiagnosticTestCase( + "repairMode_none_no_repair", + checks={"repairMode": Eq("None")}, + msg="validate should return repairMode: 'None' when no repair requested", + ), ] @@ -207,3 +218,87 @@ def test_validate_ns_matches_namespace(collection): {"ns": expected_ns}, msg="validate should return ns matching the actual namespace", ) + + +# Property [Detailed Structure]: validate returns correct sub-structure for +# keysPerIndex, indexDetails, and option-specific response shapes. +DETAILED_STRUCTURE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "keysPerIndex_entries", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}, + { + "createIndexes": "", + "indexes": [{"key": {"x": 1}, "name": "x_1"}], + }, + ], + checks={ + "keysPerIndex._id_": IsType("int"), + "keysPerIndex.x_1": IsType("int"), + }, + msg="keysPerIndex should have integer entries for each index", + ), + DiagnosticTestCase( + "indexDetails_entries", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}, + { + "createIndexes": "", + "indexes": [{"key": {"x": 1}, "name": "x_1"}], + }, + ], + checks={ + "indexDetails._id_.valid": Eq(True), + "indexDetails._id_.spec": IsType("object"), + "indexDetails.x_1.valid": Eq(True), + "indexDetails.x_1.spec": IsType("object"), + }, + msg="indexDetails entries should have valid: true and spec as object", + ), + DiagnosticTestCase( + "full_true_response", + setup=[{"insert": "", "documents": [{"_id": 1}]}], + command={"full": True}, + checks={ + "ok": Eq(1.0), + "valid": Eq(True), + "ns": IsType("string"), + "nrecords": IsType("int"), + "nIndexes": Gte(1), + "keysPerIndex": IsType("object"), + "indexDetails": IsType("object"), + "repairMode": Eq("None"), + }, + msg="validate with full: true should return all standard response fields", + ), + DiagnosticTestCase( + "metadata_true_response", + setup=[ + {"insert": "", "documents": [{"_id": i, "x": i} for i in range(5)]}, + ], + command={"metadata": True}, + checks={ + "ok": Eq(1.0), + "valid": Eq(True), + "ns": IsType("string"), + "nIndexes": IsType("int"), + "nrecords": NotExists(), + "nInvalidDocuments": NotExists(), + "nNonCompliantDocuments": NotExists(), + }, + msg="validate with metadata: true should return structural fields" + " but omit document-count fields", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(DETAILED_STRUCTURE_TESTS)) +def test_validate_detailed_structure(collection, test): + """Test validate response sub-structure for indexes and option modes.""" + for cmd in test.setup: + execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + cmd = {"validate": collection.name} + if test.command: + cmd.update(test.command) + result = execute_command(collection, cmd) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) From ac37999b5af9635b857fb24c9610892995fb1ca3 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 23 Jun 2026 10:31:04 -0700 Subject: [PATCH 08/15] remove exact duplicates Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_response_structure.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index 992781eac..782d1f6d8 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -114,11 +114,6 @@ checks={"valid": Eq(True)}, msg="validate should return valid: true for a healthy collection", ), - DiagnosticTestCase( - "repaired_false_no_repair", - checks={"repaired": Eq(False)}, - msg="validate should return repaired: false when no repair requested", - ), DiagnosticTestCase( "warnings_empty_healthy", checks={"warnings": Eq([])}, @@ -154,11 +149,6 @@ checks={"repairMode": IsType("string")}, msg="validate should return repairMode as a string", ), - DiagnosticTestCase( - "repairMode_none_no_repair", - checks={"repairMode": Eq("None")}, - msg="validate should return repairMode: 'None' when no repair requested", - ), ] From ec3203f461715b563b37cc643dc89002e3189640 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 23 Jun 2026 10:43:38 -0700 Subject: [PATCH 09/15] rename tests Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_core_behavior.py | 25 ++++---- .../validate/test_validate_edge_cases.py | 59 ++++++------------- .../validate/test_validate_indexes.py | 21 +++++++ 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index d3e220019..968aec305 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -167,10 +167,11 @@ def test_validate_clustered_collection(database_client, collection): ) -# Property [Valid Combinations]: validate succeeds with valid option combinations. -VALID_COMBINATION_TESTS: list[DiagnosticTestCase] = [ +# Property [Valid Options]: validate succeeds with each option individually +# and with compatible multi-option combinations. +VALID_OPTION_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( - "all_defaults_explicit", + "all_options_false", command={"full": False, "repair": False, "metadata": False, "checkBSONConformance": False}, checks={"ok": Eq(1.0)}, msg="validate should succeed with all options set to false explicitly", @@ -188,7 +189,7 @@ def test_validate_clustered_collection(database_client, collection): msg="validate with checkBSONConformance: true should succeed", ), DiagnosticTestCase( - "full_and_checkBSONConformance", + "full_with_checkBSONConformance", command={"full": True, "checkBSONConformance": True}, checks={"ok": Eq(1.0)}, msg="validate with full: true and checkBSONConformance: true should succeed", @@ -200,19 +201,19 @@ def test_validate_clustered_collection(database_client, collection): msg="validate with metadata: true should succeed", ), DiagnosticTestCase( - "fixMultikey_true_alone", + "fixMultikey_true", command={"fixMultikey": True}, checks={"ok": Eq(1.0)}, - msg="validate with fixMultikey: true alone should succeed", + msg="validate with fixMultikey: true should succeed", ), DiagnosticTestCase( - "repair_true_alone", + "repair_true", command={"repair": True}, checks={"ok": Eq(1.0)}, - msg="validate with repair: true alone should succeed", + msg="validate with repair: true should succeed", ), DiagnosticTestCase( - "repair_true_with_fixMultikey", + "repair_with_fixMultikey", command={"repair": True, "fixMultikey": True}, checks={"ok": Eq(1.0)}, msg="validate with repair: true and fixMultikey: true should succeed", @@ -220,9 +221,9 @@ def test_validate_clustered_collection(database_client, collection): ] -@pytest.mark.parametrize("test", pytest_params(VALID_COMBINATION_TESTS)) -def test_validate_valid_option_combinations(collection, test): - """Test that validate succeeds with valid option combinations.""" +@pytest.mark.parametrize("test", pytest_params(VALID_OPTION_TESTS)) +def test_validate_valid_options(collection, test): + """Test that validate succeeds with valid option values.""" collection.insert_one({"_id": 1}) result = execute_command(collection, {"validate": collection.name, **test.command}) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index c0ad182db..7b641b444 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -125,27 +125,6 @@ checks={"ok": Eq(1.0), "valid": Eq(True), "nrecords": Eq(2)}, msg="validate should return valid: true for documents with binary data", ), - DiagnosticTestCase( - "many_indexes", - setup=[ - { - "insert": "", - "documents": [{"_id": i, "a": i, "b": i, "c": i, "d": i, "e": i} for i in range(5)], - }, - { - "createIndexes": "", - "indexes": [ - {"key": {"a": 1}, "name": "a_1"}, - {"key": {"b": 1}, "name": "b_1"}, - {"key": {"c": 1}, "name": "c_1"}, - {"key": {"d": 1}, "name": "d_1"}, - {"key": {"e": 1}, "name": "e_1"}, - ], - }, - ], - checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(6)}, - msg="validate should report nIndexes: 6 with 5 secondary indexes", - ), ] @@ -158,32 +137,30 @@ def test_validate_document_variety(collection, test): assertProperties(result, test.checks, msg=test.msg, raw_res=True) -# Property [Collection Name Edge Cases]: validate succeeds with unusual but -# valid collection name suffixes. -COLLECTION_NAME_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "unicode_name", - command={"suffix": "_\u00e9\u00e8\u00ea"}, - checks={"ok": Eq(1.0)}, +def test_validate_unicode_collection_name(database_client, collection): + """Test validate succeeds with a unicode collection name.""" + coll_name = f"{collection.name}_\u00e9\u00e8\u00ea" + coll = database_client[coll_name] + coll.insert_one({"_id": 1}) + result = execute_command(coll, {"validate": coll.name}) + assertSuccessPartial( + result, + {"ok": 1.0}, msg="validate should succeed with unicode collection name", - ), - DiagnosticTestCase( - "numeric_looking_name", - command={"suffix": "_12345"}, - checks={"ok": Eq(1.0)}, - msg="validate should succeed with numeric-looking collection name", - ), -] + ) -@pytest.mark.parametrize("test", pytest_params(COLLECTION_NAME_TESTS)) -def test_validate_collection_name_edge_cases(database_client, collection, test): - """Test validate succeeds with unusual but valid collection names.""" - coll_name = f"{collection.name}{test.command['suffix']}" +def test_validate_numeric_looking_collection_name(database_client, collection): + """Test validate succeeds with a numeric-looking collection name.""" + coll_name = f"{collection.name}_12345" coll = database_client[coll_name] coll.insert_one({"_id": 1}) result = execute_command(coll, {"validate": coll.name}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) + assertSuccessPartial( + result, + {"ok": 1.0}, + msg="validate should succeed with numeric-looking collection name", + ) def test_validate_long_collection_name(database_client, collection): diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py index d22e53629..0fe64b5f2 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py @@ -198,6 +198,27 @@ checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(4)}, msg="validate should report all 4 indexes (_id + 3 secondary)", ), + DiagnosticTestCase( + "many_indexes", + setup=[ + { + "insert": "", + "documents": [{"_id": i, "a": i, "b": i, "c": i, "d": i, "e": i} for i in range(5)], + }, + { + "createIndexes": "", + "indexes": [ + {"key": {"a": 1}, "name": "a_1"}, + {"key": {"b": 1}, "name": "b_1"}, + {"key": {"c": 1}, "name": "c_1"}, + {"key": {"d": 1}, "name": "d_1"}, + {"key": {"e": 1}, "name": "e_1"}, + ], + }, + ], + checks={"ok": Eq(1.0), "valid": Eq(True), "nIndexes": Eq(6)}, + msg="validate should report nIndexes: 6 with 5 secondary indexes", + ), ] From 332eb7e1f824d56c3cfb0e6ddf36bdfa7cc9b2c9 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 23 Jun 2026 10:50:43 -0700 Subject: [PATCH 10/15] merge identical test functions Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_argument_type.py | 16 ++++------ .../validate/test_validate_error_cases.py | 22 +++++-------- .../commands/validate/test_validate_repair.py | 31 ++++--------------- .../test_validate_response_structure.py | 18 ++++------- 4 files changed, 25 insertions(+), 62 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py index 831e05e50..d4a28310d 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py @@ -143,13 +143,6 @@ ] -@pytest.mark.parametrize("test", pytest_params(INVALID_TYPE_TESTS)) -def test_validate_rejects_non_string_types(collection, test): - """Test that validate rejects non-string BSON types for the collection name.""" - result = execute_command(collection, test.command) - assertFailureCode(result, test.error_code, msg=test.msg) - - # Property [Invalid String Values]: validate rejects invalid string values for collection name. INVALID_STRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( @@ -167,9 +160,12 @@ def test_validate_rejects_non_string_types(collection, test): ] -@pytest.mark.parametrize("test", pytest_params(INVALID_STRING_TESTS)) -def test_validate_rejects_invalid_string_values(collection, test): - """Test that validate rejects invalid string values for collection name.""" +INVALID_ARGUMENT_TESTS = INVALID_TYPE_TESTS + INVALID_STRING_TESTS + + +@pytest.mark.parametrize("test", pytest_params(INVALID_ARGUMENT_TESTS)) +def test_validate_rejects_invalid_arguments(collection, test): + """Test that validate rejects non-string types and invalid string values.""" result = execute_command(collection, test.command) assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py index f2e42a5c4..a1b186c8a 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py @@ -91,14 +91,6 @@ def test_validate_view_rejected(database_client, collection): ] -@pytest.mark.parametrize("test", pytest_params(INVALID_COMBINATION_TESTS)) -def test_validate_invalid_option_combinations(collection, test): - """Test that validate errors on invalid option combinations.""" - collection.insert_one({"_id": 1}) - result = execute_command(collection, {"validate": collection.name, **test.command}) - assertFailureCode(result, test.error_code, msg=test.msg) - - # Property [Truthy Standalone Error]: validate rejects truthy background # values on standalone mode. TRUTHY_TYPE_TESTS: list[DiagnosticTestCase] = [ @@ -123,12 +115,12 @@ def test_validate_invalid_option_combinations(collection, test): ] -@pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) -def test_validate_background_truthy_standalone_error(collection, test): - """Test that background with truthy values errors on standalone mode.""" +OPTION_ERROR_TESTS = INVALID_COMBINATION_TESTS + TRUTHY_TYPE_TESTS + + +@pytest.mark.parametrize("test", pytest_params(OPTION_ERROR_TESTS)) +def test_validate_option_errors(collection, test): + """Test that validate errors on invalid option combinations and truthy background.""" collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) + result = execute_command(collection, {"validate": collection.name, **test.command}) assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py index e2ec8eea2..271dcc7fc 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py @@ -156,17 +156,6 @@ ] -@pytest.mark.parametrize("test", pytest_params(REPAIR_TYPE_TESTS)) -def test_validate_repair_accepted_types(collection, test): - """Test that validate accepts all BSON types for the repair parameter.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - # Property [Type Coercion]: validate accepts all BSON types for the fixMultikey parameter. FIXMULTIKEY_TYPE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( @@ -304,17 +293,6 @@ def test_validate_repair_accepted_types(collection, test): ] -@pytest.mark.parametrize("test", pytest_params(FIXMULTIKEY_TYPE_TESTS)) -def test_validate_fixMultikey_accepted_types(collection, test): - """Test that validate accepts all BSON types for the fixMultikey parameter.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - # Property [Repair Mode]: validate returns correct repairMode for different configurations. REPAIR_MODE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( @@ -344,9 +322,12 @@ def test_validate_fixMultikey_accepted_types(collection, test): ] -@pytest.mark.parametrize("test", pytest_params(REPAIR_MODE_TESTS)) -def test_validate_repair_mode(collection, test): - """Test that validate returns correct repairMode for different configurations.""" +REPAIR_AND_FIXMULTIKEY_TESTS = REPAIR_TYPE_TESTS + FIXMULTIKEY_TYPE_TESTS + REPAIR_MODE_TESTS + + +@pytest.mark.parametrize("test", pytest_params(REPAIR_AND_FIXMULTIKEY_TESTS)) +def test_validate_repair_and_fixMultikey(collection, test): + """Test repair/fixMultikey type coercion and repairMode values.""" collection.insert_one({"_id": 1}) result = execute_command( collection, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index 782d1f6d8..26cd8744d 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -189,15 +189,6 @@ def test_validate_response_properties(collection, test): ] -@pytest.mark.parametrize("test", pytest_params(RESPONSE_VALUE_TESTS)) -def test_validate_response_values(collection, test): - """Test validate response values match collection state.""" - for cmd in test.setup: - execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) - result = execute_command(collection, {"validate": collection.name}) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) - - def test_validate_ns_matches_namespace(collection): """Test validate ns field matches the actual database.collection namespace.""" collection.insert_one({"_id": 1}) @@ -282,9 +273,12 @@ def test_validate_ns_matches_namespace(collection): ] -@pytest.mark.parametrize("test", pytest_params(DETAILED_STRUCTURE_TESTS)) -def test_validate_detailed_structure(collection, test): - """Test validate response sub-structure for indexes and option modes.""" +RESPONSE_DETAIL_TESTS = RESPONSE_VALUE_TESTS + DETAILED_STRUCTURE_TESTS + + +@pytest.mark.parametrize("test", pytest_params(RESPONSE_DETAIL_TESTS)) +def test_validate_response_details(collection, test): + """Test validate response values, sub-structure, and option-specific shapes.""" for cmd in test.setup: execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) cmd = {"validate": collection.name} From 058d49b00aca72958171571aa544232a6e202bf7 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 23 Jun 2026 10:56:22 -0700 Subject: [PATCH 11/15] fix PR Signed-off-by: Alina (Xi) Li --- docs/testing/TEST_COVERAGE.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/testing/TEST_COVERAGE.md b/docs/testing/TEST_COVERAGE.md index 165ab70c5..50e253cb0 100644 --- a/docs/testing/TEST_COVERAGE.md +++ b/docs/testing/TEST_COVERAGE.md @@ -526,11 +526,9 @@ For each invalid_type in [string, object, array, ...]: - BSON type ordering → `tests/core/bson_types/`. Operators that use it (e.g. `$max`, `$gt`, `$sort`) get 1-2 wiring cases, not the full type-pair matrix. - Collation comparison → `tests/core/collation/`. Commands that accept `collation` test syntactic acceptance only. Sub-fields testing and semantic behavior is in 'tests/core/collation/'. - GeoJSON parsing and validation → `geospatial/specifiers/geometry/`. Geo operators that accept GeoJSON — test that the operator wires to the GeoJSON parser, not GeoJSON syntax tests. + - Wire-protocol namespace validation → TBD. Commands that take a namespace as their first field — single representative case, not the full character matrix. - Field path validation → Issue #118. - **Not foundational** (test exhaustively per command per §1 and §8e): - - Wire-protocol namespace validation — commands that take a collection name as their first field must test the full canonical BSON type set for non-string type rejection. Different engines may handle specific BSON types differently for specific commands, so this is a per-command input property, not a uniform foundational behavior. - **Test naming convention**: wiring tests typically use the suffix `_bson_wiring.py` or `__wiring.py`. Compare to `tests/core/operator/expressions/comparisons/gt/test_gt_bson_wiring.py` for the right shape — small, representative, explicitly named. From 060473544854b922099fe7f472f1c4453387b2cb Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 23 Jun 2026 11:01:03 -0700 Subject: [PATCH 12/15] put success together Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_argument_type.py | 222 ------------------ .../validate/test_validate_error_cases.py | 155 +++++++++++- .../validate/test_validate_metadata_type.py | 55 ++++- 3 files changed, 203 insertions(+), 229 deletions(-) delete mode 100644 documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py deleted file mode 100644 index d4a28310d..000000000 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_argument_type.py +++ /dev/null @@ -1,222 +0,0 @@ -"""Tests for validate command argument type handling. - -Validates that the validate parameter (collection name) must be a string and -rejects all other BSON types with the correct error code. Also validates that -the background parameter accepts falsy BSON types via coercion. -""" - -from __future__ import annotations - -from datetime import datetime, timezone - -import pytest -from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp - -from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( - DiagnosticTestCase, -) -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties -from documentdb_tests.framework.error_codes import ( - INVALID_NAMESPACE_ERROR, - NAMESPACE_NOT_FOUND_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 [Type Rejection]: validate rejects all non-string BSON types for the collection name. -INVALID_TYPE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "double", - command={"validate": 1.0}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject double for collection name", - ), - DiagnosticTestCase( - "int32", - command={"validate": 1}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject int32 for collection name", - ), - DiagnosticTestCase( - "int64", - command={"validate": Int64(1)}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject int64 for collection name", - ), - DiagnosticTestCase( - "decimal128", - command={"validate": Decimal128("1")}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject Decimal128 for collection name", - ), - DiagnosticTestCase( - "bool_true", - command={"validate": True}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject bool true for collection name", - ), - DiagnosticTestCase( - "bool_false", - command={"validate": False}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject bool false for collection name", - ), - DiagnosticTestCase( - "null", - command={"validate": None}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject null for collection name", - ), - DiagnosticTestCase( - "object", - command={"validate": {"a": 1}}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject object for collection name", - ), - DiagnosticTestCase( - "empty_object", - command={"validate": {}}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject empty object for collection name", - ), - DiagnosticTestCase( - "array", - command={"validate": [1, 2]}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject array for collection name", - ), - DiagnosticTestCase( - "empty_array", - command={"validate": []}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject empty array for collection name", - ), - DiagnosticTestCase( - "binary", - command={"validate": Binary(b"data")}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject Binary for collection name", - ), - DiagnosticTestCase( - "objectid", - command={"validate": ObjectId()}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject ObjectId for collection name", - ), - DiagnosticTestCase( - "datetime", - command={"validate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject datetime for collection name", - ), - DiagnosticTestCase( - "regex", - command={"validate": Regex(".*")}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject Regex for collection name", - ), - DiagnosticTestCase( - "timestamp", - command={"validate": Timestamp(0, 0)}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject Timestamp for collection name", - ), - DiagnosticTestCase( - "code", - command={"validate": Code("function(){}")}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject JavaScript Code for collection name", - ), - DiagnosticTestCase( - "minkey", - command={"validate": MinKey()}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject MinKey for collection name", - ), - DiagnosticTestCase( - "maxkey", - command={"validate": MaxKey()}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject MaxKey for collection name", - ), -] - - -# Property [Invalid String Values]: validate rejects invalid string values for collection name. -INVALID_STRING_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "empty_string", - command={"validate": ""}, - error_code=INVALID_NAMESPACE_ERROR, - msg="validate should reject empty string for collection name", - ), - DiagnosticTestCase( - "dollar_prefix", - command={"validate": "$invalid"}, - error_code=NAMESPACE_NOT_FOUND_ERROR, - msg="validate should return NamespaceNotFound for dollar-prefixed collection name", - ), -] - - -INVALID_ARGUMENT_TESTS = INVALID_TYPE_TESTS + INVALID_STRING_TESTS - - -@pytest.mark.parametrize("test", pytest_params(INVALID_ARGUMENT_TESTS)) -def test_validate_rejects_invalid_arguments(collection, test): - """Test that validate rejects non-string types and invalid string values.""" - result = execute_command(collection, test.command) - assertFailureCode(result, test.error_code, msg=test.msg) - - -# Property [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. -FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ - DiagnosticTestCase( - "bool_false", - command={"background": False}, - checks={"ok": Eq(1.0)}, - msg="background should accept bool false", - ), - DiagnosticTestCase( - "int32_0", - command={"background": 0}, - checks={"ok": Eq(1.0)}, - msg="background should accept int32 0 (coerces to false)", - ), - DiagnosticTestCase( - "double_0", - command={"background": 0.0}, - checks={"ok": Eq(1.0)}, - msg="background should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_0", - command={"background": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="background should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"background": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="background should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"background": None}, - checks={"ok": Eq(1.0)}, - msg="background should accept null (treated as omitted/false)", - ), -] - - -@pytest.mark.parametrize("test", pytest_params(FALSY_TYPE_TESTS)) -def test_validate_background_falsy_types(collection, test): - """Test that validate accepts falsy types for the background parameter.""" - collection.insert_one({"_id": 1}) - result = execute_command( - collection, - {"validate": collection.name, **test.command}, - ) - assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py index a1b186c8a..65db8b3ca 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py @@ -1,12 +1,16 @@ """Tests for validate command error cases. -Validates that validate returns expected errors for non-existent collections, -views, invalid option combinations, and truthy background values on standalone. +Validates that validate returns expected errors for non-string collection name +types, invalid string values, non-existent collections, views, invalid option +combinations, and truthy background values on standalone. """ from __future__ import annotations +from datetime import datetime, timezone + import pytest +from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, @@ -15,12 +19,159 @@ from documentdb_tests.framework.error_codes import ( COMMAND_NOT_SUPPORTED_ERROR, COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + INVALID_NAMESPACE_ERROR, INVALID_OPTIONS_ERROR, NAMESPACE_NOT_FOUND_ERROR, ) from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params +# Property [Type Rejection]: validate rejects all non-string BSON types for the collection name. +INVALID_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "double", + command={"validate": 1.0}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject double for collection name", + ), + DiagnosticTestCase( + "int32", + command={"validate": 1}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject int32 for collection name", + ), + DiagnosticTestCase( + "int64", + command={"validate": Int64(1)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject int64 for collection name", + ), + DiagnosticTestCase( + "decimal128", + command={"validate": Decimal128("1")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Decimal128 for collection name", + ), + DiagnosticTestCase( + "bool_true", + command={"validate": True}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject bool true for collection name", + ), + DiagnosticTestCase( + "bool_false", + command={"validate": False}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject bool false for collection name", + ), + DiagnosticTestCase( + "null", + command={"validate": None}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject null for collection name", + ), + DiagnosticTestCase( + "object", + command={"validate": {"a": 1}}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject object for collection name", + ), + DiagnosticTestCase( + "empty_object", + command={"validate": {}}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty object for collection name", + ), + DiagnosticTestCase( + "array", + command={"validate": [1, 2]}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject array for collection name", + ), + DiagnosticTestCase( + "empty_array", + command={"validate": []}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty array for collection name", + ), + DiagnosticTestCase( + "binary", + command={"validate": Binary(b"data")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Binary for collection name", + ), + DiagnosticTestCase( + "objectid", + command={"validate": ObjectId()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject ObjectId for collection name", + ), + DiagnosticTestCase( + "datetime", + command={"validate": datetime(2024, 1, 1, tzinfo=timezone.utc)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject datetime for collection name", + ), + DiagnosticTestCase( + "regex", + command={"validate": Regex(".*")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Regex for collection name", + ), + DiagnosticTestCase( + "timestamp", + command={"validate": Timestamp(0, 0)}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject Timestamp for collection name", + ), + DiagnosticTestCase( + "code", + command={"validate": Code("function(){}")}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject JavaScript Code for collection name", + ), + DiagnosticTestCase( + "minkey", + command={"validate": MinKey()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject MinKey for collection name", + ), + DiagnosticTestCase( + "maxkey", + command={"validate": MaxKey()}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject MaxKey for collection name", + ), +] + + +# Property [Invalid String Values]: validate rejects invalid string values for collection name. +INVALID_STRING_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "empty_string", + command={"validate": ""}, + error_code=INVALID_NAMESPACE_ERROR, + msg="validate should reject empty string for collection name", + ), + DiagnosticTestCase( + "dollar_prefix", + command={"validate": "$invalid"}, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="validate should return NamespaceNotFound for dollar-prefixed collection name", + ), +] + + +INVALID_ARGUMENT_TESTS = INVALID_TYPE_TESTS + INVALID_STRING_TESTS + + +@pytest.mark.parametrize("test", pytest_params(INVALID_ARGUMENT_TESTS)) +def test_validate_rejects_invalid_arguments(collection, test): + """Test that validate rejects non-string types and invalid string values.""" + result = execute_command(collection, test.command) + assertFailureCode(result, test.error_code, msg=test.msg) + + # Property [Non-Existent Collection]: validate returns NamespaceNotFound for # a collection that does not exist. NON_EXISTENT_TESTS: list[DiagnosticTestCase] = [ diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py index 148a9580a..24bf22afc 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py @@ -1,6 +1,7 @@ -"""Tests for validate command 'metadata' parameter type coercion. +"""Tests for validate command 'metadata' and 'background' parameter type coercion. -Validates that the metadata parameter accepts all BSON types via coercion. +Validates that the metadata parameter accepts all BSON types via coercion +and that the background parameter accepts falsy BSON types via coercion. """ from __future__ import annotations @@ -155,9 +156,53 @@ ] -@pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) -def test_validate_metadata_accepted_types(collection, test): - """Test that validate accepts all BSON types for the metadata parameter.""" +# Property [Falsy Type Acceptance]: validate accepts falsy BSON types for the background parameter. +FALSY_TYPE_TESTS: list[DiagnosticTestCase] = [ + DiagnosticTestCase( + "bool_false", + command={"background": False}, + checks={"ok": Eq(1.0)}, + msg="background should accept bool false", + ), + DiagnosticTestCase( + "int32_0", + command={"background": 0}, + checks={"ok": Eq(1.0)}, + msg="background should accept int32 0 (coerces to false)", + ), + DiagnosticTestCase( + "double_0", + command={"background": 0.0}, + checks={"ok": Eq(1.0)}, + msg="background should accept double 0.0 (coerces to false)", + ), + DiagnosticTestCase( + "int64_0", + command={"background": Int64(0)}, + checks={"ok": Eq(1.0)}, + msg="background should accept Int64(0) (coerces to false)", + ), + DiagnosticTestCase( + "decimal128_0", + command={"background": Decimal128("0")}, + checks={"ok": Eq(1.0)}, + msg="background should accept Decimal128('0') (coerces to false)", + ), + DiagnosticTestCase( + "null", + command={"background": None}, + checks={"ok": Eq(1.0)}, + msg="background should accept null (treated as omitted/false)", + ), +] + + +METADATA_AND_BACKGROUND_TESTS = ACCEPTED_TYPE_TESTS + FALSY_TYPE_TESTS + + +@pytest.mark.parametrize("test", pytest_params(METADATA_AND_BACKGROUND_TESTS)) +def test_validate_metadata_and_background_types(collection, test): + """Test type coercion for metadata and background parameters.""" collection.insert_one({"_id": 1}) result = execute_command( collection, From d2322a88aec2f4b47e40493054d1435d95ca6775 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 25 Jun 2026 11:45:24 -0700 Subject: [PATCH 13/15] remove near-identical BSON cases Signed-off-by: Alina (Xi) Li --- ...y => test_validate_bool_param_coercion.py} | 6 +- ...test_validate_checkBSONConformance_type.py | 132 +-------- .../validate/test_validate_metadata_type.py | 131 +-------- .../commands/validate/test_validate_repair.py | 262 +----------------- 4 files changed, 34 insertions(+), 497 deletions(-) rename documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/{test_validate_full_type.py => test_validate_bool_param_coercion.py} (94%) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_bool_param_coercion.py similarity index 94% rename from documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py rename to documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_bool_param_coercion.py index b8509ae0b..e180a2081 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_full_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_bool_param_coercion.py @@ -1,6 +1,8 @@ -"""Tests for validate command 'full' parameter type coercion. +"""Canonical BSON type coercion matrix for validate boolean parameters. -Validates that the full parameter accepts all BSON types via coercion. +Uses 'full' as the representative parameter. All boolean parameters +(full, metadata, checkBSONConformance, repair, fixMultikey) share the +same coercion logic; other per-parameter files contain only wiring tests. """ from __future__ import annotations diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py index addedec24..e45f5fcf4 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_checkBSONConformance_type.py @@ -1,15 +1,12 @@ -"""Tests for validate command 'checkBSONConformance' parameter type coercion. +"""Wiring tests for validate command 'checkBSONConformance' parameter type coercion. -Validates that the checkBSONConformance parameter accepts all BSON types via -coercion. +Confirms checkBSONConformance uses the same boolean coercion as other params. +The full BSON type matrix is in test_validate_bool_param_coercion.py. """ from __future__ import annotations -from datetime import datetime, timezone - import pytest -from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, @@ -19,147 +16,32 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq -# Property [Type Coercion]: validate accepts all BSON types for the -# checkBSONConformance parameter via coercion. -ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ +# Property [Type Coercion Wiring]: checkBSONConformance delegates to shared boolean coercion. +WIRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", command={"checkBSONConformance": True}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept bool true", ), - DiagnosticTestCase( - "bool_false", - command={"checkBSONConformance": False}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept bool false", - ), - DiagnosticTestCase( - "int32_1", - command={"checkBSONConformance": 1}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept int32 1 (coerces to true)", - ), DiagnosticTestCase( "int32_0", command={"checkBSONConformance": 0}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept int32 0 (coerces to false)", ), - DiagnosticTestCase( - "double_1", - command={"checkBSONConformance": 1.0}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept double 1.0 (coerces to true)", - ), - DiagnosticTestCase( - "double_0", - command={"checkBSONConformance": 0.0}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_1", - command={"checkBSONConformance": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Int64(1) (coerces to true)", - ), - DiagnosticTestCase( - "int64_0", - command={"checkBSONConformance": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_1", - command={"checkBSONConformance": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Decimal128('1') (coerces to true)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"checkBSONConformance": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"checkBSONConformance": None}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept null (treated as omitted/false)", - ), DiagnosticTestCase( "string", command={"checkBSONConformance": "true"}, checks={"ok": Eq(1.0)}, msg="checkBSONConformance should accept string (coerces to truthy)", ), - DiagnosticTestCase( - "object", - command={"checkBSONConformance": {}}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept object (coerces to truthy)", - ), - DiagnosticTestCase( - "array", - command={"checkBSONConformance": []}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept array (coerces to truthy)", - ), - DiagnosticTestCase( - "binary", - command={"checkBSONConformance": Binary(b"")}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Binary (coerces to truthy)", - ), - DiagnosticTestCase( - "objectid", - command={"checkBSONConformance": ObjectId()}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept ObjectId (coerces to truthy)", - ), - DiagnosticTestCase( - "datetime", - command={"checkBSONConformance": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept datetime (coerces to truthy)", - ), - DiagnosticTestCase( - "regex", - command={"checkBSONConformance": Regex(".*")}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Regex (coerces to truthy)", - ), - DiagnosticTestCase( - "timestamp", - command={"checkBSONConformance": Timestamp(0, 0)}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept Timestamp (coerces to truthy)", - ), - DiagnosticTestCase( - "code", - command={"checkBSONConformance": Code("function(){}")}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept JavaScript Code (coerces to truthy)", - ), - DiagnosticTestCase( - "minkey", - command={"checkBSONConformance": MinKey()}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept MinKey (coerces to truthy)", - ), - DiagnosticTestCase( - "maxkey", - command={"checkBSONConformance": MaxKey()}, - checks={"ok": Eq(1.0)}, - msg="checkBSONConformance should accept MaxKey (coerces to truthy)", - ), ] -@pytest.mark.parametrize("test", pytest_params(ACCEPTED_TYPE_TESTS)) +@pytest.mark.parametrize("test", pytest_params(WIRING_TESTS)) def test_validate_checkBSONConformance_accepted_types(collection, test): - """Test that validate accepts all BSON types for checkBSONConformance.""" + """Test that checkBSONConformance uses shared boolean coercion.""" collection.insert_one({"_id": 1}) result = execute_command( collection, diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py index 24bf22afc..4f183e395 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_metadata_type.py @@ -1,15 +1,14 @@ -"""Tests for validate command 'metadata' and 'background' parameter type coercion. +"""Wiring tests for validate 'metadata' and 'background' parameter type coercion. -Validates that the metadata parameter accepts all BSON types via coercion -and that the background parameter accepts falsy BSON types via coercion. +Confirms metadata uses the same boolean coercion as other params (wiring only). +The full BSON type matrix is in test_validate_bool_param_coercion.py. +Also tests that the background parameter accepts falsy BSON types. """ from __future__ import annotations -from datetime import datetime, timezone - import pytest -from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp +from bson import Decimal128, Int64 from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, @@ -19,140 +18,26 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq -# Property [Type Coercion]: validate accepts all BSON types for the metadata parameter via coercion. -ACCEPTED_TYPE_TESTS: list[DiagnosticTestCase] = [ +# Property [Type Coercion Wiring]: metadata delegates to shared boolean coercion. +METADATA_WIRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "bool_true", command={"metadata": True}, checks={"ok": Eq(1.0)}, msg="metadata should accept bool true", ), - DiagnosticTestCase( - "bool_false", - command={"metadata": False}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept bool false", - ), - DiagnosticTestCase( - "int32_1", - command={"metadata": 1}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept int32 1 (coerces to true)", - ), DiagnosticTestCase( "int32_0", command={"metadata": 0}, checks={"ok": Eq(1.0)}, msg="metadata should accept int32 0 (coerces to false)", ), - DiagnosticTestCase( - "double_1", - command={"metadata": 1.0}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept double 1.0 (coerces to true)", - ), - DiagnosticTestCase( - "double_0", - command={"metadata": 0.0}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_1", - command={"metadata": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Int64(1) (coerces to true)", - ), - DiagnosticTestCase( - "int64_0", - command={"metadata": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_1", - command={"metadata": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Decimal128('1') (coerces to true)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"metadata": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"metadata": None}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept null (treated as omitted/false)", - ), DiagnosticTestCase( "string", command={"metadata": "true"}, checks={"ok": Eq(1.0)}, msg="metadata should accept string (coerces to truthy)", ), - DiagnosticTestCase( - "object", - command={"metadata": {}}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept object (coerces to truthy)", - ), - DiagnosticTestCase( - "array", - command={"metadata": []}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept array (coerces to truthy)", - ), - DiagnosticTestCase( - "binary", - command={"metadata": Binary(b"")}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Binary (coerces to truthy)", - ), - DiagnosticTestCase( - "objectid", - command={"metadata": ObjectId()}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept ObjectId (coerces to truthy)", - ), - DiagnosticTestCase( - "datetime", - command={"metadata": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept datetime (coerces to truthy)", - ), - DiagnosticTestCase( - "regex", - command={"metadata": Regex(".*")}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Regex (coerces to truthy)", - ), - DiagnosticTestCase( - "timestamp", - command={"metadata": Timestamp(0, 0)}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept Timestamp (coerces to truthy)", - ), - DiagnosticTestCase( - "code", - command={"metadata": Code("function(){}")}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept JavaScript Code (coerces to truthy)", - ), - DiagnosticTestCase( - "minkey", - command={"metadata": MinKey()}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept MinKey (coerces to truthy)", - ), - DiagnosticTestCase( - "maxkey", - command={"metadata": MaxKey()}, - checks={"ok": Eq(1.0)}, - msg="metadata should accept MaxKey (coerces to truthy)", - ), ] @@ -197,7 +82,7 @@ ] -METADATA_AND_BACKGROUND_TESTS = ACCEPTED_TYPE_TESTS + FALSY_TYPE_TESTS +METADATA_AND_BACKGROUND_TESTS = METADATA_WIRING_TESTS + FALSY_TYPE_TESTS @pytest.mark.parametrize("test", pytest_params(METADATA_AND_BACKGROUND_TESTS)) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py index 271dcc7fc..748fa43e1 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py @@ -1,15 +1,13 @@ -"""Tests for validate command repair and fixMultikey options. +"""Wiring tests for validate command repair and fixMultikey options. -Validates type coercion for repair and fixMultikey parameters and verifies -repairMode values for different repair configurations. +Confirms repair and fixMultikey use the same boolean coercion as other params +(wiring only). The full BSON type matrix is in test_validate_bool_param_coercion.py. +Also verifies repairMode values for different repair configurations. """ from __future__ import annotations -from datetime import datetime, timezone - import pytest -from bson import Binary, Code, Decimal128, Int64, MaxKey, MinKey, ObjectId, Regex, Timestamp from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, @@ -19,280 +17,50 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq -# Property [Type Coercion]: validate accepts all BSON types for the repair parameter via coercion. -REPAIR_TYPE_TESTS: list[DiagnosticTestCase] = [ +# Property [Type Coercion Wiring]: repair delegates to shared boolean coercion. +REPAIR_WIRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( - "bool_true", + "repair_bool_true", command={"repair": True}, checks={"ok": Eq(1.0)}, msg="repair should accept bool true", ), DiagnosticTestCase( - "bool_false", - command={"repair": False}, - checks={"ok": Eq(1.0)}, - msg="repair should accept bool false", - ), - DiagnosticTestCase( - "int32_1", - command={"repair": 1}, - checks={"ok": Eq(1.0)}, - msg="repair should accept int32 1 (coerces to true)", - ), - DiagnosticTestCase( - "int32_0", + "repair_int32_0", command={"repair": 0}, checks={"ok": Eq(1.0)}, msg="repair should accept int32 0 (coerces to false)", ), DiagnosticTestCase( - "double_1", - command={"repair": 1.0}, - checks={"ok": Eq(1.0)}, - msg="repair should accept double 1.0 (coerces to true)", - ), - DiagnosticTestCase( - "double_0", - command={"repair": 0.0}, - checks={"ok": Eq(1.0)}, - msg="repair should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_1", - command={"repair": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Int64(1) (coerces to true)", - ), - DiagnosticTestCase( - "int64_0", - command={"repair": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_1", - command={"repair": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Decimal128('1') (coerces to true)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"repair": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"repair": None}, - checks={"ok": Eq(1.0)}, - msg="repair should accept null (treated as omitted/false)", - ), - DiagnosticTestCase( - "string", + "repair_string", command={"repair": "true"}, checks={"ok": Eq(1.0)}, msg="repair should accept string (coerces to truthy)", ), - DiagnosticTestCase( - "object", - command={"repair": {}}, - checks={"ok": Eq(1.0)}, - msg="repair should accept object (coerces to truthy)", - ), - DiagnosticTestCase( - "array", - command={"repair": []}, - checks={"ok": Eq(1.0)}, - msg="repair should accept array (coerces to truthy)", - ), - DiagnosticTestCase( - "binary", - command={"repair": Binary(b"")}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Binary (coerces to truthy)", - ), - DiagnosticTestCase( - "objectid", - command={"repair": ObjectId()}, - checks={"ok": Eq(1.0)}, - msg="repair should accept ObjectId (coerces to truthy)", - ), - DiagnosticTestCase( - "datetime", - command={"repair": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - checks={"ok": Eq(1.0)}, - msg="repair should accept datetime (coerces to truthy)", - ), - DiagnosticTestCase( - "regex", - command={"repair": Regex(".*")}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Regex (coerces to truthy)", - ), - DiagnosticTestCase( - "timestamp", - command={"repair": Timestamp(0, 0)}, - checks={"ok": Eq(1.0)}, - msg="repair should accept Timestamp (coerces to truthy)", - ), - DiagnosticTestCase( - "code", - command={"repair": Code("function(){}")}, - checks={"ok": Eq(1.0)}, - msg="repair should accept JavaScript Code (coerces to truthy)", - ), - DiagnosticTestCase( - "minkey", - command={"repair": MinKey()}, - checks={"ok": Eq(1.0)}, - msg="repair should accept MinKey (coerces to truthy)", - ), - DiagnosticTestCase( - "maxkey", - command={"repair": MaxKey()}, - checks={"ok": Eq(1.0)}, - msg="repair should accept MaxKey (coerces to truthy)", - ), ] - -# Property [Type Coercion]: validate accepts all BSON types for the fixMultikey parameter. -FIXMULTIKEY_TYPE_TESTS: list[DiagnosticTestCase] = [ +# Property [Type Coercion Wiring]: fixMultikey delegates to shared boolean coercion. +FIXMULTIKEY_WIRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( - "bool_true", + "fixMultikey_bool_true", command={"fixMultikey": True}, checks={"ok": Eq(1.0)}, msg="fixMultikey should accept bool true", ), DiagnosticTestCase( - "bool_false", - command={"fixMultikey": False}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept bool false", - ), - DiagnosticTestCase( - "int32_1", - command={"fixMultikey": 1}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept int32 1 (coerces to true)", - ), - DiagnosticTestCase( - "int32_0", + "fixMultikey_int32_0", command={"fixMultikey": 0}, checks={"ok": Eq(1.0)}, msg="fixMultikey should accept int32 0 (coerces to false)", ), DiagnosticTestCase( - "double_1", - command={"fixMultikey": 1.0}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept double 1.0 (coerces to true)", - ), - DiagnosticTestCase( - "double_0", - command={"fixMultikey": 0.0}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept double 0.0 (coerces to false)", - ), - DiagnosticTestCase( - "int64_1", - command={"fixMultikey": Int64(1)}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Int64(1) (coerces to true)", - ), - DiagnosticTestCase( - "int64_0", - command={"fixMultikey": Int64(0)}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Int64(0) (coerces to false)", - ), - DiagnosticTestCase( - "decimal128_1", - command={"fixMultikey": Decimal128("1")}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Decimal128('1') (coerces to true)", - ), - DiagnosticTestCase( - "decimal128_0", - command={"fixMultikey": Decimal128("0")}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Decimal128('0') (coerces to false)", - ), - DiagnosticTestCase( - "null", - command={"fixMultikey": None}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept null (treated as omitted/false)", - ), - DiagnosticTestCase( - "string", + "fixMultikey_string", command={"fixMultikey": "true"}, checks={"ok": Eq(1.0)}, msg="fixMultikey should accept string (coerces to truthy)", ), - DiagnosticTestCase( - "object", - command={"fixMultikey": {}}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept object (coerces to truthy)", - ), - DiagnosticTestCase( - "array", - command={"fixMultikey": []}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept array (coerces to truthy)", - ), - DiagnosticTestCase( - "binary", - command={"fixMultikey": Binary(b"")}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Binary (coerces to truthy)", - ), - DiagnosticTestCase( - "objectid", - command={"fixMultikey": ObjectId()}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept ObjectId (coerces to truthy)", - ), - DiagnosticTestCase( - "datetime", - command={"fixMultikey": datetime(2024, 1, 1, tzinfo=timezone.utc)}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept datetime (coerces to truthy)", - ), - DiagnosticTestCase( - "regex", - command={"fixMultikey": Regex(".*")}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Regex (coerces to truthy)", - ), - DiagnosticTestCase( - "timestamp", - command={"fixMultikey": Timestamp(0, 0)}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept Timestamp (coerces to truthy)", - ), - DiagnosticTestCase( - "code", - command={"fixMultikey": Code("function(){}")}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept JavaScript Code (coerces to truthy)", - ), - DiagnosticTestCase( - "minkey", - command={"fixMultikey": MinKey()}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept MinKey (coerces to truthy)", - ), - DiagnosticTestCase( - "maxkey", - command={"fixMultikey": MaxKey()}, - checks={"ok": Eq(1.0)}, - msg="fixMultikey should accept MaxKey (coerces to truthy)", - ), ] - # Property [Repair Mode]: validate returns correct repairMode for different configurations. REPAIR_MODE_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( @@ -322,7 +90,7 @@ ] -REPAIR_AND_FIXMULTIKEY_TESTS = REPAIR_TYPE_TESTS + FIXMULTIKEY_TYPE_TESTS + REPAIR_MODE_TESTS +REPAIR_AND_FIXMULTIKEY_TESTS = REPAIR_WIRING_TESTS + FIXMULTIKEY_WIRING_TESTS + REPAIR_MODE_TESTS @pytest.mark.parametrize("test", pytest_params(REPAIR_AND_FIXMULTIKEY_TESTS)) From 2cd3cf6f0171d8d4fbcd358c7b59982673872f3d Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 25 Jun 2026 12:11:19 -0700 Subject: [PATCH 14/15] add validate_repair and skip on tests that fail on replica_set Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_core_behavior.py | 15 +++++++++++++++ .../validate/test_validate_error_cases.py | 14 ++++++++++---- .../commands/validate/test_validate_repair.py | 2 ++ documentdb_tests/framework/preconditions.py | 6 ++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 968aec305..041961665 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -200,6 +200,12 @@ def test_validate_clustered_collection(database_client, collection): checks={"ok": Eq(1.0)}, msg="validate with metadata: true should succeed", ), +] + + +# Property [Valid Repair Options]: validate succeeds with repair/fixMultikey +# options (standalone only). +VALID_REPAIR_OPTION_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( "fixMultikey_true", command={"fixMultikey": True}, @@ -227,3 +233,12 @@ def test_validate_valid_options(collection, test): collection.insert_one({"_id": 1}) result = execute_command(collection, {"validate": collection.name, **test.command}) assertProperties(result, test.checks, msg=test.msg, raw_res=True) + + +@pytest.mark.requires(validate_repair=True) +@pytest.mark.parametrize("test", pytest_params(VALID_REPAIR_OPTION_TESTS)) +def test_validate_valid_repair_options(collection, test): + """Test that validate succeeds with repair/fixMultikey options on standalone.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name, **test.command}) + assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py index 65db8b3ca..4847bdd01 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_error_cases.py @@ -266,12 +266,18 @@ def test_validate_view_rejected(database_client, collection): ] -OPTION_ERROR_TESTS = INVALID_COMBINATION_TESTS + TRUTHY_TYPE_TESTS +@pytest.mark.parametrize("test", pytest_params(INVALID_COMBINATION_TESTS)) +def test_validate_option_errors(collection, test): + """Test that validate errors on invalid option combinations.""" + collection.insert_one({"_id": 1}) + result = execute_command(collection, {"validate": collection.name, **test.command}) + assertFailureCode(result, test.error_code, msg=test.msg) -@pytest.mark.parametrize("test", pytest_params(OPTION_ERROR_TESTS)) -def test_validate_option_errors(collection, test): - """Test that validate errors on invalid option combinations and truthy background.""" +@pytest.mark.requires(validate_repair=True) +@pytest.mark.parametrize("test", pytest_params(TRUTHY_TYPE_TESTS)) +def test_validate_background_truthy_rejected(collection, test): + """Test that validate rejects truthy background values on standalone.""" collection.insert_one({"_id": 1}) result = execute_command(collection, {"validate": collection.name, **test.command}) assertFailureCode(result, test.error_code, msg=test.msg) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py index 748fa43e1..eb9531825 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_repair.py @@ -17,6 +17,8 @@ from documentdb_tests.framework.parametrize import pytest_params from documentdb_tests.framework.property_checks import Eq +pytestmark = pytest.mark.requires(validate_repair=True) + # Property [Type Coercion Wiring]: repair delegates to shared boolean coercion. REPAIR_WIRING_TESTS: list[DiagnosticTestCase] = [ DiagnosticTestCase( diff --git a/documentdb_tests/framework/preconditions.py b/documentdb_tests/framework/preconditions.py index 8c3bf2534..29c13f764 100644 --- a/documentdb_tests/framework/preconditions.py +++ b/documentdb_tests/framework/preconditions.py @@ -57,6 +57,10 @@ "reindex": "reIndex is permitted", "local_rename": "renaming into the unreplicated local database is permitted", "replication": "replication commands are available (applyOps, oplog access)", + "validate_repair": ( + "validate with repair/fixMultikey is permitted and background validation " + "is rejected (standalone-only behavior)" + ), } # The capabilities each (engine, topology) target has. To add an engine or @@ -79,6 +83,7 @@ "unforced_compact", "reindex", "local_rename", + "validate_repair", } ), ("documentdb", "standalone"): frozenset( @@ -93,6 +98,7 @@ "unforced_compact", "reindex", "replication", + "validate_repair", } ), } From f7e40155f264f34b407ea6226303c9666d340f37 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 25 Jun 2026 12:15:21 -0700 Subject: [PATCH 15/15] add helper bind_collection Signed-off-by: Alina (Xi) Li --- .../validate/test_validate_core_behavior.py | 3 ++- .../commands/validate/test_validate_edge_cases.py | 3 ++- .../commands/validate/test_validate_indexes.py | 3 ++- .../validate/test_validate_response_structure.py | 3 ++- .../system/diagnostic/utils/diagnostic_test_case.py | 13 ++++++++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py index 041961665..07302f948 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_core_behavior.py @@ -13,6 +13,7 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, + bind_collection, ) from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial from documentdb_tests.framework.executor import execute_command @@ -79,7 +80,7 @@ def test_validate_core_behavior(collection, test): """Test validate core behavior with various collection states.""" for cmd in test.setup: - execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + execute_command(collection, bind_collection(cmd, collection.name)) cmd = {"validate": collection.name} if test.command: cmd.update(test.command) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py index 7b641b444..d7674fad6 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_edge_cases.py @@ -13,6 +13,7 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, + bind_collection, ) from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial from documentdb_tests.framework.executor import execute_command @@ -132,7 +133,7 @@ def test_validate_document_variety(collection, test): """Test validate with diverse document shapes and index counts.""" for cmd in test.setup: - execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + execute_command(collection, bind_collection(cmd, collection.name)) result = execute_command(collection, {"validate": collection.name}) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py index 0fe64b5f2..ae027bef0 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_indexes.py @@ -13,6 +13,7 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, + bind_collection, ) from documentdb_tests.framework.assertions import assertProperties from documentdb_tests.framework.executor import execute_command @@ -226,6 +227,6 @@ def test_validate_index_types(collection, test): """Test validate with various index types.""" for cmd in test.setup: - execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + execute_command(collection, bind_collection(cmd, collection.name)) result = execute_command(collection, {"validate": collection.name}) assertProperties(result, test.checks, msg=test.msg, raw_res=True) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py index 26cd8744d..f7fb62676 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/commands/validate/test_validate_response_structure.py @@ -10,6 +10,7 @@ from documentdb_tests.compatibility.tests.system.diagnostic.utils.diagnostic_test_case import ( DiagnosticTestCase, + bind_collection, ) from documentdb_tests.framework.assertions import assertProperties, assertSuccessPartial from documentdb_tests.framework.executor import execute_command @@ -280,7 +281,7 @@ def test_validate_ns_matches_namespace(collection): def test_validate_response_details(collection, test): """Test validate response values, sub-structure, and option-specific shapes.""" for cmd in test.setup: - execute_command(collection, {**cmd, next(iter(cmd)): collection.name}) + execute_command(collection, bind_collection(cmd, collection.name)) cmd = {"validate": collection.name} if test.command: cmd.update(test.command) diff --git a/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py b/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py index 3d08d60c0..65045be33 100644 --- a/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py +++ b/documentdb_tests/compatibility/tests/system/diagnostic/utils/diagnostic_test_case.py @@ -1,4 +1,4 @@ -"""Shared test case for diagnostic command tests.""" +"""Shared test case and utilities for diagnostic command tests.""" from dataclasses import dataclass, field from typing import Any, Dict, List, Optional @@ -21,3 +21,14 @@ class DiagnosticTestCase(BaseTestCase): command: Optional[Dict[str, Any]] = None use_admin: bool = True checks: Dict[str, Any] = field(default_factory=dict) + + +def bind_collection(cmd: Dict[str, Any], name: str) -> Dict[str, Any]: + """Replace the placeholder collection name in a setup command. + + Setup commands use ``""`` as a placeholder for the collection name + (e.g. ``{"insert": "", "documents": [...]}``). This helper overwrites + the first key's value with the real collection name. + """ + command_name = next(iter(cmd)) + return {**cmd, command_name: name}