# 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 package_publish workflow."""

import logging
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any, ClassVar

from debusine.artifacts.models import CollectionCategory, TaskTypes
from debusine.db.models import (
    Artifact,
    Collection,
    TaskDatabase,
    WorkRequest,
    Workspace,
)
from debusine.db.playground import scenarios
from debusine.server.celery import run_server_task
from debusine.server.workflows import PackagePublishWorkflow
from debusine.server.workflows.base import orchestrate_workflow
from debusine.server.workflows.models import (
    BaseWorkflowData,
    PackagePublishWorkflowData,
    SbuildWorkflowData,
    WorkRequestWorkflowData,
)
from debusine.server.workflows.tests.helpers import (
    SampleWorkflow,
    WorkflowTestBase,
)
from debusine.tasks.models import (
    BaseDynamicTaskData,
    LookupMultiple,
    SbuildInput,
)
from debusine.test.test_utils import preserve_task_registry


@dataclass
class ExpectedCopy:
    """
    A summary of an expected copy.

    This exists to abbreviate tests, and is only useful for copying
    source/binary artifacts.
    """

    artifact: Artifact
    component: str
    section: str
    priority: str | None = None

    @property
    def source_items(self) -> list[str]:
        return [f"{self.artifact.id}@artifacts"]

    @property
    def variables(self) -> dict[str, str]:
        variables = {"component": self.component, "section": self.section}
        if self.priority is not None:
            variables["priority"] = self.priority
        return variables


class PackagePublishWorkflowTests(WorkflowTestBase[PackagePublishWorkflow]):
    """Unit tests for :py:class:`PackagePublishWorkflow`."""

    scenario = scenarios.DefaultContext()
    source_artifact: ClassVar[Artifact]
    binary_artifact: ClassVar[Artifact]

    @classmethod
    def setUpTestData(cls) -> None:
        """Set up common data for tests."""
        super().setUpTestData()
        cls.playground.create_group_role(
            cls.scenario.workspace,
            Workspace.Roles.OWNER,
            users=[cls.scenario.user],
        )
        cls.source_artifact = cls.playground.create_source_artifact(
            name="hello"
        )
        cls.binary_artifact = (
            cls.playground.create_minimal_binary_package_artifact("hello")
        )

    def create_suite_collection(
        self, name: str, *, workspace: Workspace | None = None
    ) -> Collection:
        """Create a `debian:suite` collection."""
        return self.playground.create_collection(
            name=name, category=CollectionCategory.SUITE, workspace=workspace
        )

    def create_package_publish_workflow(
        self, task_data: dict[str, Any]
    ) -> PackagePublishWorkflow:
        """Create a package_publish workflow."""
        wr = self.playground.create_workflow(
            task_name="package_publish", task_data=task_data
        )
        return self.get_workflow(wr)

    def test_create_orchestrator(self) -> None:
        """A PackagePublishWorkflow can be instantiated."""
        source_artifact = self.source_artifact.pk
        binary_artifacts = [self.binary_artifact.pk]
        target_suite = f"bookworm@{CollectionCategory.SUITE}"

        workflow = self.create_package_publish_workflow(
            {
                "source_artifact": source_artifact,
                "binary_artifacts": binary_artifacts,
                "target_suite": target_suite,
            }
        )

        self.assertEqual(workflow.data.source_artifact, source_artifact)
        self.assertEqual(
            workflow.data.binary_artifacts,
            LookupMultiple.parse_obj(binary_artifacts),
        )
        self.assertEqual(workflow.data.target_suite, target_suite)

    def orchestrate(
        self,
        task_data: PackagePublishWorkflowData,
        source_artifact: Artifact,
        architectures: Sequence[str],
        *,
        workspace: Workspace | None = None,
    ) -> WorkRequest:
        """Create and orchestrate a PackagePublishWorkflow."""

        class ExamplePipeline(
            SampleWorkflow[BaseWorkflowData, BaseDynamicTaskData]
        ):
            """Example pipeline for package publishing."""

            def populate(self) -> None:
                """Populate the pipeline."""
                sbuild = self.work_request_ensure_child_workflow(
                    task_name="sbuild",
                    task_data=SbuildWorkflowData(
                        input=SbuildInput(source_artifact=source_artifact.id),
                        target_distribution="debian:sid",
                        architectures=list(architectures) or ["all"],
                    ),
                    workflow_data=WorkRequestWorkflowData(
                        display_name="sbuild", step="sbuild"
                    ),
                )
                self.orchestrate_child(sbuild)

                package_publish = self.work_request_ensure_child_workflow(
                    task_name="package_publish",
                    task_data=task_data,
                    workflow_data=WorkRequestWorkflowData(
                        display_name="publish packages", step="package_publish"
                    ),
                )

                if task_data.source_artifact is not None:
                    self.requires_artifact(
                        package_publish, task_data.source_artifact
                    )
                self.requires_artifact(
                    package_publish, task_data.binary_artifacts
                )
                self.orchestrate_child(package_publish)

        template = self.playground.create_workflow_template(
            name="examplepipeline-template",
            task_name="examplepipeline",
            workspace=workspace,
        )
        root = self.playground.create_workflow(task_name="examplepipeline")
        self.assertEqual(root.workspace, template.workspace)
        self.assertTrue(orchestrate_workflow(root))

        return root

    def assert_copies(
        self,
        work_request: WorkRequest,
        target_collection: Collection,
        expected_copies: Sequence[ExpectedCopy],
    ) -> None:
        """Assert that a task declares the expected copies."""
        self.assertEqual(
            work_request.task_data,
            {
                "copies": [
                    {
                        "source_items": copy.source_items,
                        "target_collection": target_collection.id,
                        "unembargo": False,
                        "replace": False,
                        "variables": copy.variables,
                    }
                    for copy in expected_copies
                ]
            },
        )

    def assert_updates_indexes(
        self, package_publish: WorkRequest, target_suite: Collection
    ) -> None:
        """Assert that the workflow updates indexes after copying."""
        copy_collection_items = package_publish.children.get(
            task_type=TaskTypes.SERVER, task_name="copycollectionitems"
        )
        self.playground.assign_worker(copy_collection_items)
        trigger_update_suites = package_publish.children.get(
            task_type=TaskTypes.INTERNAL,
            task_name="workflow",
            workflow_data_json__step="trigger-update-suites",
        )

        self.assertQuerySetEqual(
            trigger_update_suites.dependencies.all(), [copy_collection_items]
        )

        copy_collection_items_result = run_server_task.apply(
            args=(copy_collection_items.id,)
        )
        self.assertTrue(copy_collection_items_result.get())

        trigger_update_suites.refresh_from_db()
        self.assertEqual(
            trigger_update_suites.status, WorkRequest.Statuses.PENDING
        )

        self.schedule_and_run_workflow_callback(package_publish)
        trigger_update_suites.refresh_from_db()
        self.assertEqual(
            trigger_update_suites.status, WorkRequest.Statuses.COMPLETED
        )
        self.assertEqual(
            trigger_update_suites.result, WorkRequest.Results.SUCCESS
        )

        update_suites = target_suite.workspace.workrequest_set.get(
            task_type=TaskTypes.WORKFLOW, task_name="update_suites"
        )
        self.assertEqual(update_suites.status, WorkRequest.Statuses.PENDING)
        self.assertEqual(
            update_suites.task_data, {"only_suites": [target_suite.name]}
        )

        self.schedule_and_run_workflow(update_suites)
        update_suites.refresh_from_db()
        self.assertEqual(update_suites.status, WorkRequest.Statuses.RUNNING)
        self.assertTrue(update_suites.children.exists())

    @preserve_task_registry()
    def test_populate_source_and_binary(self) -> None:
        """Test population with both source and binary artifacts."""
        source_artifact = self.playground.create_source_artifact(
            name="hello", architectures={"any", "all"}
        )
        target_suite = self.create_suite_collection("bookworm")
        architectures = ("amd64", "i386", "all")

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                source_artifact=source_artifact.id,
                binary_artifacts=LookupMultiple.parse_obj(
                    [
                        f"internal@collections/name:build-{arch}"
                        for arch in architectures
                    ]
                ),
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
            ),
            source_artifact=source_artifact,
            architectures=architectures,
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        self.assertEqual(package_publish.status, WorkRequest.Statuses.BLOCKED)

        sbuild_workflow = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        binaries = self.simulate_sbuild_workflow_completion(sbuild_workflow)

        package_publish.refresh_from_db()
        self.assertEqual(package_publish.status, WorkRequest.Statuses.PENDING)
        self.schedule_and_run_workflow(root)

        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        expected = [ExpectedCopy(source_artifact, "main", "misc")]
        for arch in architectures:
            expected += [
                ExpectedCopy(binary_artifact, "main", "misc", "optional")
                for binary_artifact in binaries[arch]
            ]
        self.assert_copies(copy_collection_items, target_suite, expected)
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_source_only(self) -> None:
        """Test population with only a source artifact."""
        source_artifact = self.playground.create_source_artifact(
            name="hello", version="1.0-1", architectures={"all"}
        )
        target_suite = self.create_suite_collection("bookworm")

        with self.assertLogs(
            logger="debusine.server.workflows.package_publish",
            level=logging.WARNING,
        ) as log:
            root = self.orchestrate(
                task_data=PackagePublishWorkflowData(
                    source_artifact=source_artifact.id,
                    target_suite=(
                        f"{target_suite.name}@{CollectionCategory.SUITE}"
                    ),
                ),
                source_artifact=source_artifact,
                architectures=[],
            )
        self.assertEqual(
            [record.message for record in log.records],
            [
                f"No component specified when adding hello_1.0-1 to "
                f"bookworm@{CollectionCategory.SUITE}; defaulting to 'main'",
                f"No section specified when adding hello_1.0-1 to "
                f"bookworm@{CollectionCategory.SUITE}; defaulting to 'misc'",
            ],
        )

        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )
        self.assert_copies(
            copy_collection_items,
            target_suite,
            [ExpectedCopy(source_artifact, "main", "misc")],
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_source_upload_only(self) -> None:
        """Test population with only a source upload artifact."""
        upload_artifacts = self.playground.create_upload_artifacts(
            src_name="hello",
            src_architectures={"any", "all"},
            binary=False,
            section="editors",
        )
        target_suite = self.create_suite_collection("bookworm")

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                source_artifact=upload_artifacts.upload.id,
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
            ),
            source_artifact=upload_artifacts.source,
            architectures=[],
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        self.assert_copies(
            copy_collection_items,
            target_suite,
            [ExpectedCopy(upload_artifacts.source, "main", "editors")],
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_missing_binary_fields(self) -> None:
        """Population warns about missing binary component/section/priority."""
        source_artifact = self.playground.create_source_artifact(
            name="hello", version="1.0-1", architectures={"all"}
        )
        binary_artifact = (
            self.playground.create_minimal_binary_package_artifact(
                srcpkg_name="hello",
                srcpkg_version="1.0-1",
                package="hello",
                version="1.0-1",
                architecture="all",
            )
        )
        target_suite = self.create_suite_collection("bookworm")

        with self.assertLogs(
            logger="debusine.server.workflows.package_publish",
            level=logging.WARNING,
        ) as log:
            root = self.orchestrate(
                task_data=PackagePublishWorkflowData(
                    binary_artifacts=LookupMultiple.parse_obj(
                        [binary_artifact.id]
                    ),
                    target_suite=(
                        f"{target_suite.name}@{CollectionCategory.SUITE}"
                    ),
                ),
                source_artifact=source_artifact,
                architectures=[],
            )
        self.assertEqual(
            [record.message for record in log.records],
            [
                f"No component specified when adding hello_1.0-1_all to "
                f"bookworm@{CollectionCategory.SUITE}; defaulting to 'main'",
                f"No section specified when adding hello_1.0-1_all to "
                f"bookworm@{CollectionCategory.SUITE}; defaulting to 'misc'",
                f"No priority specified when adding hello_1.0-1_all to "
                f"bookworm@{CollectionCategory.SUITE}; defaulting to "
                f"'optional'",
            ],
        )

        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )
        self.assert_copies(
            copy_collection_items,
            target_suite,
            [ExpectedCopy(binary_artifact, "main", "misc", "optional")],
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_non_main_component(self) -> None:
        """Population handles sections with non-main components."""
        upload_artifacts = self.playground.create_upload_artifacts(
            src_name="hello",
            src_architectures={"any", "all"},
            binary=False,
            section="contrib/games",
        )
        target_suite = self.create_suite_collection("bookworm")

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                source_artifact=upload_artifacts.upload.id,
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
            ),
            source_artifact=upload_artifacts.source,
            architectures=[],
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        self.assert_copies(
            copy_collection_items,
            target_suite,
            [ExpectedCopy(upload_artifacts.source, "contrib", "games")],
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_honours_suite_variables(self) -> None:
        """Population honours ``suite_variables``."""
        source_artifact = self.playground.create_source_artifact(
            name="hello", version="1.0-1", architectures={"all"}
        )
        binary_artifact = (
            self.playground.create_minimal_binary_package_artifact(
                srcpkg_name="hello",
                srcpkg_version="1.0-1",
                package="hello",
                version="1.0-1",
                architecture="all",
            )
        )
        target_suite = self.create_suite_collection("bookworm")

        with self.assertNoLogs(
            logger="debusine.server.workflows.package_publish"
        ):
            root = self.orchestrate(
                task_data=PackagePublishWorkflowData(
                    source_artifact=source_artifact.id,
                    binary_artifacts=LookupMultiple.parse_obj(
                        [binary_artifact.id]
                    ),
                    target_suite=(
                        f"{target_suite.name}@{CollectionCategory.SUITE}"
                    ),
                    suite_variables={
                        "component": "non-free",
                        "section": "editors",
                        "priority": "extra",
                    },
                ),
                source_artifact=source_artifact,
                architectures=[],
            )

        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )
        self.assert_copies(
            copy_collection_items,
            target_suite,
            [
                ExpectedCopy(artifact, "non-free", "editors", "extra")
                for artifact in (source_artifact, binary_artifact)
            ],
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_no_update_indexes(self) -> None:
        """The workflow can be told not to update indexes."""
        upload_artifacts = self.playground.create_upload_artifacts(
            src_name="hello",
            src_architectures={"any", "all"},
            binary=False,
            section="editors",
        )
        target_suite = self.create_suite_collection("bookworm")

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                source_artifact=upload_artifacts.upload.id,
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
                update_indexes=False,
            ),
            source_artifact=upload_artifacts.source,
            architectures=[],
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        self.assert_copies(
            copy_collection_items,
            target_suite,
            [ExpectedCopy(upload_artifacts.source, "main", "editors")],
        )
        self.assertFalse(
            package_publish.children.filter(
                task_type=TaskTypes.INTERNAL,
                task_name="workflow",
                workflow_data_json__step="trigger-update-suites",
            ).exists()
        )

    @preserve_task_registry()
    def test_populate_package_build_logs(self) -> None:
        """Test population with binary artifacts and copying build logs."""
        source_workspace = self.playground.create_workspace(name="source")
        self.playground.create_group_role(
            source_workspace, Workspace.Roles.OWNER, users=[self.scenario.user]
        )
        target_workspace = self.playground.create_workspace(name="target")
        self.playground.create_group_role(
            target_workspace, Workspace.Roles.OWNER, users=[self.scenario.user]
        )
        self.playground.create_singleton_collection(
            CollectionCategory.ARCHIVE, workspace=target_workspace
        )
        source_workspace.set_inheritance([target_workspace])
        target_suite = self.create_suite_collection(
            "bookworm", workspace=target_workspace
        )
        source_build_logs_collection = (
            self.playground.create_singleton_collection(
                CollectionCategory.PACKAGE_BUILD_LOGS,
                workspace=source_workspace,
            )
        )
        target_build_logs_collection = (
            self.playground.create_singleton_collection(
                CollectionCategory.PACKAGE_BUILD_LOGS,
                workspace=target_workspace,
            )
        )
        source_artifact = self.playground.create_source_artifact(
            name="hello", architectures={"any", "all"}
        )
        architectures = ("amd64", "i386", "all")
        binary_artifacts_lookup = [
            f"internal@collections/name:build-{arch}" for arch in architectures
        ]

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                binary_artifacts=LookupMultiple.parse_obj(
                    binary_artifacts_lookup
                ),
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
                unembargo=True,
            ),
            source_artifact=source_artifact,
            architectures=architectures,
            workspace=source_workspace,
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        self.assertEqual(package_publish.status, WorkRequest.Statuses.BLOCKED)

        sbuild_workflow = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        binaries = self.simulate_sbuild_workflow_completion(sbuild_workflow)

        package_publish.refresh_from_db()
        self.assertEqual(package_publish.status, WorkRequest.Statuses.PENDING)
        self.schedule_and_run_workflow(root)

        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        expected_copies: list[dict[str, Any]] = []
        for arch in architectures:
            for binary_artifact in binaries[arch]:
                expected_copies.append(
                    {
                        "source_items": [f"{binary_artifact.id}@artifacts"],
                        "target_collection": target_suite.id,
                        "unembargo": True,
                        "replace": False,
                        "variables": {
                            "component": "main",
                            "section": "misc",
                            "priority": "optional",
                        },
                    }
                )
        expected_copies.append(
            {
                "source_items": [
                    {
                        "collection": source_build_logs_collection.id,
                        "lookup_filters": [
                            [
                                "same_work_request",
                                binary_artifacts_lookup,
                            ]
                        ],
                    }
                ],
                "target_collection": target_build_logs_collection.id,
                "unembargo": True,
                "replace": False,
            }
        )
        self.assertEqual(
            copy_collection_items.task_data, {"copies": expected_copies}
        )
        self.assert_updates_indexes(package_publish, target_suite)

    @preserve_task_registry()
    def test_populate_package_task_history(self) -> None:
        """Test population with binary artifacts and copying build logs."""
        source_workspace = self.playground.create_workspace(name="source")
        self.playground.create_group_role(
            source_workspace, Workspace.Roles.OWNER, users=[self.scenario.user]
        )
        target_workspace = self.playground.create_workspace(name="target")
        self.playground.create_group_role(
            target_workspace, Workspace.Roles.OWNER, users=[self.scenario.user]
        )
        self.playground.create_singleton_collection(
            CollectionCategory.ARCHIVE, workspace=target_workspace
        )
        source_workspace.set_inheritance([target_workspace])
        target_suite = self.create_suite_collection(
            "bookworm", workspace=target_workspace
        )
        source_task_history_collection = (
            self.playground.create_singleton_collection(
                CollectionCategory.TASK_HISTORY,
                workspace=source_workspace,
            )
        )
        target_task_history_collection = (
            self.playground.create_singleton_collection(
                CollectionCategory.TASK_HISTORY,
                workspace=target_workspace,
            )
        )
        source_artifact = self.playground.create_source_artifact(
            name="hello", architectures={"any", "all"}
        )
        architectures = ("amd64", "i386", "all")
        binary_artifacts_lookup = [
            f"internal@collections/name:build-{arch}" for arch in architectures
        ]

        root = self.orchestrate(
            task_data=PackagePublishWorkflowData(
                binary_artifacts=LookupMultiple.parse_obj(
                    binary_artifacts_lookup
                ),
                target_suite=(
                    f"{target_suite.name}@{CollectionCategory.SUITE}"
                ),
                unembargo=True,
            ),
            source_artifact=source_artifact,
            architectures=architectures,
            workspace=source_workspace,
        )
        package_publish = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="package_publish"
        )
        self.assertEqual(package_publish.status, WorkRequest.Statuses.BLOCKED)

        sbuild_workflow = root.children.get(
            task_type=TaskTypes.WORKFLOW, task_name="sbuild"
        )
        binaries = self.simulate_sbuild_workflow_completion(sbuild_workflow)

        package_publish.refresh_from_db()
        self.assertEqual(package_publish.status, WorkRequest.Statuses.PENDING)
        self.schedule_and_run_workflow(root)

        copy_collection_items = package_publish.children.get(
            task_name="copycollectionitems"
        )

        expected_copies: list[dict[str, Any]] = []
        for arch in architectures:
            for binary_artifact in binaries[arch]:
                expected_copies.append(
                    {
                        "source_items": [f"{binary_artifact.id}@artifacts"],
                        "target_collection": target_suite.id,
                        "unembargo": True,
                        "replace": False,
                        "variables": {
                            "component": "main",
                            "section": "misc",
                            "priority": "optional",
                        },
                    }
                )
        expected_copies.append(
            {
                "source_items": [
                    {
                        "collection": source_task_history_collection.id,
                        "lookup_filters": [
                            ["same_workflow", binary_artifacts_lookup]
                        ],
                    }
                ],
                "target_collection": target_task_history_collection.id,
                "unembargo": True,
                "replace": False,
            }
        )
        self.assertEqual(
            copy_collection_items.task_data, {"copies": expected_copies}
        )
        self.assert_updates_indexes(package_publish, target_suite)

    def test_compute_dynamic_data_binary_package(self) -> None:
        upload_artifact = (
            self.playground.create_upload_artifacts(src_name="hello")
        ).upload

        wr = self.playground.create_workflow(
            task_name="package_publish",
            task_data=PackagePublishWorkflowData(
                binary_artifacts=LookupMultiple.parse_obj([upload_artifact.id]),
                target_suite=f"bookworm@{CollectionCategory.SUITE}",
            ),
        )
        workflow = self.get_workflow(wr)

        self.assertEqual(
            workflow.compute_dynamic_data(TaskDatabase(wr)),
            BaseDynamicTaskData(
                subject="hello",
                parameter_summary="hello → "
                f"bookworm@{CollectionCategory.SUITE}",
            ),
        )
