Repository: Kinto/kinto

Issue:
Remove the delete-collection command 
This command was introduced to fix a very specific use-case a long time ago where we had created a collection with an invalid ID. It could thus not be deleted via the HTTP API. 

I think we can remove it.

(from #1953)


Discussion:
Yes it makes sense.
Hi, can I take up this bug and work on. As I started my open source contribution this would be my first fix
Welcome onboard! Go ahead ;) 

In brief..
- remove the code itself
- remove the tests
- remove the docs
- add a CHANGELOG entry :)

Resolving patch:
diff --git a/kinto/__main__.py b/kinto/__main__.py
--- a/kinto/__main__.py
+++ b/kinto/__main__.py
@@ -25,15 +25,7 @@ def main(args=None):
         args = sys.argv[1:]
 
     parser = argparse.ArgumentParser(description="Kinto Command-Line " "Interface")
-    commands = (
-        "init",
-        "start",
-        "migrate",
-        "delete-collection",
-        "version",
-        "rebuild-quotas",
-        "create-user",
-    )
+    commands = ("init", "start", "migrate", "version", "rebuild-quotas", "create-user")
     subparsers = parser.add_subparsers(
         title="subcommands",
         description="Main Kinto CLI commands",
@@ -103,11 +95,6 @@ def main(args=None):
                 required=False,
                 default=False,
             )
-        elif command == "delete-collection":
-            subparser.add_argument(
-                "--bucket", help="The bucket where the collection " "belongs to.", required=True
-            )
-            subparser.add_argument("--collection", help="The collection to remove.", required=True)
 
         elif command == "rebuild-quotas":
             subparser.add_argument(
@@ -219,12 +206,6 @@ def main(args=None):
         env = bootstrap(config_file, options={"command": "migrate"})
         core_scripts.migrate(env, dry_run=dry_run)
 
-    elif which_command == "delete-collection":
-        env = bootstrap(config_file, options={"command": "delete-collection"})
-        return kinto_scripts.delete_collection(
-            env, parsed_args["bucket"], parsed_args["collection"]
-        )
-
     elif which_command == "rebuild-quotas":
         dry_run = parsed_args["dry_run"]
         env = bootstrap(config_file, options={"command": "rebuild-quotas"})
diff --git a/kinto/scripts.py b/kinto/scripts.py
--- a/kinto/scripts.py
+++ b/kinto/scripts.py
@@ -3,66 +3,12 @@
 import transaction as current_transaction
 from pyramid.settings import asbool
 
-from kinto.core.storage import exceptions as storage_exceptions
 from kinto.plugins.quotas import scripts as quotas
 
 
 logger = logging.getLogger(__name__)
 
 
-def delete_collection(env, bucket_id, collection_id):
-    registry = env["registry"]
-    settings = registry.settings
-    readonly_mode = asbool(settings.get("readonly", False))
-
-    if readonly_mode:
-        message = "Cannot delete the collection while in readonly mode."
-        logger.error(message)
-        return 31
-
-    bucket = f"/buckets/{bucket_id}"
-    collection = f"/buckets/{bucket_id}/collections/{collection_id}"
-
-    try:
-        registry.storage.get(resource_name="bucket", parent_id="", object_id=bucket_id)
-    except storage_exceptions.ObjectNotFoundError:
-        logger.error(f"Bucket '{bucket}' does not exist.")
-        return 32
-
-    try:
-        registry.storage.get(resource_name="collection", parent_id=bucket, object_id=collection_id)
-    except storage_exceptions.ObjectNotFoundError:
-        logger.error(f"Collection '{collection}' does not exist.")
-        return 33
-
-    deleted = registry.storage.delete_all(
-        resource_name="record", parent_id=collection, with_deleted=False
-    )
-    if len(deleted) == 0:
-        logger.info(f"No records found for '{collection}'.")
-    else:
-        logger.info(f"{len(deleted)} record(s) were deleted.")
-
-    registry.storage.delete(
-        resource_name="collection", parent_id=bucket, object_id=collection_id, with_deleted=False
-    )
-    logger.info(f"'{collection}' collection object was deleted.")
-
-    obj = "/buckets/{bucket_id}/collections/{collection_id}/records/{object_id}"
-    registry.permission.delete_object_permissions(
-        collection,
-        *[
-            obj.format(bucket_id=bucket_id, collection_id=collection_id, object_id=r["id"])
-            for r in deleted
-        ],
-    )
-    logger.info("Related permissions were deleted.")
-
-    current_transaction.commit()
-
-    return 0
-
-
 def rebuild_quotas(env, dry_run=False):
     """Administrative command to rebuild quota usage information.
 


Test patch:
diff --git a/tests/openapi/test_responses_errors.py b/tests/openapi/test_responses_errors.py
--- a/tests/openapi/test_responses_errors.py
+++ b/tests/openapi/test_responses_errors.py
@@ -308,7 +308,7 @@ def test_list_delete_401(self):
         schema = self.spec.deref(op.op_spec["responses"]["401"])
         validate_response(schema, op, response)
 
-    def test_lidt_delete_403(self):
+    def test_list_delete_403(self):
         headers = {**self.headers, **testing.get_user_headers("aaa")}
         response = self.app.delete("/buckets/b1/collections", headers=headers, status=403)
         response = self.cast_bravado_response(response)
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -159,35 +159,6 @@ def test_cli_migrate_command_runs_init_schema(self):
             assert res == 0
             assert mocked_migrate.call_count == 1
 
-    def test_cli_delete_collection_run_delete_collection_script(self):
-        with mock.patch("kinto.__main__.kinto_scripts.delete_collection") as del_col:
-            del_col.return_value = mock.sentinel.del_col_code
-            res = main(
-                [
-                    "init",
-                    "--ini",
-                    TEMP_KINTO_INI,
-                    "--backend",
-                    "memory",
-                    "--cache-backend",
-                    "memory",
-                ]
-            )
-            assert res == 0
-            res = main(
-                [
-                    "delete-collection",
-                    "--ini",
-                    TEMP_KINTO_INI,
-                    "--bucket",
-                    "test_bucket",
-                    "--collection",
-                    "test_collection",
-                ]
-            )
-            assert res == mock.sentinel.del_col_code
-            assert del_col.call_count == 1
-
     def test_cli_rebuild_quotas_run_rebuild_quotas_script(self):
         with mock.patch("kinto.__main__.kinto_scripts.rebuild_quotas") as reb_quo:
             reb_quo.return_value = mock.sentinel.reb_quo_code
diff --git a/tests/test_scripts.py b/tests/test_scripts.py
--- a/tests/test_scripts.py
+++ b/tests/test_scripts.py
@@ -1,88 +1,6 @@
 import unittest
 from unittest import mock
-
 from kinto import scripts
-from kinto.core.storage.exceptions import ObjectNotFoundError
-
-
-class DeleteCollectionTest(unittest.TestCase):
-    def setUp(self):
-        self.registry = mock.MagicMock()
-
-    def test_delete_collection_in_read_only_display_an_error(self):
-        with mock.patch("kinto.scripts.logger") as mocked:
-            self.registry.settings = {"readonly": "true"}
-            code = scripts.delete_collection(
-                {"registry": self.registry}, "test_bucket", "test_collection"
-            )
-            assert code == 31
-            mocked.error.assert_any_call("Cannot delete the collection while " "in readonly mode.")
-
-    def test_delete_collection_remove_collection_objects(self):
-        self.registry.storage.delete_all.return_value = [{"id": "1234"}, {"id": "5678"}]
-
-        with mock.patch("kinto.scripts.logger") as mocked:
-            scripts.delete_collection(
-                {"registry": self.registry}, "test_bucket", "test_collection"
-            )
-
-        self.registry.storage.delete_all.assert_called_with(
-            resource_name="record",
-            parent_id="/buckets/test_bucket/collections/test_collection",
-            with_deleted=False,
-        )
-        self.registry.storage.delete.assert_called_with(
-            resource_name="collection",
-            parent_id="/buckets/test_bucket",
-            object_id="test_collection",
-            with_deleted=False,
-        )
-        self.registry.permission.delete_object_permissions.assert_called_with(
-            "/buckets/test_bucket/collections/test_collection",
-            "/buckets/test_bucket/collections/test_collection/records/1234",
-            "/buckets/test_bucket/collections/test_collection/records/5678",
-        )
-
-        mocked.info.assert_any_call("2 record(s) were deleted.")
-        mocked.info.assert_any_call(
-            "'/buckets/test_bucket/collections/test_collection' " "collection object was deleted."
-        )
-
-    def test_delete_collection_tell_when_no_objects_where_found(self):
-        self.registry.storage.delete_all.return_value = []
-
-        with mock.patch("kinto.scripts.logger") as mocked:
-            scripts.delete_collection(
-                {"registry": self.registry}, "test_bucket", "test_collection"
-            )
-
-        mocked.info.assert_any_call(
-            "No records found for " "'/buckets/test_bucket/collections/test_collection'."
-        )
-        mocked.info.assert_any_call(
-            "'/buckets/test_bucket/collections/test_collection' " "collection object was deleted."
-        )
-        mocked.info.assert_any_call("Related permissions were deleted.")
-
-    def test_delete_collection_raise_if_the_bucket_does_not_exist(self):
-        self.registry.storage.get.side_effect = ObjectNotFoundError
-        with mock.patch("kinto.scripts.logger") as mocked:
-            resp = scripts.delete_collection(
-                {"registry": self.registry}, "test_bucket", "test_collection"
-            )
-        assert resp == 32
-        mocked.error.assert_called_with("Bucket '/buckets/test_bucket' does not exist.")
-
-    def test_delete_collection_raise_if_the_collection_does_not_exist(self):
-        self.registry.storage.get.side_effect = ["", ObjectNotFoundError]
-        with mock.patch("kinto.scripts.logger") as mocked:
-            resp = scripts.delete_collection(
-                {"registry": self.registry}, "test_bucket", "test_collection"
-            )
-        assert resp == 33
-        mocked.error.assert_called_with(
-            "Collection '/buckets/test_bucket/collections/test_collection' " "does not exist."
-        )
 
 
 class RebuildQuotasTest(unittest.TestCase):