From 4250e24b88ac6e801944629a8785a724069bac65 Mon Sep 17 00:00:00 2001 From: Ian Forster Date: Thu, 18 Jun 2026 14:13:50 -0700 Subject: [PATCH 1/3] Add compactStructuredEncryptionData tests with proper DRY parametrization - Add NOT_ENCRYPTED_COLLECTION_ERROR constant to error_codes.py - Use bson_type_validator for exhaustive compactionTokens type rejection - Use CommandTestCase with pytest_params for parametrized core/error/edge tests - Fix smoke test to use assertFailureCode (no message content checks) - Remove redundant response structure standalone test Signed-off-by: Ian Forster Signed-off-by: Alina (Xi) Li --- .../compatibility/tests/system/__init__.py | 0 .../tests/system/administration/__init__.py | 0 .../administration/commands/__init__.py | 0 .../__init__.py | 0 ...tStructuredEncryptionData_core_behavior.py | 80 ++++++++++++ ...pactStructuredEncryptionData_edge_cases.py | 115 ++++++++++++++++++ ...actStructuredEncryptionData_error_cases.py | 92 ++++++++++++++ ...ructuredEncryptionData_field_validation.py | 109 +++++++++++++++++ ...t_smoke_compactStructuredEncryptionData.py | 13 +- documentdb_tests/framework/error_codes.py | 1 + 10 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/__init__.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_core_behavior.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_error_cases.py create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_field_validation.py diff --git a/documentdb_tests/compatibility/tests/system/__init__.py b/documentdb_tests/compatibility/tests/system/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/__init__.py b/documentdb_tests/compatibility/tests/system/administration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/__init__.py b/documentdb_tests/compatibility/tests/system/administration/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/__init__.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_core_behavior.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_core_behavior.py new file mode 100644 index 000000000..5e3e38ad7 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_core_behavior.py @@ -0,0 +1,80 @@ +"""Tests for compactStructuredEncryptionData core behavior. + +Verifies the command correctly rejects non-encrypted collections with error 6346807 +and handles non-existent collections. +""" + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import ( + NAMESPACE_NOT_FOUND_ERROR, + NOT_ENCRYPTED_COLLECTION_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = pytest.mark.admin + +# Property [Non-Encrypted Rejection]: compactStructuredEncryptionData rejects +# collections that are not configured for Queryable Encryption with error 6346807. +CORE_BEHAVIOR_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "empty_compaction_tokens", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should reject non-encrypted collection" + " with empty tokens", + ), + CommandTestCase( + "non_empty_compaction_tokens", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"field": b"\x00\x01\x02"}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should reject non-encrypted collection with tokens", + ), + CommandTestCase( + "collection_with_documents", + docs=[{"_id": 1, "name": "test"}, {"_id": 2, "name": "data"}], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should reject non-encrypted collection with documents", + ), + CommandTestCase( + "nonexistent_collection", + command=lambda ctx: { + "compactStructuredEncryptionData": "nonexistent_collection_xyz", + "compactionTokens": {}, + }, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="compactStructuredEncryptionData should error on non-existent collection", + ), +] + + +@pytest.mark.parametrize("test", pytest_params(CORE_BEHAVIOR_TESTS)) +def test_compactStructuredEncryptionData_core_behavior(database_client, collection, test): + """Test compactStructuredEncryptionData core behavior on non-encrypted collections.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py new file mode 100644 index 000000000..5ea265e63 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py @@ -0,0 +1,115 @@ +"""Tests for compactStructuredEncryptionData edge cases. + +Covers collection name edge cases and compactionTokens document content +edge cases. +""" + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import ( + NAMESPACE_NOT_FOUND_ERROR, + NOT_ENCRYPTED_COLLECTION_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params + +pytestmark = pytest.mark.admin + +# Property [Collection Name Edge Cases]: compactStructuredEncryptionData handles +# special collection name patterns correctly. +COLLECTION_NAME_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "system_prefix", + command=lambda ctx: { + "compactStructuredEncryptionData": "system.buckets.test", + "compactionTokens": {}, + }, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="compactStructuredEncryptionData should error on non-existent system prefix collection", + ), + CommandTestCase( + "dotted_name", + command=lambda ctx: { + "compactStructuredEncryptionData": "a.b.c", + "compactionTokens": {}, + }, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="compactStructuredEncryptionData should error on non-existent dotted collection", + ), + CommandTestCase( + "dollar_prefix", + command=lambda ctx: { + "compactStructuredEncryptionData": "$cmd", + "compactionTokens": {}, + }, + error_code=NAMESPACE_NOT_FOUND_ERROR, + msg="compactStructuredEncryptionData should error on non-existent" + " dollar-prefixed collection", + ), +] + +# Property [CompactionTokens Content Edge Cases]: compactStructuredEncryptionData +# handles various compactionTokens document content correctly. +COMPACTION_TOKENS_CONTENT_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "null_token_value", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"ssn": None}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should handle null token values", + ), + CommandTestCase( + "empty_string_key", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"": b"\x00\x01"}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should handle empty string key in tokens", + ), + CommandTestCase( + "dot_notation_key", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"a.b": b"\x00\x01"}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should handle dot-notation key in tokens", + ), + CommandTestCase( + "nested_document_value", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"field": {"nested": b"\x00\x01"}}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should handle nested document in token value", + ), +] + +EDGE_CASE_TESTS = COLLECTION_NAME_TESTS + COMPACTION_TOKENS_CONTENT_TESTS + + +@pytest.mark.parametrize("test", pytest_params(EDGE_CASE_TESTS)) +def test_compactStructuredEncryptionData_edge_cases(database_client, collection, test): + """Test compactStructuredEncryptionData edge cases.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_error_cases.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_error_cases.py new file mode 100644 index 000000000..d7b1e07ab --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_error_cases.py @@ -0,0 +1,92 @@ +"""Tests for compactStructuredEncryptionData error cases. + +Covers unrecognized fields and collection type variants (views, capped). +""" + +import pytest + +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import assertResult +from documentdb_tests.framework.error_codes import ( + COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + NOT_ENCRYPTED_COLLECTION_ERROR, + UNRECOGNIZED_COMMAND_FIELD_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.parametrize import pytest_params +from documentdb_tests.framework.target_collection import CappedCollection, ViewCollection + +pytestmark = pytest.mark.admin + +# Property [Unrecognized Field Rejection]: compactStructuredEncryptionData rejects +# commands with unrecognized fields. +UNRECOGNIZED_FIELD_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "extra_field", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + "unknownField": 1, + }, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="compactStructuredEncryptionData should reject unrecognized fields", + ), + CommandTestCase( + "similar_field_name", + docs=[], + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + "compactionToken": {}, + }, + error_code=UNRECOGNIZED_COMMAND_FIELD_ERROR, + msg="compactStructuredEncryptionData should reject fields with similar names", + ), +] + +# Property [Collection Type Rejection]: compactStructuredEncryptionData rejects +# views and returns non-encrypted error for capped collections. +COLLECTION_VARIANT_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "on_view", + docs=[{"_id": 1}], + target_collection=ViewCollection(), + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + }, + error_code=COMMAND_NOT_SUPPORTED_ON_VIEW_ERROR, + msg="compactStructuredEncryptionData should reject views", + ), + CommandTestCase( + "on_capped_collection", + docs=[{"_id": 1}], + target_collection=CappedCollection(), + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {}, + }, + error_code=NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should reject non-encrypted capped collection", + ), +] + +ERROR_TESTS = UNRECOGNIZED_FIELD_TESTS + COLLECTION_VARIANT_TESTS + + +@pytest.mark.parametrize("test", pytest_params(ERROR_TESTS)) +def test_compactStructuredEncryptionData_errors(database_client, collection, test): + """Test compactStructuredEncryptionData error conditions.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_field_validation.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_field_validation.py new file mode 100644 index 000000000..b21e85008 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_field_validation.py @@ -0,0 +1,109 @@ +"""Tests for compactStructuredEncryptionData command field validation. + +Covers collection name type validation (§19 representative case), +compactionTokens BSON type rejection, and missing field errors. +""" + +import pytest + +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.bson_type_validator import ( + BsonTypeTestCase, + generate_bson_acceptance_test_cases, + generate_bson_rejection_test_cases, +) +from documentdb_tests.framework.error_codes import ( + INVALID_NAMESPACE_ERROR, + MISSING_FIELD_ERROR, + NOT_ENCRYPTED_COLLECTION_ERROR, + TYPE_MISMATCH_ERROR, +) +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.test_constants import BsonType + +pytestmark = pytest.mark.admin + +# Property [CompactionTokens Type Rejection]: compactStructuredEncryptionData rejects +# non-document types for the compactionTokens field. +BSON_TYPE_PARAMS = [ + BsonTypeTestCase( + id="compactionTokens_type", + msg="compactionTokens should reject non-document types", + keyword="compactionTokens", + valid_types=[BsonType.OBJECT], + default_error_code=TYPE_MISMATCH_ERROR, + error_code_overrides={BsonType.NULL: MISSING_FIELD_ERROR}, + ), +] + +REJECTION_CASES = generate_bson_rejection_test_cases(BSON_TYPE_PARAMS) +ACCEPTANCE_CASES = generate_bson_acceptance_test_cases(BSON_TYPE_PARAMS) + + +@pytest.mark.parametrize("bson_type,sample_value,spec", REJECTION_CASES) +def test_compactStructuredEncryptionData_rejects_invalid_compactionTokens_type( + collection, bson_type, sample_value, spec +): + """Test compactStructuredEncryptionData rejects invalid BSON types for compactionTokens.""" + cmd = { + "compactStructuredEncryptionData": collection.name, + "compactionTokens": sample_value, + } + result = execute_command(collection, cmd) + assertFailureCode(result, spec.expected_code(bson_type), msg=spec.msg) + + +@pytest.mark.parametrize("bson_type,sample_value,spec", ACCEPTANCE_CASES) +def test_compactStructuredEncryptionData_accepts_valid_compactionTokens_type( + collection, bson_type, sample_value, spec +): + """Test compactStructuredEncryptionData accepts document type for compactionTokens. + + The command accepts the type but fails because the collection is not encrypted. + Error 6346807 confirms the type was accepted and processing continued. + """ + collection.insert_one({"_id": 1}) + cmd = { + "compactStructuredEncryptionData": collection.name, + "compactionTokens": sample_value, + } + result = execute_command(collection, cmd) + assertFailureCode( + result, + NOT_ENCRYPTED_COLLECTION_ERROR, + msg=spec.msg, + ) + + +def test_compactStructuredEncryptionData_rejects_non_string_collection_name(collection): + """Test compactStructuredEncryptionData rejects non-string collection name.""" + cmd = {"compactStructuredEncryptionData": 1, "compactionTokens": {}} + result = execute_command(collection, cmd) + assertFailureCode( + result, + INVALID_NAMESPACE_ERROR, + msg="compactStructuredEncryptionData should reject non-string collection name", + ) + + +def test_compactStructuredEncryptionData_rejects_empty_collection_name(collection): + """Test compactStructuredEncryptionData rejects empty string collection name.""" + cmd = {"compactStructuredEncryptionData": "", "compactionTokens": {}} + result = execute_command(collection, cmd) + assertFailureCode( + result, + INVALID_NAMESPACE_ERROR, + msg="compactStructuredEncryptionData should reject empty collection name", + ) + + +def test_compactStructuredEncryptionData_missing_compactionTokens(collection): + """Test compactStructuredEncryptionData requires compactionTokens field.""" + collection.insert_one({"_id": 1}) + cmd = {"compactStructuredEncryptionData": collection.name} + result = execute_command(collection, cmd) + assertFailureCode( + result, + MISSING_FIELD_ERROR, + msg="compactStructuredEncryptionData should error when compactionTokens is missing", + ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_smoke_compactStructuredEncryptionData.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_smoke_compactStructuredEncryptionData.py index 7f8c7cb93..077f2ffec 100644 --- a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_smoke_compactStructuredEncryptionData.py +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_smoke_compactStructuredEncryptionData.py @@ -1,12 +1,12 @@ -""" -Smoke test for compactStructuredEncryptionData command. +"""Smoke test for compactStructuredEncryptionData command. Tests basic compactStructuredEncryptionData functionality. """ import pytest -from documentdb_tests.framework.assertions import assertFailure +from documentdb_tests.framework.assertions import assertFailureCode +from documentdb_tests.framework.error_codes import NOT_ENCRYPTED_COLLECTION_ERROR from documentdb_tests.framework.executor import execute_command pytestmark = pytest.mark.smoke @@ -20,5 +20,8 @@ def test_smoke_compactStructuredEncryptionData(collection): collection, {"compactStructuredEncryptionData": collection.name, "compactionTokens": {}} ) - expected = {"code": 6346807, "msg": "Target namespace is not an encrypted collection"} - assertFailure(result, expected, msg="Should support compactStructuredEncryptionData command") + assertFailureCode( + result, + NOT_ENCRYPTED_COLLECTION_ERROR, + msg="compactStructuredEncryptionData should reject non-encrypted collection", + ) diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index 423b3fe65..fdb681888 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -507,6 +507,7 @@ ENCRYPTED_FIELD_DUPLICATE_PATH_ERROR = 6338402 ENCRYPTED_FIELD_UNSUPPORTED_TYPE_ERROR = 6338406 ENCRYPTED_FIELD_VIEW_TIMESERIES_ERROR = 6346401 +NOT_ENCRYPTED_COLLECTION_ERROR = 6346807 ENCRYPTED_FIELD_CAPPED_ERROR = 6367301 APPLYOPS_PRECONDITION_NOT_SUPPORTED_ERROR = 6711600 APPLYOPS_ALWAYS_UPSERT_NOT_SUPPORTED_ERROR = 6711601 From c31cbce3cff47bdab6d163c7a6f111ab470af664 Mon Sep 17 00:00:00 2001 From: Ian Forster Date: Wed, 24 Jun 2026 13:50:55 -0700 Subject: [PATCH 2/3] feat: add QE collection success path and missing-token tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per TEST_COVERAGE.md §16, adds happy-path coverage for compactStructuredEncryptionData on an actual Queryable Encryption collection: - Success: QE collection + valid bindata token returns ok:1.0 with stats.esc and stats.ecoc structure - Missing-token (7294900): QE collection with empty compactionTokens rejects with MISSING_COMPACT_TOKEN_ERROR Both tests gated with @pytest.mark.requires(queryable_encryption=True) so they run on mongo-replset and deselect on standalone. Also adds MISSING_COMPACT_TOKEN_ERROR (7294900) to error_codes.py and applies reviewer feedback on edge_cases msg strings. Signed-off-by: Ian Forster Signed-off-by: Alina (Xi) Li --- ...pactStructuredEncryptionData_edge_cases.py | 48 ++++++++--- ...tStructuredEncryptionData_qe_collection.py | 83 +++++++++++++++++++ documentdb_tests/framework/error_codes.py | 1 + 3 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py index 5ea265e63..60ec3923c 100644 --- a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py @@ -30,7 +30,8 @@ "compactionTokens": {}, }, error_code=NAMESPACE_NOT_FOUND_ERROR, - msg="compactStructuredEncryptionData should error on non-existent system prefix collection", + msg="compactStructuredEncryptionData should reject system.* prefix" + " collection names with namespace-not-found", ), CommandTestCase( "dotted_name", @@ -39,17 +40,18 @@ "compactionTokens": {}, }, error_code=NAMESPACE_NOT_FOUND_ERROR, - msg="compactStructuredEncryptionData should error on non-existent dotted collection", + msg="compactStructuredEncryptionData should reject multi-segment" + " dotted names with namespace-not-found", ), CommandTestCase( "dollar_prefix", command=lambda ctx: { - "compactStructuredEncryptionData": "$cmd", + "compactStructuredEncryptionData": "$myCollection", "compactionTokens": {}, }, error_code=NAMESPACE_NOT_FOUND_ERROR, - msg="compactStructuredEncryptionData should error on non-existent" - " dollar-prefixed collection", + msg="compactStructuredEncryptionData should reject dollar-prefixed" + " collection names with namespace-not-found", ), ] @@ -64,7 +66,8 @@ "compactionTokens": {"ssn": None}, }, error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should handle null token values", + msg="compactStructuredEncryptionData should reject null token value" + " with non-encrypted error", ), CommandTestCase( "empty_string_key", @@ -74,7 +77,8 @@ "compactionTokens": {"": b"\x00\x01"}, }, error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should handle empty string key in tokens", + msg="compactStructuredEncryptionData should reject empty-string" + " token key with non-encrypted error", ), CommandTestCase( "dot_notation_key", @@ -84,7 +88,8 @@ "compactionTokens": {"a.b": b"\x00\x01"}, }, error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should handle dot-notation key in tokens", + msg="compactStructuredEncryptionData should reject dot-notation" + " token key with non-encrypted error", ), CommandTestCase( "nested_document_value", @@ -94,16 +99,33 @@ "compactionTokens": {"field": {"nested": b"\x00\x01"}}, }, error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should handle nested document in token value", + msg="compactStructuredEncryptionData should reject nested document" + " token value with non-encrypted error", ), ] -EDGE_CASE_TESTS = COLLECTION_NAME_TESTS + COMPACTION_TOKENS_CONTENT_TESTS + +@pytest.mark.parametrize("test", pytest_params(COLLECTION_NAME_TESTS)) +def test_compactStructuredEncryptionData_collection_name_edge_cases( + database_client, collection, test +): + """Test compactStructuredEncryptionData rejects special collection name patterns.""" + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) + assertResult( + result, + error_code=test.error_code, + msg=test.msg, + raw_res=True, + ) -@pytest.mark.parametrize("test", pytest_params(EDGE_CASE_TESTS)) -def test_compactStructuredEncryptionData_edge_cases(database_client, collection, test): - """Test compactStructuredEncryptionData edge cases.""" +@pytest.mark.parametrize("test", pytest_params(COMPACTION_TOKENS_CONTENT_TESTS)) +def test_compactStructuredEncryptionData_compactionTokens_content_edge_cases( + database_client, collection, test +): + """Test compactStructuredEncryptionData rejects edge-case compactionTokens document content.""" collection = test.prepare(database_client, collection) ctx = CommandContext.from_collection(collection) result = execute_command(collection, test.build_command(ctx)) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py new file mode 100644 index 000000000..2b7766dd0 --- /dev/null +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py @@ -0,0 +1,83 @@ +"""Tests for compactStructuredEncryptionData on Queryable Encryption collections. + +Verifies the success path and missing-token rejection on collections that +are actually configured for Queryable Encryption. These tests require a +replica set (QE collection creation fails on standalone with 6346402). +""" + +from uuid import uuid4 + +import pytest +from bson import Binary + +from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.framework.error_codes import MISSING_COMPACT_TOKEN_ERROR +from documentdb_tests.framework.executor import execute_command +from documentdb_tests.framework.property_checks import Eq, Exists + +pytestmark = pytest.mark.requires(queryable_encryption=True) + + +@pytest.fixture() +def qe_collection(collection): + """Create a Queryable Encryption collection with one encrypted field.""" + db = collection.database + qe_name = f"{collection.name}_qe" + db.command( + "create", + qe_name, + encryptedFields={ + "fields": [ + { + "path": "ssn", + "bsonType": "string", + "keyId": Binary(uuid4().bytes, 4), + } + ] + }, + ) + yield db[qe_name] + db.drop_collection(qe_name) + + +# Property [Success Path]: compactStructuredEncryptionData succeeds on a QE collection +# with a valid compaction token and returns stats with esc and ecoc sub-documents. +def test_compact_success_returns_stats(qe_collection): + """Test compactStructuredEncryptionData succeeds on a QE collection with valid token.""" + token = Binary(b"\x00" * 32, 0) + result = execute_command( + qe_collection, + { + "compactStructuredEncryptionData": qe_collection.name, + "compactionTokens": {"ssn": token}, + }, + ) + assertProperties( + result, + { + "ok": Eq(1.0), + "stats": Exists(), + "stats.esc": Exists(), + "stats.ecoc": Exists(), + }, + raw_res=True, + msg="compactStructuredEncryptionData should succeed and return stats on QE collection.", + ) + + +# Property [Missing Token Rejection]: compactStructuredEncryptionData rejects empty +# compactionTokens on a QE collection when an encrypted path exists. +def test_compact_missing_token_for_encrypted_path(qe_collection): + """Test compactStructuredEncryptionData rejects empty tokens on QE collection.""" + result = execute_command( + qe_collection, + { + "compactStructuredEncryptionData": qe_collection.name, + "compactionTokens": {}, + }, + ) + assertFailureCode( + result, + MISSING_COMPACT_TOKEN_ERROR, + msg="compactStructuredEncryptionData should reject missing token for encrypted path.", + ) diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index fdb681888..53963b346 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -517,6 +517,7 @@ WILDCARD_MULTIPLE_FIELDS_ERROR = 7246201 WILDCARD_STRING_TYPE_ERROR = 7246202 OUT_TIMESERIES_COLLECTION_TYPE_ERROR = 7268700 +MISSING_COMPACT_TOKEN_ERROR = 7294900 OUT_TIMESERIES_OPTIONS_MISMATCH_ERROR = 7406103 SORT_DUPLICATE_KEY_ERROR = 7472500 N_ACCUMULATOR_INVALID_N_ERROR = 7548606 From 8a498a65ed4d031fa24bdc48276c4488dbcb6c32 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 29 Jun 2026 15:47:53 -0700 Subject: [PATCH 3/3] move `COMPACTION_TOKENS_CONTENT_TESTS` to QE collection Signed-off-by: Alina (Xi) Li --- ...pactStructuredEncryptionData_edge_cases.py | 69 +-------- ...tStructuredEncryptionData_qe_collection.py | 131 ++++++++++++------ 2 files changed, 92 insertions(+), 108 deletions(-) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py index 60ec3923c..4667ec137 100644 --- a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_edge_cases.py @@ -1,7 +1,6 @@ """Tests for compactStructuredEncryptionData edge cases. -Covers collection name edge cases and compactionTokens document content -edge cases. +Covers collection name edge cases. """ import pytest @@ -13,7 +12,6 @@ from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.error_codes import ( NAMESPACE_NOT_FOUND_ERROR, - NOT_ENCRYPTED_COLLECTION_ERROR, ) from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params @@ -55,55 +53,6 @@ ), ] -# Property [CompactionTokens Content Edge Cases]: compactStructuredEncryptionData -# handles various compactionTokens document content correctly. -COMPACTION_TOKENS_CONTENT_TESTS: list[CommandTestCase] = [ - CommandTestCase( - "null_token_value", - docs=[], - command=lambda ctx: { - "compactStructuredEncryptionData": ctx.collection, - "compactionTokens": {"ssn": None}, - }, - error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should reject null token value" - " with non-encrypted error", - ), - CommandTestCase( - "empty_string_key", - docs=[], - command=lambda ctx: { - "compactStructuredEncryptionData": ctx.collection, - "compactionTokens": {"": b"\x00\x01"}, - }, - error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should reject empty-string" - " token key with non-encrypted error", - ), - CommandTestCase( - "dot_notation_key", - docs=[], - command=lambda ctx: { - "compactStructuredEncryptionData": ctx.collection, - "compactionTokens": {"a.b": b"\x00\x01"}, - }, - error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should reject dot-notation" - " token key with non-encrypted error", - ), - CommandTestCase( - "nested_document_value", - docs=[], - command=lambda ctx: { - "compactStructuredEncryptionData": ctx.collection, - "compactionTokens": {"field": {"nested": b"\x00\x01"}}, - }, - error_code=NOT_ENCRYPTED_COLLECTION_ERROR, - msg="compactStructuredEncryptionData should reject nested document" - " token value with non-encrypted error", - ), -] - @pytest.mark.parametrize("test", pytest_params(COLLECTION_NAME_TESTS)) def test_compactStructuredEncryptionData_collection_name_edge_cases( @@ -119,19 +68,3 @@ def test_compactStructuredEncryptionData_collection_name_edge_cases( msg=test.msg, raw_res=True, ) - - -@pytest.mark.parametrize("test", pytest_params(COMPACTION_TOKENS_CONTENT_TESTS)) -def test_compactStructuredEncryptionData_compactionTokens_content_edge_cases( - database_client, collection, test -): - """Test compactStructuredEncryptionData rejects edge-case compactionTokens document content.""" - collection = test.prepare(database_client, collection) - ctx = CommandContext.from_collection(collection) - result = execute_command(collection, test.build_command(ctx)) - assertResult( - result, - error_code=test.error_code, - msg=test.msg, - raw_res=True, - ) diff --git a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py index 2b7766dd0..af5c5483f 100644 --- a/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py +++ b/documentdb_tests/compatibility/tests/system/administration/commands/compactStructuredEncryptionData/test_compactStructuredEncryptionData_qe_collection.py @@ -1,19 +1,25 @@ """Tests for compactStructuredEncryptionData on Queryable Encryption collections. -Verifies the success path and missing-token rejection on collections that -are actually configured for Queryable Encryption. These tests require a -replica set (QE collection creation fails on standalone with 6346402). +Verifies the success path, missing-token rejection, and token content validation +on collections that are actually configured for Queryable Encryption. These tests +require a replica set (QE collection creation fails on standalone with 6346402). """ +from __future__ import annotations + from uuid import uuid4 import pytest from bson import Binary -from documentdb_tests.framework.assertions import assertFailureCode, assertProperties +from documentdb_tests.compatibility.tests.core.utils.command_test_case import ( + CommandContext, + CommandTestCase, +) +from documentdb_tests.framework.assertions import assertFailureCode, assertSuccessPartial from documentdb_tests.framework.error_codes import MISSING_COMPACT_TOKEN_ERROR from documentdb_tests.framework.executor import execute_command -from documentdb_tests.framework.property_checks import Eq, Exists +from documentdb_tests.framework.parametrize import pytest_params pytestmark = pytest.mark.requires(queryable_encryption=True) @@ -41,43 +47,88 @@ def qe_collection(collection): # Property [Success Path]: compactStructuredEncryptionData succeeds on a QE collection -# with a valid compaction token and returns stats with esc and ecoc sub-documents. -def test_compact_success_returns_stats(qe_collection): - """Test compactStructuredEncryptionData succeeds on a QE collection with valid token.""" - token = Binary(b"\x00" * 32, 0) - result = execute_command( - qe_collection, - { - "compactStructuredEncryptionData": qe_collection.name, - "compactionTokens": {"ssn": token}, +# with a valid compaction token and returns stats. +SUCCESS_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "valid_token", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"ssn": Binary(b"\x00" * 32, 0)}, }, - ) - assertProperties( - result, - { - "ok": Eq(1.0), - "stats": Exists(), - "stats.esc": Exists(), - "stats.ecoc": Exists(), + expected={"ok": 1.0}, + msg="compactStructuredEncryptionData should succeed with valid token on QE collection.", + ), + CommandTestCase( + "null_token_value", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"ssn": None}, }, - raw_res=True, - msg="compactStructuredEncryptionData should succeed and return stats on QE collection.", - ) - + expected={"ok": 1.0}, + msg="compactStructuredEncryptionData should accept null token value on QE collection.", + ), + CommandTestCase( + "nested_document_token_value", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"ssn": {"nested": b"\x00\x01"}}, + }, + expected={"ok": 1.0}, + msg="compactStructuredEncryptionData should accept nested document token value" + " on QE collection.", + ), +] -# Property [Missing Token Rejection]: compactStructuredEncryptionData rejects empty -# compactionTokens on a QE collection when an encrypted path exists. -def test_compact_missing_token_for_encrypted_path(qe_collection): - """Test compactStructuredEncryptionData rejects empty tokens on QE collection.""" - result = execute_command( - qe_collection, - { - "compactStructuredEncryptionData": qe_collection.name, +# Property [Token Rejection]: compactStructuredEncryptionData rejects tokens that do not +# match an encrypted path on a QE collection. +ERROR_TESTS: list[CommandTestCase] = [ + CommandTestCase( + "missing_token_empty_tokens", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, "compactionTokens": {}, }, - ) - assertFailureCode( - result, - MISSING_COMPACT_TOKEN_ERROR, - msg="compactStructuredEncryptionData should reject missing token for encrypted path.", - ) + error_code=MISSING_COMPACT_TOKEN_ERROR, + msg="compactStructuredEncryptionData should reject empty compactionTokens" + " on QE collection.", + ), + CommandTestCase( + "empty_string_key", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"": Binary(b"\x00" * 32, 0)}, + }, + error_code=MISSING_COMPACT_TOKEN_ERROR, + msg="compactStructuredEncryptionData should reject empty-string token key" + " that does not match an encrypted path.", + ), + CommandTestCase( + "dot_notation_key", + command=lambda ctx: { + "compactStructuredEncryptionData": ctx.collection, + "compactionTokens": {"a.b": Binary(b"\x00" * 32, 0)}, + }, + error_code=MISSING_COMPACT_TOKEN_ERROR, + msg="compactStructuredEncryptionData should reject dot-notation token key" + " that does not match an encrypted path.", + ), +] + +QE_SUCCESS_TESTS: list[CommandTestCase] = SUCCESS_TESTS +QE_ERROR_TESTS: list[CommandTestCase] = ERROR_TESTS + + +@pytest.mark.parametrize("test", pytest_params(QE_SUCCESS_TESTS)) +def test_compactStructuredEncryptionData_qe_success(qe_collection, test): + """Test compactStructuredEncryptionData succeeds on QE collection.""" + ctx = CommandContext.from_collection(qe_collection) + result = execute_command(qe_collection, test.build_command(ctx)) + assertSuccessPartial(result, test.expected, msg=test.msg) + + +@pytest.mark.parametrize("test", pytest_params(QE_ERROR_TESTS)) +def test_compactStructuredEncryptionData_qe_error(qe_collection, test): + """Test compactStructuredEncryptionData rejects invalid tokens on QE collection.""" + ctx = CommandContext.from_collection(qe_collection) + result = execute_command(qe_collection, test.build_command(ctx)) + assertFailureCode(result, test.error_code, msg=test.msg)