# 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 SbuildWorkflow class."""

from typing import Any

import debusine.worker.tags as wtags
from debusine.artifacts.models import (
    ArtifactCategory,
    BareDataCategory,
    CollectionCategory,
    TaskTypes,
)
from debusine.client.models import RuntimeParameter
from debusine.db.models import (
    Artifact,
    CollectionItem,
    TaskDatabase,
    WorkRequest,
    WorkflowTemplate,
    default_workspace,
)
from debusine.server.collections.tests.utils import CollectionTestMixin
from debusine.server.workflows import (
    SbuildWorkflow,
    WorkflowValidationError,
    workflow_utils,
)
from debusine.server.workflows.base import orchestrate_workflow
from debusine.server.workflows.models import (
    SbuildWorkflowData,
    SbuildWorkflowDynamicData,
)
from debusine.server.workflows.tests.helpers import WorkflowTestBase
from debusine.tasks import TaskConfigError
from debusine.tasks.models import (
    ActionTypes,
    ActionUpdateCollectionWithArtifacts,
    ActionUpdateCollectionWithData,
    BackendType,
    InputArtifactSingle,
    SbuildInput,
)
from debusine.test.test_utils import create_system_tarball_data


class SbuildWorkflowTests(
    CollectionTestMixin, WorkflowTestBase[SbuildWorkflow]
):
    """Unit tests for SbuildWorkflow class."""

    def create_sbuild_template(
        self,
        name: str = "sbuild",
        static_parameters: dict[str, Any] | None = None,
    ) -> WorkflowTemplate:
        """Create a sbuild WorkflowTemplate."""
        return WorkflowTemplate.objects.create(
            name=name,
            workspace=default_workspace(),
            task_name="sbuild",
            static_parameters=static_parameters or {},
            runtime_parameters=RuntimeParameter.ANY,
        )

    def create_sbuild_workflow(
        self,
        template: WorkflowTemplate,
        source: Artifact,
        target_distribution: str = "debian:bookworm",
        architectures: list[str] = ["amd64"],
        validate: bool = True,
        **kwargs: Any,
    ) -> WorkRequest:
        return self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.pk,
                },
                "target_distribution": target_distribution,
                "architectures": architectures,
                **kwargs,
            },
            validate=validate,
        )

    def test_create_orchestrator(self) -> None:
        """Test instantiating a SbuildWorkflow."""
        source_artifact = self.playground.create_source_artifact()
        self.playground.create_debian_environment(
            vendor="debian", codename="bookworm"
        )
        task_data: dict[str, Any] = {
            "input": {
                "source_artifact": source_artifact.id,
            },
            "target_distribution": "debian:bookworm",
            "architectures": ["all", "amd64"],
        }
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=task_data,
            workspace=default_workspace(),
        )
        w = self.get_workflow(wr)
        self.assertEqual(w.data.input.source_artifact, source_artifact.id)
        self.assertEqual(w.data.target_distribution, "debian:bookworm")
        self.assertEqual(w.data.backend, BackendType.UNSHARE)
        self.assertEqual(w.data.architectures, ["all", "amd64"])
        self.assertIsNone(w.data.extra_repositories)
        self.assertIsNone(w.data.binnmu)

        task_data["binnmu"] = {
            "changelog": "Rebuild for 64bit time_t",
            "suffix": "+b1",
        }
        task_data["extra_repositories"] = [
            {
                "url": "http://example.net/foo",
                "suite": "bookworm",
                "components": ["main", "non-free"],
            },
        ]
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=task_data,
            workspace=default_workspace(),
        )
        w = self.get_workflow(wr)
        assert w.data.binnmu is not None
        self.assertEqual(w.data.binnmu.suffix, "+b1")
        assert w.data.extra_repositories is not None
        self.assertEqual(w.data.extra_repositories[0].suite, "bookworm")

    def test_compute_system_required_tags(self) -> None:
        source_artifact = self.playground.create_source_artifact(
            name="hello", version="1.0-1", architectures={"all"}
        )
        self.playground.create_debian_environment(codename="bookworm")
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=SbuildWorkflowData(
                input=SbuildInput(source_artifact=source_artifact.id),
                target_distribution="debian:bookworm",
                architectures=["all"],
            ),
        )
        workflow = self.get_workflow(wr)

        self.assertCountEqual(
            workflow.compute_system_required_tags(),
            [wtags.WORKER_TYPE_NOT_ASSIGNABLE],
        )

    def test_compute_dynamic_data(self) -> None:
        """Dynamic data includes a parameter summary."""
        source_artifact = self.playground.create_source_artifact(
            name="hello", version="1.0-1", architectures={"all"}
        )
        self.playground.create_debian_environment(codename="bookworm")
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=SbuildWorkflowData(
                input=SbuildInput(source_artifact=source_artifact.id),
                target_distribution="debian:bookworm",
                architectures=["all"],
            ),
        )
        workflow = self.get_workflow(wr)

        self.assertEqual(
            workflow.compute_dynamic_data(TaskDatabase(wr)),
            SbuildWorkflowDynamicData(
                subject="hello",
                parameter_summary="hello_1.0-1",
                source_artifact_id=source_artifact.id,
            ),
        )

    def test_get_input_artifacts(self) -> None:
        source_artifact = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "all"}
        )
        self.playground.create_debian_environment(codename="bookworm")
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=SbuildWorkflowData(
                input=SbuildInput(source_artifact=source_artifact.id),
                target_distribution="debian:bookworm",
                architectures=["all"],
            ),
        )
        wf = self.get_workflow(wr)

        self.assertIsNone(wf.dynamic_data)
        self.assertEqual(
            wf.get_input_artifacts(),
            [
                InputArtifactSingle(
                    lookup=wf.data.input.source_artifact,
                    label="input.source_artifact",
                    artifact_id=None,
                )
            ],
        )

        wf.dynamic_data = wf.build_dynamic_data(TaskDatabase(wr))

        self.assertIsNotNone(wf.dynamic_data)
        self.assertEqual(
            wf.get_input_artifacts(),
            [
                InputArtifactSingle(
                    lookup=wf.data.input.source_artifact,
                    label="input.source_artifact",
                    artifact_id=source_artifact.id,
                )
            ],
        )

    def test_validate_input(self) -> None:
        """Test that validate_input passes a valid case."""
        tarball_item = self.playground.create_debian_environment(
            variant="sbuild"
        )

        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "all"}
        )

        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.pk,
                },
                "target_distribution": "debian:bookworm",
                "architectures": ["all", "amd64"],
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)

        o = self.get_workflow(wr)
        self.assertEqual(
            workflow_utils.source_package(
                o, configuration_key="input.source_artifact"
            ),
            source,
        )
        self.assertEqual(o.architectures, {"all"})
        self.assertEqual(o._get_environment("amd64"), tarball_item.artifact)
        o.validate_input()

        # environment_variant
        self.playground.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL,
            data=create_system_tarball_data(
                codename="bookworm", variant="buildd"
            ),
        )
        # add with explicit :variant
        tarball_item = self.playground.create_debian_environment(
            environment=tarball_item.artifact,
            variables={"variant": "buildd", "backend": BackendType.UNSHARE},
        )

        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.pk,
                },
                "target_distribution": "debian:bookworm",
                "architectures": ["all", "amd64"],
                "environment_variant": "buildd",
            },
        )
        o = self.get_workflow(wr)
        self.assertEqual(o._get_environment("amd64"), tarball_item.artifact)
        o.validate_input()

    def test_computation_of_architecture_intersection(self) -> None:
        """Test computation of architectures to build."""
        template = self.create_sbuild_template()

        dsc_archs = {
            "any": {"amd64", "arm64"},
            "all": {
                "all",
            },
            "any all": {"all", "amd64", "arm64"},
            "arm64": {
                "arm64",
            },
            "linux-any": {"amd64", "arm64"},
            "gnu-any-any": {"amd64", "arm64"},
        }
        for dsc_arch, expected_archs in dsc_archs.items():
            source = self.create_source_package(
                "hello", "1.0", dsc_fields={"Architecture": dsc_arch}
            )

            for wr_archs in (["amd64"], ["all"], ["arm64", "all"]):
                with self.subTest("", dsc_arch=dsc_arch, wr_archs=wr_archs):
                    o = self.get_workflow(
                        self.playground.create_workflow(
                            template,
                            task_data={
                                "input": {
                                    "source_artifact": source.pk,
                                },
                                "target_distribution": "debian:bookworm",
                                "architectures": wr_archs,
                            },
                            validate=False,
                        ),
                    )
                    archs = expected_archs.intersection(wr_archs)
                    if archs:
                        self.assertSetEqual(o.architectures, archs)
                    else:
                        with self.assertRaisesRegex(
                            WorkflowValidationError,
                            "None of the workflow architectures are supported"
                            " by this package",
                        ):
                            o.architectures

    def test_create_work_request_unshare(self) -> None:
        """Test creating a Sbuild workflow workrequest with unshare."""
        environments = self.playground.create_debian_environments_collection()
        tarballs = {
            arch: self.playground.create_artifact(
                category=ArtifactCategory.SYSTEM_TARBALL,
                data=create_system_tarball_data(
                    codename="bookworm", variant="apt", architecture=arch
                ),
            )[0]
            for arch in ("amd64", "s390x")
        }
        for arch in ("amd64", "s390x"):
            environments.manager.add_artifact(
                tarballs[arch],
                user=self.playground.get_default_user(),
                variables={"backend": BackendType.UNSHARE},
            )
        default_workspace().collections.filter(
            category=CollectionCategory.PACKAGE_BUILD_LOGS
        ).delete()

        # Create a source package
        source = self.create_source_package(
            "hello",
            "1.0",
            dsc_fields={
                "Binary": "hello, hello-dev",
                "Architecture": "all amd64 s390x",
            },
        )

        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.pk,
                },
                "backend": BackendType.UNSHARE,
                "target_distribution": "debian:bookworm",
                "architectures": ["all", "amd64", "s390x"],
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)
        self.assertTrue(orchestrate_workflow(wr))

        expected_children = [
            (
                "all",
                {
                    "build_architecture": "amd64",
                    "build_components": ["all"],
                    "build_profiles": None,
                },
            ),
            (
                "amd64",
                {
                    "build_architecture": "amd64",
                    "build_components": ["any"],
                    "build_profiles": None,
                },
            ),
            (
                "s390x",
                {
                    "build_architecture": "s390x",
                    "build_components": ["any"],
                    "build_profiles": None,
                },
            ),
        ]

        children = list(WorkRequest.objects.filter(parent=wr).order_by("id"))
        self.assertEqual(len(children), len(expected_children))

        for child, expected_child in zip(children, expected_children):
            expected_arch, expected_data = expected_child
            self.assertEqual(child.status, WorkRequest.Statuses.PENDING)
            self.assertEqual(child.task_type, TaskTypes.WORKER)
            self.assertEqual(child.task_name, "sbuild")
            self.assertEqual(
                child.task_data,
                {
                    'backend': BackendType.UNSHARE,
                    'environment': 'debian/match:codename=bookworm',
                    'input': {'source_artifact': source.pk},
                    'binnmu': None,
                    'extra_repositories': None,
                    'build_dep_resolver': None,
                    'aspcud_criteria': None,
                    **expected_data,
                },
            )
            promise = CollectionItem.objects.get(
                parent_collection=wr.internal_collection,
                child_type=CollectionItem.Types.BARE,
                category=BareDataCategory.PROMISE,
                name=f"build-{expected_arch}",
            )
            self.assertEqual(
                promise.data,
                {
                    "promise_work_request_id": child.id,
                    "promise_workflow_id": wr.id,
                    "promise_category": ArtifactCategory.UPLOAD,
                    "binary_names": ["hello", "hello-dev"],
                    "source_package_name": "hello",
                    "architecture": expected_arch,
                },
            )
            self.assert_work_request_event_reactions(
                child,
                on_success=[
                    ActionUpdateCollectionWithArtifacts(
                        artifact_filters={"category": ArtifactCategory.UPLOAD},
                        collection="internal@collections",
                        name_template=f"build-{expected_arch}",
                        variables={
                            "binary_names": ["hello", "hello-dev"],
                            "source_package_name": "hello",
                            "architecture": expected_arch,
                        },
                    ),
                    ActionUpdateCollectionWithArtifacts(
                        artifact_filters={
                            "category": ArtifactCategory.PACKAGE_BUILD_LOG
                        },
                        collection="internal@collections",
                        name_template=f"buildlog-{expected_arch}",
                        variables={
                            "source_package_name": "hello",
                            "source_package_version": "1.0",
                            "architecture": expected_arch,
                        },
                    ),
                ],
            )
            self.assertQuerySetEqual(child.dependencies.all(), [])
            self.assertEqual(
                child.workflow_data_json,
                {
                    "display_name": f"Build {expected_arch}",
                    "step": f"build-{expected_arch}",
                },
            )

        # populate() idempotence
        orchestrator = self.get_workflow(wr)
        orchestrator.populate()
        children = list(WorkRequest.objects.filter(parent=wr))
        self.assertEqual(len(children), len(expected_children))

    def test_create_work_request_arch_all_build_architecture(self) -> None:
        """Test using a non-default `arch_all_build_architecture`."""
        environments = self.playground.create_debian_environments_collection()
        tarballs = {
            arch: self.playground.create_artifact(
                category=ArtifactCategory.SYSTEM_TARBALL,
                data=create_system_tarball_data(
                    codename="bookworm", variant="apt", architecture=arch
                ),
            )[0]
            for arch in ("amd64", "s390x")
        }
        for arch in ("amd64", "s390x"):
            environments.manager.add_artifact(
                tarballs[arch],
                user=self.playground.get_default_user(),
                variables={"backend": BackendType.UNSHARE},
            )
        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "all amd64 s390x"}
        )
        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.pk,
                },
                "backend": BackendType.UNSHARE,
                "target_distribution": "debian:bookworm",
                "architectures": ["all", "amd64", "s390x"],
                "arch_all_build_architecture": "s390x",
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)
        self.assertTrue(orchestrate_workflow(wr))

        expected_children = [
            (
                "all",
                {
                    "build_architecture": "s390x",
                    "build_components": ["all"],
                    "build_profiles": None,
                },
            ),
            (
                "amd64",
                {
                    "build_architecture": "amd64",
                    "build_components": ["any"],
                    "build_profiles": None,
                },
            ),
            (
                "s390x",
                {
                    "build_architecture": "s390x",
                    "build_components": ["any"],
                    "build_profiles": None,
                },
            ),
        ]

        children = list(WorkRequest.objects.filter(parent=wr).order_by("id"))
        self.assertEqual(len(children), len(expected_children))

    def test_create_work_request_retain_build_logs(self) -> None:
        """Test retaining build logs in a collection."""
        environments = self.playground.create_debian_environments_collection()
        tarball, _ = self.playground.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL,
            data=create_system_tarball_data(
                codename="bookworm", variant="apt", architecture="amd64"
            ),
        )
        environments.manager.add_artifact(
            tarball,
            user=self.playground.get_default_user(),
            variables={"backend": BackendType.UNSHARE},
        )
        build_logs = default_workspace().get_singleton_collection(
            user=self.playground.get_default_user(),
            category=CollectionCategory.PACKAGE_BUILD_LOGS,
        )

        # Create a source package
        source = self.create_source_package(
            "hello",
            "1.0",
            dsc_fields={
                "Binary": "hello, hello-dev",
                "Architecture": "amd64",
            },
        )

        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {"source_artifact": source.pk},
                "backend": BackendType.UNSHARE,
                "target_distribution": "debian:bookworm",
                "architectures": ["amd64"],
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)
        self.assertTrue(orchestrate_workflow(wr))

        child = WorkRequest.objects.get(parent=wr)
        self.assertEqual(child.status, WorkRequest.Statuses.PENDING)
        self.assertEqual(child.task_type, TaskTypes.WORKER)
        self.assertEqual(child.task_name, "sbuild")
        self.assertEqual(
            child.task_data,
            {
                "input": {"source_artifact": source.pk},
                "build_architecture": "amd64",
                "build_components": ["any"],
                "build_profiles": None,
                "binnmu": None,
                "extra_repositories": None,
                "build_dep_resolver": None,
                "aspcud_criteria": None,
                "backend": BackendType.UNSHARE,
                "environment": "debian/match:codename=bookworm",
            },
        )
        promise = CollectionItem.objects.get(
            parent_collection=wr.internal_collection,
            child_type=CollectionItem.Types.BARE,
            category=BareDataCategory.PROMISE,
            name="build-amd64",
        )
        self.assertEqual(
            promise.data,
            {
                "promise_work_request_id": child.id,
                "promise_workflow_id": wr.id,
                "promise_category": ArtifactCategory.UPLOAD,
                "binary_names": ["hello", "hello-dev"],
                "architecture": "amd64",
                "source_package_name": "hello",
            },
        )
        self.assert_work_request_event_reactions(
            child,
            on_creation=[
                ActionUpdateCollectionWithData(
                    collection=build_logs.id,
                    category=BareDataCategory.PACKAGE_BUILD_LOG,
                    data={
                        "work_request_id": child.id,
                        "vendor": "debian",
                        "codename": "bookworm",
                        "architecture": "amd64",
                        "srcpkg_name": "hello",
                        "srcpkg_version": "1.0",
                    },
                ),
            ],
            on_unblock=[],
            on_success=[
                ActionUpdateCollectionWithArtifacts(
                    artifact_filters={"category": ArtifactCategory.UPLOAD},
                    collection="internal@collections",
                    name_template="build-amd64",
                    variables={
                        "binary_names": ["hello", "hello-dev"],
                        "architecture": "amd64",
                        "source_package_name": "hello",
                    },
                ),
                ActionUpdateCollectionWithArtifacts(
                    artifact_filters={
                        "category": ArtifactCategory.PACKAGE_BUILD_LOG
                    },
                    collection="internal@collections",
                    name_template="buildlog-amd64",
                    variables={
                        "source_package_name": "hello",
                        "source_package_version": "1.0",
                        "architecture": "amd64",
                    },
                ),
                ActionUpdateCollectionWithArtifacts(
                    collection=build_logs.id,
                    variables={
                        "work_request_id": child.id,
                        "vendor": "debian",
                        "codename": "bookworm",
                        "architecture": "amd64",
                        "srcpkg_name": "hello",
                        "srcpkg_version": "1.0",
                    },
                    artifact_filters={
                        "category": ArtifactCategory.PACKAGE_BUILD_LOG
                    },
                ),
            ],
            on_failure=[],
        )
        self.assertQuerySetEqual(child.dependencies.all(), [])
        self.assertEqual(
            child.workflow_data_json,
            {"display_name": "Build amd64", "step": "build-amd64"},
        )

    def test_create_work_request_retry_delays(self) -> None:
        """Test adding automatic retries with delays."""
        environments = self.playground.create_debian_environments_collection()
        tarball, _ = self.playground.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL,
            data=create_system_tarball_data(
                codename="bookworm", variant="apt", architecture="amd64"
            ),
        )
        environments.manager.add_artifact(
            tarball,
            user=self.playground.get_default_user(),
            variables={"backend": BackendType.UNSHARE},
        )
        source = self.create_source_package(
            "hello",
            "1.0",
            dsc_fields={
                "Binary": "hello, hello-dev",
                "Architecture": "amd64",
            },
        )
        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {"source_artifact": source.pk},
                "backend": BackendType.UNSHARE,
                "target_distribution": "debian:bookworm",
                "architectures": ["amd64"],
                "retry_delays": ["1h", "1d"],
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)
        self.assertTrue(orchestrate_workflow(wr))

        child = WorkRequest.objects.get(parent=wr)
        self.assertEqual(child.status, WorkRequest.Statuses.PENDING)
        self.assertEqual(child.task_type, TaskTypes.WORKER)
        self.assertEqual(child.task_name, "sbuild")
        self.assertEqual(
            child.task_data,
            {
                "input": {"source_artifact": source.pk},
                "build_architecture": "amd64",
                "build_components": ["any"],
                "build_profiles": None,
                "binnmu": None,
                "extra_repositories": None,
                "build_dep_resolver": None,
                "aspcud_criteria": None,
                "backend": BackendType.UNSHARE,
                "environment": "debian/match:codename=bookworm",
            },
        )
        self.assertIn(
            ActionUpdateCollectionWithArtifacts(
                artifact_filters={"category": ArtifactCategory.UPLOAD},
                collection="internal@collections",
                created_at=None,
                name_template="build-amd64",
                variables={
                    "binary_names": ["hello", "hello-dev"],
                    "architecture": "amd64",
                    "source_package_name": "hello",
                },
            ),
            child.event_reactions.on_success,
        )
        self.assertIn(
            {
                "action": ActionTypes.RETRY_WITH_DELAYS,
                "delays": ["1h", "1d"],
            },
            child.event_reactions_json["on_failure"],
        )

    def test_create_work_request_signing_template_names(self) -> None:
        """Test signing template handling."""
        environments = self.playground.create_debian_environments_collection()
        tarball, _ = self.playground.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL,
            data=create_system_tarball_data(
                codename="bookworm", variant="apt", architecture="amd64"
            ),
        )
        environments.manager.add_artifact(
            tarball,
            user=self.playground.get_default_user(),
            variables={"backend": BackendType.UNSHARE},
        )
        source = self.create_source_package(
            "hello",
            "1.0",
            dsc_fields={
                "Binary": "hello-unsigned",
                "Architecture": "amd64",
            },
        )
        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {"source_artifact": source.pk},
                "backend": BackendType.UNSHARE,
                "target_distribution": "debian:bookworm",
                "architectures": ["amd64"],
                "signing_template_names": {"amd64": ["hello-unsigned"]},
            },
        )
        self.assertEqual(wr.status, WorkRequest.Statuses.PENDING)
        self.assertTrue(orchestrate_workflow(wr))

        child = WorkRequest.objects.get(parent=wr)
        self.assertEqual(child.status, WorkRequest.Statuses.PENDING)
        self.assertEqual(child.task_type, TaskTypes.WORKER)
        self.assertEqual(child.task_name, "sbuild")
        self.assertIn(
            ActionUpdateCollectionWithArtifacts(
                artifact_filters={"category": ArtifactCategory.UPLOAD},
                collection="internal@collections",
                created_at=None,
                name_template="build-amd64",
                variables={
                    "binary_names": ["hello-unsigned"],
                    "architecture": "amd64",
                    "source_package_name": "hello",
                },
            ),
            child.event_reactions.on_success,
        )
        self.assertIn(
            ActionUpdateCollectionWithArtifacts(
                artifact_filters={
                    "category": ArtifactCategory.BINARY_PACKAGE,
                    "data__deb_fields__Package": "hello-unsigned",
                },
                collection="internal@collections",
                created_at=None,
                name_template="signing-template-amd64-hello-unsigned",
                variables={
                    "architecture": "amd64",
                    "binary_package_name": "hello-unsigned",
                },
            ),
            child.event_reactions.on_success,
        )

    def test_source_package_error_handling(self) -> None:
        """Test source_package error handling."""
        template = self.create_sbuild_template()

        invalid_artifact, _ = self.playground.create_artifact(
            category=ArtifactCategory.TEST
        )
        with self.assertRaisesRegex(
            TaskConfigError,
            r"^input.source_artifact: unexpected artifact category: "
            r"'debusine:test'. Valid categories: "
            r"\['debian:source-package', 'debian:upload'\]$",
        ):
            self.create_sbuild_workflow(template, invalid_artifact)

        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": ""}
        )
        wr = self.create_sbuild_workflow(template, source, validate=False)
        with self.assertRaisesRegex(
            WorkflowValidationError, "package architecture list is empty"
        ):
            self.get_workflow(wr).architectures

        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "any-armhf"}
        )
        wr = self.create_sbuild_workflow(
            template, source, architectures=["kfreebsd-i386"], validate=False
        )
        with self.assertRaisesRegex(
            WorkflowValidationError,
            "None of the workflow architectures are supported by this package",
        ):
            self.get_workflow(wr).architectures

        # wildcards support
        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "any-amd64"}
        )
        wr = self.create_sbuild_workflow(
            template,
            source,
            architectures=["i386", "hurd-amd64"],
            validate=False,
        )
        self.assertEqual(self.get_workflow(wr).architectures, {"hurd-amd64"})

        # wildcards are for packages, not workflows
        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "amd64"}
        )
        wr = self.create_sbuild_workflow(
            template, source, architectures=["any"], validate=False
        )
        with self.assertRaisesRegex(
            WorkflowValidationError,
            "None of the workflow architectures are supported by this package",
        ):
            self.get_workflow(wr).architectures

        wr = self.create_sbuild_workflow(
            template, source, backend=BackendType.QEMU, validate=False
        )
        self.playground.create_debian_environments_collection()
        with self.assertRaisesRegex(
            WorkflowValidationError, "environment not found for"
        ):
            self.get_workflow(wr)._get_environment("any")

        wr = self.create_sbuild_workflow(
            template, source, target_distribution="Ubuntu", validate=False
        )
        with self.assertRaisesRegex(
            WorkflowValidationError,
            "target_distribution must be in vendor:codename format",
        ):
            self.get_workflow(wr).validate_input()

    def test_populate_upload(self) -> None:
        """The workflow accepts debian:upload artifacts."""
        upload_artifacts = self.playground.create_upload_artifacts()
        self.playground.create_debian_environment(codename="bookworm")

        wf = self.playground.create_workflow(
            task_name="sbuild",
            task_data=SbuildWorkflowData(
                input=SbuildInput(
                    source_artifact=upload_artifacts.upload.id,
                ),
                target_distribution="debian:bookworm",
                architectures=["amd64"],
            ),
        )
        self.get_workflow(wf).populate()

        children = list(WorkRequest.objects.filter(parent=wf))
        self.assertEqual(len(children), 1)
        child = children[0]
        self.assertEqual(
            child.task_data["input"]["source_artifact"],
            f"{upload_artifacts.source.id}@artifacts",
        )

    def test_populate_experimental(self) -> None:
        """The workflow handles overlay distributions."""
        source_artifact = self.playground.create_source_artifact(
            architectures={"all"}
        )
        self.playground.create_debian_environment(
            codename="experimental", variant="sbuild"
        )
        wr = self.playground.create_workflow(
            task_name="sbuild",
            task_data=SbuildWorkflowData(
                input=SbuildInput(
                    source_artifact=source_artifact.id,
                ),
                target_distribution="debian:experimental",
                arch_all_build_architecture="amd64",
                architectures=["all"],
            ),
        )
        self.get_workflow(wr).populate()
        children = list(wr.children.all())
        self.assertEqual(len(children), 1)
        child = children[0]
        self.assertEqual(len(child.task_data["extra_repositories"]), 1)
        repo = child.task_data["extra_repositories"][0]
        self.assertEqual(repo["suite"], "experimental")

        self.assertEqual(child.task_data["build_dep_resolver"], "aspcud")
        self.assertEqual(
            child.task_data["aspcud_criteria"],
            (
                "-count(down),-count(changed,APT-Release:=/experimental/),"
                "-removed,-changed,-new"
            ),
        )

    def test_label(self) -> None:
        """Test get_label."""
        source = self.create_source_package(
            "hello", "1.0", dsc_fields={"Architecture": "all"}
        )
        self.playground.create_debian_environment(codename="bookworm")
        template = self.create_sbuild_template()
        wr = self.playground.create_workflow(
            template,
            task_data={
                "input": {
                    "source_artifact": source.id,
                },
                "target_distribution": "debian:bookworm",
                "architectures": ["all", "amd64"],
            },
        )

        workflow = self.get_workflow(wr)
        self.assertEqual(workflow.get_label(), "sbuild")
