# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Unit tests for the task to copy items into target collections."""

from collections.abc import Sequence
from pathlib import Path
from textwrap import dedent
from typing import Any, ClassVar, override

from django.conf import settings

from debusine.artifacts.models import (
    ArtifactCategory,
    BareDataCategory,
    CollectionCategory,
)
from debusine.client.models import LookupChildType
from debusine.db.models import (
    Artifact,
    Collection,
    CollectionItem,
    FileStore,
    Scope,
    WorkRequest,
    Workspace,
)
from debusine.db.playground import scenarios
from debusine.db.tasks.models import (
    DBInputCollectionSingle,
    DBInputLookupResult,
)
from debusine.server.tasks import CopyCollectionItems
from debusine.server.tasks.copy_collection_items import CannotCopy
from debusine.server.tasks.models import (
    CopyCollectionItemsCopies,
    CopyCollectionItemsData,
)
from debusine.server.tasks.tests.helpers import ServerTaskTestCase
from debusine.tasks.models import LookupMultiple


class CopyCollectionItemsTests(ServerTaskTestCase[CopyCollectionItems]):
    """Tests for :py:class:`CopyCollectionItems`."""

    scenario = scenarios.DefaultContext()

    source_scope: ClassVar[Scope]
    source_workspace: ClassVar[Workspace]
    source_build_logs: ClassVar[Collection]
    target_build_logs: ClassVar[Collection]
    target_build_logs_input: ClassVar[DBInputCollectionSingle]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.source_scope = cls.playground.get_or_create_scope(
            name="source", label="Source"
        )
        cls.source_workspace = cls.playground.create_workspace(
            scope=cls.source_scope, name="source", public=True
        )
        cls.source_build_logs = cls.playground.create_singleton_collection(
            CollectionCategory.PACKAGE_BUILD_LOGS,
            workspace=cls.source_workspace,
        )
        cls.target_build_logs = cls.playground.create_singleton_collection(
            CollectionCategory.PACKAGE_BUILD_LOGS,
        )
        cls.target_build_logs_input = DBInputCollectionSingle.from_collection(
            cls.target_build_logs,
            lookup=f"{cls.target_build_logs.pk}@collections",
            label="copies[0].target_collection",
        )

    @override
    def setUp(self) -> None:
        super().setUp()
        collection_category = CollectionCategory.PACKAGE_BUILD_LOGS
        self.task_data = {
            "copies": [
                CopyCollectionItemsCopies(
                    source_items={
                        "collection": f"_@{collection_category}",
                        "child_type": LookupChildType.BARE,
                    },
                    target_collection=self.target_build_logs.id,
                )
            ]
        }

    def create_build_log_artifact(
        self,
        work_request: WorkRequest | None = None,
        build_log_contents: bytes | None = None,
        skip_add_files_in_store: bool = False,
        extra_data: dict[str, Any] | None = None,
    ) -> Artifact:
        """Create an artifact with a build log."""
        if work_request is None:
            work_request = self.playground.create_worker_task(
                workspace=self.source_workspace
            )
        artifact = self.playground.create_build_log_artifact(
            source="hello",
            version="1.0-1",
            build_arch="amd64",
            workspace=self.source_workspace,
            work_request=work_request,
            contents=build_log_contents,
            skip_add_files_in_store=skip_add_files_in_store,
        )
        if extra_data is not None:
            artifact.data |= extra_data
            artifact.save()
        return artifact

    def create_build_log(
        self,
        work_request: WorkRequest | None = None,
        build_log_contents: bytes | None = None,
        skip_add_files_in_store: bool = False,
        extra_data: dict[str, Any] | None = None,
    ) -> CollectionItem:
        """Create an artifact collection item with a build log."""
        artifact = self.create_build_log_artifact(
            work_request=work_request,
            build_log_contents=build_log_contents,
            skip_add_files_in_store=skip_add_files_in_store,
            extra_data=extra_data,
        )
        assert artifact.created_by_work_request is not None
        artifact_item_data = {
            "work_request_id": artifact.created_by_work_request.id,
            "vendor": "debian",
            "codename": "bookworm",
            "architecture": "amd64",
        }
        return self.source_build_logs.manager.add_artifact(
            artifact, user=self.scenario.user, variables=artifact_item_data
        )

    def create_build_log_placeholder(self) -> CollectionItem:
        return self.playground.create_bare_data_item(
            self.source_build_logs,
            "debian_bookworm_amd64_hello_1.0-1_1",
            category=BareDataCategory.PACKAGE_BUILD_LOG,
            data={
                "work_request_id": 1,
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "srcpkg_name": "hello",
                "srcpkg_version": "1.0-1",
            },
        )

    def prepare_copy_item_test(
        self, lookup: dict[str, Any]
    ) -> Sequence[DBInputLookupResult]:
        """
        Set up a task and lookup results to test ``copy_item``.

        :returns: the :py:class:`DBInputLookupResult` with the source items
          to copy
        """
        source_items = LookupMultiple.model_validate(
            {
                "collection": f"_@{CollectionCategory.PACKAGE_BUILD_LOGS}",
                **lookup,
            }
        )
        self.task_data = {
            "copies": [
                CopyCollectionItemsCopies(
                    source_items=source_items,
                    target_collection=self.target_build_logs.id,
                )
            ]
        }
        results = self.work_request.lookup_multiple(
            source_items,
            workspace=self.source_workspace,
            expect_type=LookupChildType.ANY,
        )
        return [
            DBInputLookupResult.from_lookup_result(
                result, label="copies[0].source_items"
            )
            for result in results
        ]

    def test_compute_scheduler_tags(self) -> None:
        self.assertComputesSchedulerTags()

    def test_copy_item_bare(self) -> None:
        """Copy a bare data item."""
        bare_item = self.create_build_log_placeholder()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.BARE}
        )
        self.assertEqual(len(source_items), 1)

        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
        )

        self.assertTrue(
            self.target_build_logs.child_items.filter(
                name="debian_bookworm_amd64_hello_1.0-1_1",
                child_type=CollectionItem.Types.BARE,
                category=BareDataCategory.PACKAGE_BUILD_LOG,
                collection__isnull=True,
                artifact__isnull=True,
                data=bare_item.data,
                removed_at__isnull=True,
            ).exists()
        )

    def test_copy_item_bare_needs_unembargo(self) -> None:
        """Copying bare data from private to public needs unembargo=True."""
        self.create_build_log_placeholder()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.BARE}
        )
        self.assertEqual(len(source_items), 1)
        self.source_workspace.public = False
        self.source_workspace.save()

        with self.assertRaisesRegex(
            CannotCopy,
            f"Copying from {self.source_workspace}"
            f" to {self.scenario.workspace} "
            f"requires unembargo=True",
        ):
            self.task.copy_item(
                source_items[0],
                self.target_build_logs_input,
                user=self.scenario.user,
            )

    def test_copy_item_bare_with_unembargo(self) -> None:
        """Copying bare data from private to public can unembargo it."""
        bare_item = self.create_build_log_placeholder()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.BARE}
        )
        self.assertEqual(len(source_items), 1)
        self.source_workspace.public = False
        self.source_workspace.save()

        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
            unembargo=True,
        )

        self.assertTrue(
            self.target_build_logs.child_items.filter(
                name="debian_bookworm_amd64_hello_1.0-1_1",
                child_type=CollectionItem.Types.BARE,
                category=BareDataCategory.PACKAGE_BUILD_LOG,
                collection__isnull=True,
                artifact__isnull=True,
                data=bare_item.data,
                removed_at__isnull=True,
            ).exists()
        )

    def test_copy_item_bare_variables(self) -> None:
        """Pass additional variables while copying a bare item."""
        bare_item = self.create_build_log_placeholder()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.BARE}
        )
        self.assertEqual(len(source_items), 1)

        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
            variables={"additional": "foo"},
        )

        self.assertTrue(
            self.target_build_logs.child_items.filter(
                name="debian_bookworm_amd64_hello_1.0-1_1",
                child_type=CollectionItem.Types.BARE,
                category=BareDataCategory.PACKAGE_BUILD_LOG,
                collection__isnull=True,
                artifact__isnull=True,
                data={**bare_item.data, "additional": "foo"},
                removed_at__isnull=True,
            ).exists()
        )

    def test_copy_item_artifact(self) -> None:
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[self.scenario.user],
        )
        artifact_item = self.create_build_log(work_request=sbuild_work_request)
        artifact = artifact_item.artifact
        assert artifact is not None
        build_log_file = artifact.files.get()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        source_file_store = self.source_scope.download_file_stores(
            build_log_file
        ).get()

        self.task.work_request.set_current()
        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
        )

        copied_collection_item = self.target_build_logs.child_items.get(
            name=f"debian_bookworm_amd64_hello_1.0-1_{sbuild_work_request.id}",
            child_type=CollectionItem.Types.ARTIFACT,
            category=ArtifactCategory.PACKAGE_BUILD_LOG,
            collection__isnull=True,
            data={
                **artifact_item.data,
                "srcpkg_name": "hello",
                "srcpkg_version": "1.0-1",
            },
            removed_at__isnull=True,
        )
        # The artifact was copied.
        copied_artifact = copied_collection_item.artifact
        assert copied_artifact is not None
        self.assertNotEqual(copied_artifact, artifact)
        self.assertEqual(copied_artifact.category, artifact.category)
        self.assertEqual(copied_artifact.workspace, self.scenario.workspace)
        self.assertEqual(copied_artifact.data, artifact.data)
        self.assertEqual(
            copied_artifact.expiration_delay,
            artifact.expiration_delay,
        )
        self.assertNotEqual(copied_artifact.created_at, artifact.created_at)
        self.assertEqual(copied_artifact.created_by, artifact.created_by)
        self.assertEqual(
            copied_artifact.created_by_work_request,
            artifact.created_by_work_request,
        )
        self.assertEqual(copied_artifact.original_artifact, artifact)
        self.assertEqual(copied_artifact.files.get(), build_log_file)
        # The file in the copied artifact is in the same store.
        target_file_store = self.scenario.scope.upload_file_stores(
            build_log_file
        ).get()
        self.assertEqual(source_file_store, target_file_store)

    def test_copy_item_artifact_needs_unembargo(self) -> None:
        """Copying an artifact from private to public needs unembargo=True."""
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.create_build_log(work_request=sbuild_work_request)
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        self.source_workspace.public = False
        self.source_workspace.save()

        with self.assertRaisesRegex(
            CannotCopy,
            f"Copying from {self.source_workspace}"
            f" to {self.scenario.workspace} "
            f"requires unembargo=True",
        ):
            self.task.copy_item(
                source_items[0],
                self.target_build_logs_input,
                user=self.scenario.user,
            )

    def test_copy_item_artifact_with_unembargo(self) -> None:
        """Copying an artifact from private to public can unembargo it."""
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace, assign_contributor_role=True
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[sbuild_work_request.created_by],
        )
        artifact_item = self.create_build_log(work_request=sbuild_work_request)
        artifact = artifact_item.artifact
        assert artifact is not None
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        self.source_workspace.public = False
        self.source_workspace.save()

        self.task.work_request.set_current()
        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
            unembargo=True,
        )

        copied_collection_item = self.target_build_logs.child_items.get(
            name=f"debian_bookworm_amd64_hello_1.0-1_{sbuild_work_request.id}",
            child_type=CollectionItem.Types.ARTIFACT,
            category=ArtifactCategory.PACKAGE_BUILD_LOG,
            collection__isnull=True,
            data={
                **artifact_item.data,
                "srcpkg_name": "hello",
                "srcpkg_version": "1.0-1",
            },
            removed_at__isnull=True,
        )
        # The artifact was copied.
        copied_artifact = copied_collection_item.artifact
        assert copied_artifact is not None
        self.assertEqual(copied_artifact.workspace, self.scenario.workspace)

    def test_copy_item_artifact_variables(self) -> None:
        """Pass additional variables while copying an artifact."""
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[sbuild_work_request.created_by],
        )
        self.create_build_log(extra_data={"nested": {"foo": "bar"}})
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        target_collection = self.playground.create_collection(
            name="test",
            category=CollectionCategory.WORKFLOW_INTERNAL,
            workspace=self.scenario.workspace,
        )

        self.task.work_request.set_current()
        self.task.copy_item(
            source_items[0],
            DBInputCollectionSingle.from_collection(
                target_collection,
                lookup="internal@collections",
                label="copies[0].target_collection",
            ),
            user=self.scenario.user,
            name_template="{vendor}_{nested_foo}",
            variables={"$nested_foo": "nested.foo"},
        )

        self.assertTrue(
            target_collection.child_items.filter(
                name="debian_bar",
                child_type=CollectionItem.Types.ARTIFACT,
                category=ArtifactCategory.PACKAGE_BUILD_LOG,
                collection__isnull=True,
                data={},
                removed_at__isnull=True,
            ).exists()
        )

    def test_copy_item_artifact_failed_to_expand_variables(self) -> None:
        """Fail if the given variables cannot be expanded."""
        self.create_build_log()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)

        with self.assertRaisesRegex(
            CannotCopy, r"Cannot expand variables: \{'\$key': 'nonexistent'\}"
        ):
            self.task.copy_item(
                source_items[0],
                self.target_build_logs_input,
                user=self.scenario.user,
                variables={"$key": "nonexistent"},
            )

    def test_copy_item_artifact_incomplete_file(self) -> None:
        """We cannot copy artifacts with incomplete files."""
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[sbuild_work_request.created_by],
        )
        artifact_item = self.create_build_log(skip_add_files_in_store=True)
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)

        self.task.work_request.set_current()
        with self.assertRaisesRegex(
            CannotCopy,
            f"Cannot copy incomplete 'hello_1.0-1_amd64.buildlog' from "
            f"artifact ID {artifact_item.artifact_id}",
        ):
            self.task.copy_item(
                source_items[0],
                self.target_build_logs_input,
                user=self.scenario.user,
            )

    def test_copy_item_artifact_not_in_any_file_store(self) -> None:
        """We cannot copy artifacts that are not in any file store."""
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[sbuild_work_request.created_by],
        )
        artifact_item = self.create_build_log()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        self.source_scope.file_stores.get().files.clear()

        self.task.work_request.set_current()
        with self.assertRaisesRegex(
            CannotCopy,
            f"Cannot copy 'hello_1.0-1_amd64.buildlog' from artifact ID "
            f"{artifact_item.artifact_id}: not in any available file store",
        ):
            self.task.copy_item(
                source_items[0],
                self.target_build_logs_input,
                user=self.scenario.user,
            )

    def test_copy_item_artifact_different_file_store(self) -> None:
        """
        Copy artifact file contents from a different file store.

        We copy between scopes in order to exercise the case where the
        download and upload file stores are different.
        """
        file_store = FileStore.objects.create(
            name="test",
            backend=FileStore.BackendChoices.LOCAL,
            configuration={"base_directory": settings.DEBUSINE_STORE_DIRECTORY},
        )
        self.source_scope.file_stores.set([file_store])
        sbuild_work_request = self.playground.create_worker_task(
            workspace=self.source_workspace
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[sbuild_work_request.created_by],
        )
        build_log_contents = b"a build log\n"
        artifact_item = self.create_build_log(
            work_request=sbuild_work_request,
            build_log_contents=build_log_contents,
        )
        artifact = artifact_item.artifact
        assert artifact is not None
        build_log_file = artifact.files.get()
        source_items = self.prepare_copy_item_test(
            {"child_type": LookupChildType.ARTIFACT}
        )
        self.assertEqual(len(source_items), 1)
        source_file_store = self.source_scope.download_file_stores(
            build_log_file
        ).get()

        self.task.work_request.set_current()
        self.task.copy_item(
            source_items[0],
            self.target_build_logs_input,
            user=self.scenario.user,
        )

        copied_collection_item = self.target_build_logs.child_items.get(
            name=f"debian_bookworm_amd64_hello_1.0-1_{sbuild_work_request.id}",
            child_type=CollectionItem.Types.ARTIFACT,
            category=ArtifactCategory.PACKAGE_BUILD_LOG,
            collection__isnull=True,
            data={
                **artifact_item.data,
                "srcpkg_name": "hello",
                "srcpkg_version": "1.0-1",
            },
            removed_at__isnull=True,
        )
        copied_artifact = copied_collection_item.artifact
        assert copied_artifact is not None
        self.assertNotEqual(copied_artifact, artifact)
        self.assertQuerySetEqual(copied_artifact.files.all(), [build_log_file])
        # The file in the copied artifact is in a different store.
        target_file_store = self.scenario.scope.upload_file_stores(
            build_log_file
        ).get()
        self.assertNotEqual(source_file_store, target_file_store)
        # The file contents were copied.
        with (
            source_file_store.get_backend_object().get_stream(
                build_log_file
            ) as source_file,
            target_file_store.get_backend_object().get_stream(
                build_log_file
            ) as target_file,
        ):
            self.assertEqual(target_file.read(), source_file.read())

    def test_execute(self) -> None:
        """The task copies all the given items."""
        # Partially simulate a package build in a workflow, and add the
        # resulting artifacts to appropriate source collections as starting
        # points for copies.
        template = self.playground.create_workflow_template(
            name="test", task_name="noop"
        )
        workflow = self.playground.create_workflow(template, task_data={})
        assert workflow.internal_collection is not None
        source_package = self.playground.create_source_artifact(
            workspace=self.source_workspace
        )
        sbuild = self.playground.simulate_package_build(
            source_package, workflow=workflow
        )
        binary_package = sbuild.artifact_set.get(
            category=ArtifactCategory.BINARY_PACKAGE
        )
        workflow.internal_collection.manager.add_artifact(
            binary_package,
            user=sbuild.created_by,
            workflow=workflow,
            name="binary-amd64",
        )
        build_log = sbuild.artifact_set.get(
            category=ArtifactCategory.PACKAGE_BUILD_LOG
        )
        workflow.internal_collection.manager.add_artifact(
            build_log,
            user=sbuild.created_by,
            workflow=workflow,
            name="build-log",
            variables={
                "work_request_id": sbuild.id,
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
            },
        )

        # Make a target suite and a task to perform the copies.
        target_suite = self.playground.create_collection(
            name="trixie",
            category=CollectionCategory.SUITE,
            workspace=self.scenario.workspace,
        )
        binary_package_lookup = LookupMultiple.model_validate(
            {
                "collection": "internal@collections",
                "category": ArtifactCategory.BINARY_PACKAGE,
            }
        )
        copy_collection_items = workflow.create_child_server(
            task_name="copycollectionitems",
            task_data=CopyCollectionItemsData(
                copies=[
                    CopyCollectionItemsCopies(
                        source_items=binary_package_lookup,
                        target_collection=target_suite.id,
                        variables={
                            "component": "main",
                            "section": "python",
                            "priority": "optional",
                        },
                    ),
                    CopyCollectionItemsCopies(
                        source_items=LookupMultiple.model_validate(
                            {
                                "collection": "internal@collections",
                                "name": "build-log",
                            }
                        ),
                        target_collection=self.target_build_logs.id,
                    ),
                ]
            ),
        )
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[copy_collection_items.created_by],
        )
        self.playground.advance_work_request(
            copy_collection_items, assign_new_worker=True
        )
        task = copy_collection_items.get_task()

        with self.playground.assign_role(
            self.source_workspace,
            copy_collection_items.created_by,
            Workspace.Roles.CONTRIBUTOR,
        ):
            self.assertEqual(task.execute(), WorkRequest.Results.SUCCESS)

        debug_logs_artifact = copy_collection_items.artifact_set.get(
            category=ArtifactCategory.WORK_REQUEST_DEBUG_LOGS
        )
        copy_log = debug_logs_artifact.fileinartifact_set.get()
        self.assertEqual(copy_log.path, "copy-collection-items.log")
        fileobj = copy_log.file
        file_backend = self.source_workspace.scope.download_file_backend(
            fileobj
        )
        with file_backend.get_stream(fileobj) as file:
            self.assertEqual(
                file.read().decode(),
                dedent(
                    f"""\
                    Copying to {target_suite}: {binary_package}
                    Copying to {self.target_build_logs}: {build_log}
                    """
                ),
            )

        # The target collections contain the requested items.
        self.assertTrue(
            target_suite.child_items.filter(
                name="hello_1.0-1_all",
                child_type=CollectionItem.Types.ARTIFACT,
                category=ArtifactCategory.BINARY_PACKAGE,
                artifact__isnull=False,
                data={
                    "srcpkg_name": "hello",
                    "srcpkg_version": "1.0-1",
                    "package": "hello",
                    "version": "1.0-1",
                    "architecture": "all",
                    "component": "main",
                    "section": "python",
                    "priority": "optional",
                },
                removed_at__isnull=True,
            ).exists()
        )
        self.assertTrue(
            self.target_build_logs.child_items.filter(
                name=f"debian_bookworm_amd64_hello_1.0-1_{sbuild.id}",
                child_type=CollectionItem.Types.ARTIFACT,
                category=ArtifactCategory.PACKAGE_BUILD_LOG,
                artifact__isnull=False,
                data__work_request_id=sbuild.id,
                removed_at__isnull=True,
            ).exists()
        )

    def test_execute_copy_fails(self) -> None:
        self.create_build_log_placeholder()
        source_items = LookupMultiple.model_validate(
            {
                "collection": self.source_build_logs.id,
                "child_type": LookupChildType.BARE,
            }
        )
        self.task_data = {
            "copies": [
                CopyCollectionItemsCopies(
                    source_items=source_items,
                    target_collection=self.target_build_logs.id,
                )
            ]
        }
        copy_collection_items = self.work_request

        self.source_workspace.public = False
        self.source_workspace.save()
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[copy_collection_items.created_by],
        )
        self.playground.advance_work_request(
            copy_collection_items, assign_new_worker=True
        )

        with (
            self.playground.assign_role(
                self.source_workspace,
                copy_collection_items.created_by,
                Workspace.Roles.CONTRIBUTOR,
            ),
            self.assertRaises(CannotCopy),
        ):
            self.task.execute()

        assert self.task._debug_log_files_directory is not None
        self.assertEqual(
            Path(
                self.task._debug_log_files_directory.name,
                "copy-collection-items.log",
            ).read_text(),
            f"Copy failed: Copying from {self.source_workspace} to "
            f"{self.scenario.workspace} requires unembargo=True\n",
        )

    def test_get_label(self) -> None:
        """Test get_label."""
        self.playground.create_collection(
            name="bookworm", category=CollectionCategory.SUITE
        )
        self.playground.create_collection(
            name="trixie", category=CollectionCategory.SUITE
        )
        self.task_data = {
            "copies": [
                {
                    "source_items": {"collection": 1},
                    "target_collection": "bookworm@debian:suite",
                },
                {
                    "source_items": {"collection": 1},
                    "target_collection": "trixie@debian:suite",
                },
            ]
        }

        self.assertEqual(
            self.task.get_label(),
            "copy to bookworm@debian:suite, trixie@debian:suite",
        )
