From 7e2ab18e2cd7782b2d2674fffbd6b9e494feeaa7 Mon Sep 17 00:00:00 2001 From: Ian Forster Date: Thu, 18 Jun 2026 14:13:50 -0700 Subject: [PATCH 1/2] 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 --- .../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 86884ce4c..918d7e351 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -490,6 +490,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 ENCRYPTED_FIELD_RANGE_MIN_MAX_ERROR = 6720005 ENCRYPTED_FIELD_RANGE_TYPE_ERROR = 6775201 From 5fa290f46211380e0bef889f6062af57bc564229 Mon Sep 17 00:00:00 2001 From: Ian Forster Date: Wed, 24 Jun 2026 13:50:55 -0700 Subject: [PATCH 2/2] 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 --- ...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 918d7e351..b362e387f 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -498,6 +498,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