From ad540c2d0cb1384702e4c4a851c253c8f49d3cd7 Mon Sep 17 00:00:00 2001 From: Daniel Frankcom Date: Tue, 21 Apr 2026 17:36:54 -0700 Subject: [PATCH 1/3] Add collection $drop tests Signed-off-by: Daniel Frankcom --- .DS_Store | Bin 0 -> 6148 bytes .../commands/drop/test_drop_basic.py | 94 +++++++++++ .../commands/drop/test_drop_invalid_names.py | 102 ++++++++++++ .../commands/drop/test_drop_options.py | 153 ++++++++++++++++++ .../drop/test_drop_response_fields.py | 132 +++++++++++++++ .../drop/test_drop_special_collections.py | 60 +++++++ .../commands/drop/test_drop_views.py | 80 +++++++++ .../collections/commands/utils/__init__.py | 0 .../commands/utils/command_test_case.py | 72 +++++++++ documentdb_tests/framework/assertions.py | 5 + documentdb_tests/framework/error_codes.py | 2 + 11 files changed, 700 insertions(+) create mode 100644 .DS_Store create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_basic.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_invalid_names.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_options.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_response_fields.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_special_collections.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_views.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/utils/__init__.py create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/utils/command_test_case.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 CommandContext: + db = collection.database.name + coll = collection.name + return cls(collection=coll, database=db, namespace=f"{db}.{coll}") + + +@dataclass(frozen=True) +class CommandTestCase(BaseTestCase): + """Test case for collection command tests. + + Collection commands often reference fixture-dependent values like + collection names and namespaces. Fields that need these values accept + a callable that receives a CommandContext at execution time. + + Attributes: + setup: A callable that receives a Database and returns the + Collection to execute against. When None, uses the default + fixture collection. + docs: Documents to insert before executing the command. + command: A callable (CommandContext -> dict) for commands that + need fixture values, or a plain dict. + expected: A callable (CommandContext -> dict) for results that + need fixture values, a plain dict, or None for error cases. + """ + + setup: Callable[[Database], Collection] | None = None + docs: list[dict[str, Any]] | None = None + command: dict[str, Any] | Callable[[CommandContext], dict[str, Any]] | None = None + expected: dict[str, Any] | Callable[[CommandContext], dict[str, Any]] | None = None + + def build_command(self, ctx: CommandContext) -> dict[str, Any]: + """Resolve the command dict from a callable or plain dict.""" + if self.command is None: + raise ValueError(f"CommandTestCase '{self.id}' has no command defined") + if isinstance(self.command, dict): + return self.command + return self.command(ctx) + + def build_expected(self, ctx: CommandContext) -> dict[str, Any] | None: + """Resolve expected from a callable or plain value.""" + if self.expected is None or isinstance(self.expected, dict): + return self.expected + return self.expected(ctx) diff --git a/documentdb_tests/framework/assertions.py b/documentdb_tests/framework/assertions.py index c9528ac7..777f0447 100644 --- a/documentdb_tests/framework/assertions.py +++ b/documentdb_tests/framework/assertions.py @@ -239,6 +239,7 @@ def assertResult( msg: Optional[str] = None, ignore_order_in: Optional[list[str]] = None, ignore_doc_order: bool = False, + raw_res: bool = False, ): """ Universal assertion that handles success and error cases. @@ -252,11 +253,14 @@ def assertResult( comparison (for fields like set operation results where element order is unspecified) ignore_doc_order: If True, compare lists ignoring order (duplicates still matter) + raw_res: If True, compare the raw result dict instead of + extracting cursor.firstBatch Usage: assertResult(result, expected=[{"_id": 1}]) # Success case assertResult(result, error_code=16555) # Error case assertResult(result, expected=[{"r": [3, 1, 2]}], ignore_order_in=["r"]) + assertResult(result, expected={"ok": 1.0}, raw_res=True) # Raw command result """ if error_code is not None: assertFailureCode(result, error_code, msg) @@ -265,6 +269,7 @@ def assertResult( result, expected, msg, + raw_res=raw_res, ignore_order_in=ignore_order_in, ignore_doc_order=ignore_doc_order, ) diff --git a/documentdb_tests/framework/error_codes.py b/documentdb_tests/framework/error_codes.py index 67a764f6..f77454d7 100644 --- a/documentdb_tests/framework/error_codes.py +++ b/documentdb_tests/framework/error_codes.py @@ -7,6 +7,7 @@ FAILED_TO_PARSE_ERROR = 9 TYPE_MISMATCH_ERROR = 14 OVERFLOW_ERROR = 15 +INVALID_NAMESPACE_ERROR = 73 UNRECOGNIZED_EXPRESSION_ERROR = 168 CONVERSION_FAILURE_ERROR = 241 EXPRESSION_NOT_OBJECT_ERROR = 10065 @@ -176,6 +177,7 @@ ARRAY_TO_OBJECT_INVALID_PAIR_ERROR = 40397 ARRAY_TO_OBJECT_INVALID_ELEMENT_ERROR = 40398 MERGE_OBJECTS_INVALID_TYPE_ERROR = 40400 +UNRECOGNIZED_COMMAND_FIELD_ERROR = 40415 INVALID_TIMEZONE_ERROR = 40485 DATEFROMPARTS_MIXING_ERROR = 40489 DATEFROMPARTS_INVALID_TYPE_ERROR = 40515 From 7e2352fd95d3209153bd245af620062ef855188a Mon Sep 17 00:00:00 2001 From: Daniel Frankcom Date: Wed, 22 Apr 2026 09:47:53 -0700 Subject: [PATCH 2/3] Remove .DS_Store and add to .gitignore Signed-off-by: Daniel Frankcom --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 3 +++ 2 files changed, 3 insertions(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Wed, 22 Apr 2026 17:13:23 -0700 Subject: [PATCH 3/3] Refactor CommandTestCase with TargetCollection Signed-off-by: Daniel Frankcom --- .../commands/drop/test_drop_basic.py | 27 +++--- .../commands/drop/test_drop_invalid_names.py | 8 +- .../commands/drop/test_drop_options.py | 8 +- .../drop/test_drop_response_fields.py | 81 +++++----------- .../drop/test_drop_special_collections.py | 15 +-- .../commands/drop/test_drop_views.py | 95 +++++++++---------- .../commands/utils/command_test_case.py | 27 ++++-- .../commands/utils/target_collection.py | 55 +++++++++++ 8 files changed, 175 insertions(+), 141 deletions(-) create mode 100644 documentdb_tests/compatibility/tests/core/collections/commands/utils/target_collection.py diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_basic.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_basic.py index 666f3b8b..9598f267 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_basic.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_basic.py @@ -4,6 +4,9 @@ CommandContext, CommandTestCase, ) +from documentdb_tests.compatibility.tests.core.collections.commands.utils.target_collection import ( + NamedCollection, +) from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params @@ -20,8 +23,8 @@ ), CommandTestCase( "empty_collection", - setup=lambda db: db.create_collection("empty_via_create"), - command={"drop": "empty_via_create"}, + target_collection=NamedCollection(), + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, msg="Explicitly created empty collection should have nIndexesWas=1", ), @@ -38,22 +41,22 @@ DROP_SPECIAL_NAME_TESTS: list[CommandTestCase] = [ CommandTestCase( "spaces", - setup=lambda db: db.create_collection("test drop spaces"), - command={"drop": "test drop spaces"}, + target_collection=NamedCollection(suffix=" spaces"), + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with spaces in name", ), CommandTestCase( "unicode", - setup=lambda db: db.create_collection("test_drôp_ünïcödé"), - command={"drop": "test_drôp_ünïcödé"}, + target_collection=NamedCollection(suffix="_drôp_ünïcödé"), + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with unicode in name", ), CommandTestCase( "dots", - setup=lambda db: db.create_collection("test.drop.dots"), - command={"drop": "test.drop.dots"}, + target_collection=NamedCollection(suffix=".dots"), + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with dots in name", ), @@ -80,11 +83,9 @@ @pytest.mark.parametrize("test", pytest_params(DROP_BASIC_ALL_TESTS)) def test_drop_basic(database_client, collection, test): """Test basic drop command response.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_invalid_names.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_invalid_names.py index 6fed50a8..6fcb482a 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_invalid_names.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_invalid_names.py @@ -88,11 +88,9 @@ @pytest.mark.parametrize("test", pytest_params(DROP_INVALID_NAME_TESTS)) def test_drop_invalid_names(database_client, collection, test): """Test drop command rejects invalid collection names.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_options.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_options.py index 9d68da93..a9816cb2 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_options.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_options.py @@ -139,11 +139,9 @@ @pytest.mark.parametrize("test", pytest_params(DROP_OPTIONS_TESTS)) def test_drop_options(database_client, collection, test): """Test drop command option acceptance and rejection.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_response_fields.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_response_fields.py index 647e6110..63d47aab 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_response_fields.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_response_fields.py @@ -1,4 +1,5 @@ import pytest +from pymongo import IndexModel from documentdb_tests.compatibility.tests.core.collections.commands.utils.command_test_case import ( CommandContext, @@ -20,27 +21,17 @@ ), CommandTestCase( "one_additional", - setup=lambda db: ( - db.create_collection("test_one_idx"), - db["test_one_idx"].insert_one({"_id": 1, "a": 1}), - db["test_one_idx"].create_index("a"), - db["test_one_idx"], - )[-1], - command={"drop": "test_one_idx"}, + indexes=[IndexModel("a")], + docs=[{"_id": 1, "a": 1}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should have nIndexesWas=2 with one extra index", ), CommandTestCase( "multiple", - setup=lambda db: ( - db.create_collection("test_multi_idx"), - db["test_multi_idx"].insert_one({"_id": 1, "a": 1, "b": 1, "c": 1}), - db["test_multi_idx"].create_index("a"), - db["test_multi_idx"].create_index("b"), - db["test_multi_idx"].create_index("c"), - db["test_multi_idx"], - )[-1], - command={"drop": "test_multi_idx"}, + indexes=[IndexModel("a"), IndexModel("b"), IndexModel("c")], + docs=[{"_id": 1, "a": 1, "b": 1, "c": 1}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 4, "ns": ctx.namespace, "ok": 1.0}, msg="Should have nIndexesWas=4 with 3 extra indexes", ), @@ -51,61 +42,41 @@ DROP_INDEX_TYPE_TESTS: list[CommandTestCase] = [ CommandTestCase( "compound", - setup=lambda db: ( - db.create_collection("test_compound"), - db["test_compound"].insert_one({"_id": 1, "a": 1, "b": 1}), - db["test_compound"].create_index([("a", 1), ("b", 1)]), - db["test_compound"], - )[-1], - command={"drop": "test_compound"}, + indexes=[IndexModel([("a", 1), ("b", 1)])], + docs=[{"_id": 1, "a": 1, "b": 1}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should have nIndexesWas=2 with compound index", ), CommandTestCase( "text", - setup=lambda db: ( - db.create_collection("test_text"), - db["test_text"].insert_one({"_id": 1, "content": "hello world"}), - db["test_text"].create_index([("content", "text")]), - db["test_text"], - )[-1], - command={"drop": "test_text"}, + indexes=[IndexModel([("content", "text")])], + docs=[{"_id": 1, "content": "hello world"}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with text index", ), CommandTestCase( "ttl", - setup=lambda db: ( - db.create_collection("test_ttl"), - db["test_ttl"].insert_one({"_id": 1, "createdAt": None}), - db["test_ttl"].create_index("createdAt", expireAfterSeconds=3600), - db["test_ttl"], - )[-1], - command={"drop": "test_ttl"}, + indexes=[IndexModel("createdAt", expireAfterSeconds=3600)], + docs=[{"_id": 1, "createdAt": None}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with TTL index", ), CommandTestCase( "unique", - setup=lambda db: ( - db.create_collection("test_unique"), - db["test_unique"].insert_one({"_id": 1, "email": "test"}), - db["test_unique"].create_index("email", unique=True), - db["test_unique"], - )[-1], - command={"drop": "test_unique"}, + indexes=[IndexModel("email", unique=True)], + docs=[{"_id": 1, "email": "test"}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with unique index", ), CommandTestCase( "hashed", - setup=lambda db: ( - db.create_collection("test_hashed"), - db["test_hashed"].insert_one({"_id": 1, "key": "value"}), - db["test_hashed"].create_index([("key", "hashed")]), - db["test_hashed"], - )[-1], - command={"drop": "test_hashed"}, + indexes=[IndexModel([("key", "hashed")])], + docs=[{"_id": 1, "key": "value"}], + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 2, "ns": ctx.namespace, "ok": 1.0}, msg="Should succeed with hashed index", ), @@ -118,11 +89,9 @@ @pytest.mark.parametrize("test", pytest_params(DROP_RESPONSE_TESTS)) def test_drop_response(database_client, collection, test): """Test drop command response fields.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_special_collections.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_special_collections.py index fa56ecd2..34d7af95 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_special_collections.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_special_collections.py @@ -4,6 +4,9 @@ CommandContext, CommandTestCase, ) +from documentdb_tests.compatibility.tests.core.collections.commands.utils.target_collection import ( + CappedCollection, +) from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params @@ -12,9 +15,9 @@ DROP_CAPPED_TESTS: list[CommandTestCase] = [ CommandTestCase( "capped", - setup=lambda db: db.create_collection("test_drop_capped", capped=True, size=4096), + target_collection=CappedCollection(size=4096), docs=[{"_id": 1}], - command={"drop": "test_drop_capped"}, + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, msg="Drop on capped collection should succeed", ), @@ -46,11 +49,9 @@ @pytest.mark.parametrize("test", pytest_params(DROP_SPECIAL_TESTS)) def test_drop_special(database_client, collection, test): """Test drop on special collection types.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_views.py b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_views.py index 3d71bd18..7c5e4291 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_views.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/drop/test_drop_views.py @@ -4,6 +4,9 @@ CommandContext, CommandTestCase, ) +from documentdb_tests.compatibility.tests.core.collections.commands.utils.target_collection import ( + ViewCollection, +) from documentdb_tests.framework.assertions import assertResult from documentdb_tests.framework.executor import execute_command from documentdb_tests.framework.parametrize import pytest_params @@ -13,64 +16,21 @@ DROP_VIEW_TESTS: list[CommandTestCase] = [ CommandTestCase( "view", - setup=lambda db: ( - db.create_collection("test_drop_view_src"), - db.command("create", "test_drop_view_v", viewOn="test_drop_view_src", pipeline=[]), - db["test_drop_view_v"], - )[-1], - command={"drop": "test_drop_view_v"}, + target_collection=ViewCollection(), + command=lambda ctx: {"drop": ctx.collection}, expected=lambda ctx: {"ns": ctx.namespace, "ok": 1.0}, msg="Drop on view should return ns and ok without nIndexesWas", ), ] -# Property [Underlying Collection Drop]: drop succeeds on the source -# collection underlying a view. -DROP_UNDERLYING_TESTS: list[CommandTestCase] = [ - CommandTestCase( - "succeeds", - setup=lambda db: ( - db["test_drop_under_src"].insert_one({"_id": 1, "a": 1}), - db.command("create", "test_drop_under_v", viewOn="test_drop_under_src", pipeline=[]), - db["test_drop_under_src"], - )[-1], - command={"drop": "test_drop_under_src"}, - expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, - msg="Drop underlying collection should succeed", - ), -] - -# Property [system.views Drop]: drop succeeds on the system.views collection. -DROP_SYSTEM_VIEWS_TESTS: list[CommandTestCase] = [ - CommandTestCase( - "system_views", - setup=lambda db: ( - db.create_collection("test_drop_sysviews_src"), - db.command( - "create", "test_drop_sysviews_v", viewOn="test_drop_sysviews_src", pipeline=[] - ), - db["system.views"], - )[-1], - command={"drop": "system.views"}, - expected=lambda ctx: {"nIndexesWas": 1, "ns": ctx.namespace, "ok": 1.0}, - msg="Drop on system.views should succeed", - ), -] - -DROP_VIEW_ALL_TESTS: list[CommandTestCase] = ( - DROP_VIEW_TESTS + DROP_UNDERLYING_TESTS + DROP_SYSTEM_VIEWS_TESTS -) - @pytest.mark.collection_mgmt -@pytest.mark.parametrize("test", pytest_params(DROP_VIEW_ALL_TESTS)) +@pytest.mark.parametrize("test", pytest_params(DROP_VIEW_TESTS)) def test_drop_views(database_client, collection, test): """Test drop command behavior on views.""" - target = test.setup(database_client) if test.setup else collection - if test.docs: - target.insert_many(test.docs) - ctx = CommandContext.from_collection(target) - result = execute_command(target, test.build_command(ctx)) + collection = test.prepare(database_client, collection) + ctx = CommandContext.from_collection(collection) + result = execute_command(collection, test.build_command(ctx)) assertResult( result, expected=test.build_expected(ctx), @@ -78,3 +38,40 @@ def test_drop_views(database_client, collection, test): msg=test.msg, raw_res=True, ) + + +# Property [Underlying Collection Drop]: drop succeeds on the source +# collection underlying a view. +@pytest.mark.collection_mgmt +def test_drop_underlying_collection(database_client, collection): + """Drop the source collection underlying a view.""" + collection.insert_one({"_id": 1, "a": 1}) + view_name = f"{collection.name}_view" + database_client.command("create", view_name, viewOn=collection.name, pipeline=[]) + result = execute_command(collection, {"drop": collection.name}) + ns = f"{database_client.name}.{collection.name}" + assertResult( + result, + expected={"nIndexesWas": 1, "ns": ns, "ok": 1.0}, + msg="Drop underlying collection should succeed", + raw_res=True, + ) + + +# Property [system.views Drop]: drop succeeds on the system.views collection. +@pytest.mark.collection_mgmt +def test_drop_system_views(database_client, collection): + """Drop the system.views collection.""" + src_name = f"{collection.name}_src" + view_name = f"{collection.name}_view" + database_client.create_collection(src_name) + database_client.command("create", view_name, viewOn=src_name, pipeline=[]) + system_views = database_client["system.views"] + result = execute_command(system_views, {"drop": "system.views"}) + ns = f"{database_client.name}.system.views" + assertResult( + result, + expected={"nIndexesWas": 1, "ns": ns, "ok": 1.0}, + msg="Drop on system.views should succeed", + raw_res=True, + ) diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/utils/command_test_case.py b/documentdb_tests/compatibility/tests/core/collections/commands/utils/command_test_case.py index d2cfd924..cb8fcba5 100644 --- a/documentdb_tests/compatibility/tests/core/collections/commands/utils/command_test_case.py +++ b/documentdb_tests/compatibility/tests/core/collections/commands/utils/command_test_case.py @@ -3,12 +3,16 @@ from __future__ import annotations from collections.abc import Callable -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any +from pymongo import IndexModel from pymongo.collection import Collection from pymongo.database import Database +from documentdb_tests.compatibility.tests.core.collections.commands.utils.target_collection import ( + TargetCollection, +) from documentdb_tests.framework.test_case import BaseTestCase @@ -27,7 +31,7 @@ class CommandContext: namespace: str @classmethod - def from_collection(cls, collection) -> CommandContext: + def from_collection(cls, collection: Collection) -> CommandContext: db = collection.database.name coll = collection.name return cls(collection=coll, database=db, namespace=f"{db}.{coll}") @@ -42,9 +46,10 @@ class CommandTestCase(BaseTestCase): a callable that receives a CommandContext at execution time. Attributes: - setup: A callable that receives a Database and returns the - Collection to execute against. When None, uses the default - fixture collection. + target_collection: Describes the collection to execute against. + Defaults to the fixture collection. + indexes: Indexes to create before executing the command. Each + entry is passed to create_index. docs: Documents to insert before executing the command. command: A callable (CommandContext -> dict) for commands that need fixture values, or a plain dict. @@ -52,11 +57,21 @@ class CommandTestCase(BaseTestCase): need fixture values, a plain dict, or None for error cases. """ - setup: Callable[[Database], Collection] | None = None + target_collection: TargetCollection = field(default_factory=TargetCollection) + indexes: list[IndexModel] | None = None docs: list[dict[str, Any]] | None = None command: dict[str, Any] | Callable[[CommandContext], dict[str, Any]] | None = None expected: dict[str, Any] | Callable[[CommandContext], dict[str, Any]] | None = None + def prepare(self, db: Database, collection: Collection) -> Collection: + """Resolve the target collection and apply indexes/docs.""" + collection = self.target_collection.resolve(db, collection) + if self.indexes: + collection.create_indexes(self.indexes) + if self.docs: + collection.insert_many(self.docs) + return collection + def build_command(self, ctx: CommandContext) -> dict[str, Any]: """Resolve the command dict from a callable or plain dict.""" if self.command is None: diff --git a/documentdb_tests/compatibility/tests/core/collections/commands/utils/target_collection.py b/documentdb_tests/compatibility/tests/core/collections/commands/utils/target_collection.py new file mode 100644 index 00000000..7d96df63 --- /dev/null +++ b/documentdb_tests/compatibility/tests/core/collections/commands/utils/target_collection.py @@ -0,0 +1,55 @@ +"""Collection target types for command tests. + +Each subclass describes a kind of collection a test needs and knows how +to create it from the fixture collection. All derived names use the +fixture name as a prefix to guarantee parallel-safe uniqueness. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from pymongo.collection import Collection +from pymongo.database import Database + + +@dataclass(frozen=True) +class TargetCollection: + """Default. Use the fixture collection as-is.""" + + def resolve(self, db: Database, collection: Collection) -> Collection: + return collection + + +@dataclass(frozen=True) +class ViewCollection(TargetCollection): + """A view on the fixture collection.""" + + def resolve(self, db: Database, collection: Collection) -> Collection: + view_name = f"{collection.name}_view" + db.command("create", view_name, viewOn=collection.name, pipeline=[]) + return db[view_name] + + +@dataclass(frozen=True) +class CappedCollection(TargetCollection): + """A capped collection.""" + + size: int = 4096 + + def resolve(self, db: Database, collection: Collection) -> Collection: + name = f"{collection.name}_capped" + db.create_collection(name, capped=True, size=self.size) + return db[name] + + +@dataclass(frozen=True) +class NamedCollection(TargetCollection): + """A collection with a custom name suffix.""" + + suffix: str = "" + + def resolve(self, db: Database, collection: Collection) -> Collection: + name = f"{collection.name}{self.suffix}" + db.create_collection(name) + return db[name]